LibWeb: Implement legacy-clone of session storage shed on window.open

When a new window is opened via window.open() with an opener
(non-null), the session storage must be cloned from the opener's
storage shed into the new window's storage shed. This implements
the legacy-clone a traversable storage shed algorithm from the
Storage spec.
This commit is contained in:
Martin Chrástek 2026-05-18 08:52:19 +02:00 committed by Shannon Booth
parent 93bbdf1f55
commit 437c8b1d19
10 changed files with 112 additions and 1 deletions

View file

@ -125,7 +125,11 @@ GC::Ref<TraversableNavigable> TraversableNavigable::create_a_new_top_level_trave
traversable->m_session_history_entries.append(*initial_history_entry);
traversable->set_has_session_history_entry_and_ready_for_navigation();
// FIXME: 10. If opener is non-null, then legacy-clone a traversable storage shed given opener's top-level traversable and traversable. [STORAGE]
// 10. If opener is non-null, then legacy-clone a traversable storage shed given opener's top-level traversable and traversable. [STORAGE]
if (opener) {
auto opener_traversable = opener->top_level_traversable();
traversable->storage_shed().legacy_clone(opener_traversable->storage_shed(), page);
}
// 11. Append traversable to the user agent's top-level traversable set.
user_agent_top_level_traversable_set().set(traversable);

View file

@ -183,4 +183,9 @@ void SessionStorageBottle::remove(String const& key)
m_map.remove(key);
}
void SessionStorageBottle::copy_map_from(SessionStorageBottle const& other)
{
m_map = other.m_map;
}
}

View file

@ -97,6 +97,8 @@ public:
virtual void clear() override;
virtual void remove(String const&) override;
void copy_map_from(SessionStorageBottle const&);
private:
explicit SessionStorageBottle(Optional<u64> quota)
: StorageBottle(quota)

View file

@ -4,9 +4,11 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/TypeCasts.h>
#include <LibGC/Heap.h>
#include <LibWeb/HTML/Scripting/Environments.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/StorageAPI/StorageBottle.h>
#include <LibWeb/StorageAPI/StorageShed.h>
namespace Web::StorageAPI {
@ -38,4 +40,28 @@ GC::Ptr<StorageShelf> StorageShed::obtain_a_storage_shelf(HTML::EnvironmentSetti
});
}
// https://storage.spec.whatwg.org/#legacy-clone-a-traversable-storage-shed
// To legacy-clone a traversable storage shed, given a traversable navigable A and a traversable navigable B, run these steps:
void StorageShed::legacy_clone(StorageShed const& a_storage_shed, GC::Ref<Page> page)
{
// 1. For each key → shelf of As storage shed:
for (auto const& [key, shelf] : a_storage_shed.m_data) {
// 1. Let newShelf be the result of running create a storage shelf with "session".
auto new_shelf = StorageShelf::create(heap(), page, key, StorageType::Session);
// 2. Set newShelfs bucket map["default"]'s bottle map["sessionStorage"]'s map to a clone of shelfs bucket map["default"]'s bottle map["sessionStorage"]'s map.
auto& shelf_bucket = *shelf->bucket_map().get("default"sv).value();
auto& new_shelf_bucket = *new_shelf->bucket_map().get("default"sv).value();
auto const& shelf_bottle = *shelf_bucket.bottle_map()[to_underlying(StorageEndpointType::SessionStorage)];
auto& new_shelf_bottle = *new_shelf_bucket.bottle_map()[to_underlying(StorageEndpointType::SessionStorage)];
as<SessionStorageBottle>(new_shelf_bottle).copy_map_from(static_cast<SessionStorageBottle const&>(shelf_bottle));
// 3. Set Bs storage shed[key] to newShelf.
m_data.set(key, new_shelf);
}
}
}

View file

@ -25,6 +25,7 @@ public:
static GC::Ref<StorageShed> create(GC::Heap& heap) { return heap.allocate<StorageShed>(); }
GC::Ptr<StorageShelf> obtain_a_storage_shelf(HTML::EnvironmentSettingsObject&, StorageType);
void legacy_clone(StorageShed const&, GC::Ref<Page>);
virtual void visit_edges(GC::Cell::Visitor& visitor) override;

View file

@ -32,6 +32,7 @@ Text/input/wpt-import/html/syntax/parsing/unclosed-svg-script.html
; These tests require cross-origin iframe access to succeed.
Text/input/wpt-import/webstorage/event_session_oldvalue.html
Text/input/wpt-import/webstorage/event_session_storagearea.html
Text/input/wpt-import/webstorage/storage_session_window_open.window.html
Text/input/navigation/navigation-navigate.html
Text/input/navigation/location-reload-fetch.html
Text/input/HTML/session-storage-event-fired-to-lazy-window.html

View file

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass A new window to make sure there is a copy of the previous window's sessionStorage, and that they diverge after a change

View file

@ -0,0 +1,41 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WebStorage Test: sessionStorage - second page</title>
</head>
<body>
<script>
var storage = window.sessionStorage;
var assertions = [];
assertions.push({
actual: storage.getItem("FOO"),
expected: "BAR",
message: "storage.getItem('FOO')"
});
storage.setItem("FOO", "BAR-NEWWINDOW");
assertions.push({
actual: storage.getItem("FOO"),
expected: "BAR-NEWWINDOW",
message: "value for FOO after changing"
});
assertions.push({
actual: window.opener.sessionStorage.getItem("FOO"),
expected: "BAR",
message: "value for FOO in my opening window"
});
assertions.push({
actual: storage.getItem("BAZ"),
expected: null,
message: "value for BAZ set after window.open(), is not set in new window"
});
window.opener.postMessage(assertions, '*');
</script>
</body>
</html>

View file

@ -0,0 +1,8 @@
<!doctype html>
<meta charset=utf-8>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<div id=log></div>
<script src="../webstorage/storage_session_window_open.window.js"></script>

View file

@ -0,0 +1,17 @@
async_test(function(t) {
var storage = window.sessionStorage;
storage.clear();
storage.setItem("FOO", "BAR");
var win = window.open("resources/storage_session_window_open_second.html");
storage.setItem("BAZ", "QUX");
window.addEventListener('message', t.step_func(function(e) {
e.data.forEach(t.step_func(function(assertion) {
assert_equals(assertion.actual, assertion.expected, assertion.message);
}));
win.close();
t.done();
}));
}, "A new window to make sure there is a copy of the previous window's sessionStorage, and that they diverge after a change");