From 2066ed2318eae490e7581a634959637698861e5a Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Thu, 26 Dec 2024 11:56:03 +1300 Subject: [PATCH] LibWeb: Correctly initialize Storage objects on the Document Instead of storing all storage objects in static memory, we now follow the the spec by lazily creating a unique Storage object on each document object. Each Storage object now holds a 'proxy' to the underlying backing storage. For now, this proxy is simply a reference to the backing object. In the future, it will need to be some type of interface object that stores on a SQLite database or similar. Session storage is now correctly stored / tracked as part of the TraversableNavigable object. Local storage is still stored in a static map, but eventually this should be factored into something that is stored at the user agent level. --- Libraries/LibWeb/CMakeLists.txt | 4 + Libraries/LibWeb/DOM/Document.cpp | 3 + Libraries/LibWeb/DOM/Document.h | 14 +++ Libraries/LibWeb/Forward.h | 6 ++ Libraries/LibWeb/HTML/Storage.cpp | 44 +++++----- Libraries/LibWeb/HTML/Storage.h | 13 +-- Libraries/LibWeb/HTML/TraversableNavigable.h | 8 ++ Libraries/LibWeb/HTML/Window.cpp | 59 +++++++++---- Libraries/LibWeb/StorageAPI/StorageBottle.cpp | 88 +++++++++++++++++++ Libraries/LibWeb/StorageAPI/StorageBottle.h | 52 +++++++++++ .../LibWeb/StorageAPI/StorageEndpoint.cpp | 24 +++++ Libraries/LibWeb/StorageAPI/StorageEndpoint.h | 38 ++++++++ Libraries/LibWeb/StorageAPI/StorageShed.cpp | 38 ++++++++ Libraries/LibWeb/StorageAPI/StorageShed.h | 29 ++++++ Libraries/LibWeb/StorageAPI/StorageShelf.cpp | 20 +++++ Libraries/LibWeb/StorageAPI/StorageShelf.h | 27 ++++++ Libraries/LibWeb/StorageAPI/StorageType.h | 18 ++++ .../webstorage/event_session_storagearea.txt | 6 ++ .../webstorage/event_session_storagearea.html | 40 +++++++++ .../resources/session_set_item_iframe.html | 16 ++++ 20 files changed, 503 insertions(+), 44 deletions(-) create mode 100644 Libraries/LibWeb/StorageAPI/StorageBottle.cpp create mode 100644 Libraries/LibWeb/StorageAPI/StorageBottle.h create mode 100644 Libraries/LibWeb/StorageAPI/StorageEndpoint.cpp create mode 100644 Libraries/LibWeb/StorageAPI/StorageEndpoint.h create mode 100644 Libraries/LibWeb/StorageAPI/StorageShed.cpp create mode 100644 Libraries/LibWeb/StorageAPI/StorageShed.h create mode 100644 Libraries/LibWeb/StorageAPI/StorageShelf.cpp create mode 100644 Libraries/LibWeb/StorageAPI/StorageShelf.h create mode 100644 Libraries/LibWeb/StorageAPI/StorageType.h create mode 100644 Tests/LibWeb/Text/expected/wpt-import/webstorage/event_session_storagearea.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/webstorage/event_session_storagearea.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/webstorage/resources/session_set_item_iframe.html diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 9413a99d05c..04db006e130 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -690,8 +690,12 @@ set(SOURCES ServiceWorker/ServiceWorkerRegistration.cpp SRI/SRI.cpp StorageAPI/NavigatorStorage.cpp + StorageAPI/StorageBottle.cpp + StorageAPI/StorageEndpoint.cpp StorageAPI/StorageKey.cpp StorageAPI/StorageManager.cpp + StorageAPI/StorageShed.cpp + StorageAPI/StorageShelf.cpp Streams/AbstractOperations.cpp Streams/ByteLengthQueuingStrategy.cpp Streams/CountQueuingStrategy.cpp diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index 352cdb8150f..ac6f4f534d5 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -116,6 +116,7 @@ #include #include #include +#include #include #include #include @@ -537,6 +538,8 @@ void Document::visit_edges(Cell::Visitor& visitor) visitor.visit(m_top_layer_pending_removals); visitor.visit(m_console_client); visitor.visit(m_editing_host_manager); + visitor.visit(m_local_storage_holder); + visitor.visit(m_session_storage_holder); } // https://w3c.github.io/selection-api/#dom-document-getselection diff --git a/Libraries/LibWeb/DOM/Document.h b/Libraries/LibWeb/DOM/Document.h index a2e67af1c7e..a8e4f9c46dc 100644 --- a/Libraries/LibWeb/DOM/Document.h +++ b/Libraries/LibWeb/DOM/Document.h @@ -760,6 +760,12 @@ public: GC::Ptr container_document() const; + GC::Ptr session_storage_holder() { return m_session_storage_holder; } + void set_session_storage_holder(GC::Ptr storage) { m_session_storage_holder = storage; } + + GC::Ptr local_storage_holder() { return m_local_storage_holder; } + void set_local_storage_holder(GC::Ptr storage) { m_local_storage_holder = storage; } + protected: virtual void initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor&) override; @@ -1073,6 +1079,14 @@ private: // https://w3c.github.io/editing/docs/execCommand/#css-styling-flag bool m_css_styling_flag { false }; + + // https://html.spec.whatwg.org/multipage/webstorage.html#session-storage-holder + // A Document object has an associated session storage holder, which is null or a Storage object. It is initially null. + GC::Ptr m_session_storage_holder; + + // https://html.spec.whatwg.org/multipage/webstorage.html#local-storage-holder + // A Document object has an associated local storage holder, which is null or a Storage object. It is initially null. + GC::Ptr m_local_storage_holder; }; template<> diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 2563067e15f..ac03501a03f 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -752,6 +752,12 @@ struct UnderlyingSource; namespace Web::StorageAPI { class NavigatorStorage; class StorageManager; +class StorageShed; + +struct StorageBottle; +struct StorageBucket; +struct StorageEndpoint; +struct StorageShelf; } namespace Web::SVG { diff --git a/Libraries/LibWeb/HTML/Storage.cpp b/Libraries/LibWeb/HTML/Storage.cpp index 1a5df493a0f..3d1f5a9d210 100644 --- a/Libraries/LibWeb/HTML/Storage.cpp +++ b/Libraries/LibWeb/HTML/Storage.cpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2022, Andreas Kling * Copyright (c) 2023, Luke Wilde - * Copyright (c) 2024, Shannon Booth + * Copyright (c) 2024-2025, Shannon Booth * * SPDX-License-Identifier: BSD-2-Clause */ @@ -25,15 +25,15 @@ static HashTable>& all_storages() return storages; } -GC::Ref Storage::create(JS::Realm& realm, Type type, u64 quota_bytes) +GC::Ref Storage::create(JS::Realm& realm, Type type, NonnullRefPtr storage_bottle) { - return realm.create(realm, type, quota_bytes); + return realm.create(realm, type, move(storage_bottle)); } -Storage::Storage(JS::Realm& realm, Type type, u64 quota_bytes) +Storage::Storage(JS::Realm& realm, Type type, NonnullRefPtr storage_bottle) : Bindings::PlatformObject(realm) , m_type(type) - , m_quota_bytes(quota_bytes) + , m_storage_bottle(move(storage_bottle)) { m_legacy_platform_object_flags = LegacyPlatformObjectFlags { .supports_indexed_properties = true, @@ -66,18 +66,18 @@ void Storage::finalize() size_t Storage::length() const { // The length getter steps are to return this's map's size. - return m_map.size(); + return map().size(); } // https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-key Optional Storage::key(size_t index) { // 1. If index is greater than or equal to this's map's size, then return null. - if (index >= m_map.size()) + if (index >= map().size()) return {}; // 2. Let keys be the result of running get the keys on this's map. - auto keys = m_map.keys(); + auto keys = map().keys(); // 3. Return keys[index]. return keys[index]; @@ -87,8 +87,8 @@ Optional Storage::key(size_t index) Optional Storage::get_item(StringView key) const { // 1. If this's map[key] does not exist, then return null. - auto it = m_map.find(key); - if (it == m_map.end()) + auto it = map().find(key); + if (it == map().end()) return {}; // 2. Return this's map[key]. @@ -108,7 +108,7 @@ WebIDL::ExceptionOr Storage::set_item(String const& key, String const& val // 3. If this's map[key] exists: auto new_size = m_stored_bytes; - if (auto it = m_map.find(key); it != m_map.end()) { + if (auto it = map().find(key); it != map().end()) { // 1. Set oldValue to this's map[key]. old_value = it->value; @@ -124,11 +124,11 @@ WebIDL::ExceptionOr Storage::set_item(String const& key, String const& val // 4. If value cannot be stored, then throw a "QuotaExceededError" DOMException exception. new_size += value.bytes().size() - old_value.value_or(String {}).bytes().size(); - if (new_size > m_quota_bytes) - return WebIDL::QuotaExceededError::create(realm, MUST(String::formatted("Unable to store more than {} bytes in storage"sv, m_quota_bytes))); + if (m_storage_bottle->quota.has_value() && new_size > *m_storage_bottle->quota) + return WebIDL::QuotaExceededError::create(realm, MUST(String::formatted("Unable to store more than {} bytes in storage"sv, *m_storage_bottle->quota))); // 5. Set this's map[key] to value. - m_map.set(key, value); + map().set(key, value); m_stored_bytes = new_size; // 6. If reorder is true, then reorder this. @@ -146,15 +146,15 @@ void Storage::remove_item(String const& key) { // 1. If this's map[key] does not exist, then return null. // FIXME: Return null? - auto it = m_map.find(key); - if (it == m_map.end()) + auto it = map().find(key); + if (it == map().end()) return; // 2. Set oldValue to this's map[key]. auto old_value = it->value; // 3. Remove this's map[key]. - m_map.remove(it); + map().remove(it); m_stored_bytes = m_stored_bytes - key.bytes().size() - old_value.bytes().size(); // 4. Reorder this. @@ -168,7 +168,7 @@ void Storage::remove_item(String const& key) void Storage::clear() { // 1. Clear this's map. - m_map.clear(); + map().clear(); // 2. Broadcast this with null, null, and null. broadcast({}, {}, {}); @@ -245,8 +245,8 @@ Vector Storage::supported_property_names() const { // The supported property names on a Storage object storage are the result of running get the keys on storage's map. Vector names; - names.ensure_capacity(m_map.size()); - for (auto const& key : m_map.keys()) + names.ensure_capacity(map().size()); + for (auto const& key : map().keys()) names.unchecked_append(key); return names; } @@ -294,9 +294,9 @@ WebIDL::ExceptionOr Storage::set_value_of_named_property(String const& key void Storage::dump() const { - dbgln("Storage ({} key(s))", m_map.size()); + dbgln("Storage ({} key(s))", map().size()); size_t i = 0; - for (auto const& it : m_map) { + for (auto const& it : map()) { dbgln("[{}] \"{}\": \"{}\"", i, it.key, it.value); ++i; } diff --git a/Libraries/LibWeb/HTML/Storage.h b/Libraries/LibWeb/HTML/Storage.h index 27c339878f3..84d09695f7c 100644 --- a/Libraries/LibWeb/HTML/Storage.h +++ b/Libraries/LibWeb/HTML/Storage.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2022, Andreas Kling * Copyright (c) 2023, Luke Wilde + * Copyright (c) 2024-2025, Shannon Booth * * SPDX-License-Identifier: BSD-2-Clause */ @@ -9,6 +10,7 @@ #include #include +#include #include namespace Web::HTML { @@ -25,7 +27,7 @@ public: Session, }; - [[nodiscard]] static GC::Ref create(JS::Realm&, Type, u64 quota_bytes); + [[nodiscard]] static GC::Ref create(JS::Realm&, Type, NonnullRefPtr); ~Storage(); @@ -35,14 +37,14 @@ public: WebIDL::ExceptionOr set_item(String const& key, String const& value); void remove_item(String const& key); void clear(); - - auto const& map() const { return m_map; } + auto const& map() const { return m_storage_bottle->map; } + auto& map() { return m_storage_bottle->map; } Type type() const { return m_type; } void dump() const; private: - Storage(JS::Realm&, Type, u64 quota_limit); + Storage(JS::Realm&, Type, NonnullRefPtr); virtual void initialize(JS::Realm&) override; virtual void finalize() override; @@ -58,9 +60,8 @@ private: void reorder(); void broadcast(Optional const& key, Optional const& old_value, Optional const& new_value); - OrderedHashMap m_map; Type m_type {}; - u64 m_quota_bytes { 0 }; + NonnullRefPtr m_storage_bottle; u64 m_stored_bytes { 0 }; }; diff --git a/Libraries/LibWeb/HTML/TraversableNavigable.h b/Libraries/LibWeb/HTML/TraversableNavigable.h index bb00de11a7a..2c0ea774e42 100644 --- a/Libraries/LibWeb/HTML/TraversableNavigable.h +++ b/Libraries/LibWeb/HTML/TraversableNavigable.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #ifdef AK_OS_MACOS @@ -107,6 +108,9 @@ public: RefPtr skia_backend_context() const { return m_skia_backend_context; } + StorageAPI::StorageShed& storage_shed() { return m_storage_shed; } + StorageAPI::StorageShed const& storage_shed() const { return m_storage_shed; } + private: TraversableNavigable(GC::Ref); @@ -142,6 +146,10 @@ private: // https://html.spec.whatwg.org/multipage/document-sequences.html#system-visibility-state VisibilityState m_system_visibility_state { VisibilityState::Hidden }; + // https://storage.spec.whatwg.org/#traversable-navigable-storage-shed + // A traversable navigable holds a storage shed, which is a storage shed. A traversable navigable’s storage shed holds all session storage data. + StorageAPI::StorageShed m_storage_shed; + GC::Ref m_session_history_traversal_queue; String m_window_handle; diff --git a/Libraries/LibWeb/HTML/Window.cpp b/Libraries/LibWeb/HTML/Window.cpp index 470f52fe791..c5013fe60fc 100644 --- a/Libraries/LibWeb/HTML/Window.cpp +++ b/Libraries/LibWeb/HTML/Window.cpp @@ -67,6 +67,7 @@ #include #include #include +#include #include namespace Web::HTML { @@ -447,29 +448,55 @@ void Window::fire_a_page_transition_event(FlyString const& event_name, bool pers // https://html.spec.whatwg.org/multipage/webstorage.html#dom-localstorage WebIDL::ExceptionOr> Window::local_storage() { - // See table in: https://storage.spec.whatwg.org/#registered-storage-endpoints - constexpr u64 quota_bytes = 5 * MiB; + auto& realm = this->realm(); - // FIXME: Implement according to spec. - static HashMap> local_storage_per_origin; - auto storage = local_storage_per_origin.ensure(associated_document().origin(), [this]() -> GC::Root { - return Storage::create(realm(), Storage::Type::Local, quota_bytes); - }); - return GC::Ref { *storage }; + // 1. If this's associated Document's local storage holder is non-null, then return this's associated Document's local storage holder. + auto& associated_document = this->associated_document(); + if (auto storage = associated_document.local_storage_holder()) + return GC::Ref { *storage }; + + // 2. Let map be the result of running obtain a local storage bottle map with this's relevant settings object and "localStorage". + auto map = StorageAPI::obtain_a_local_storage_bottle_map(relevant_settings_object(*this), "localStorage"sv); + + // 3. If map is failure, then throw a "SecurityError" DOMException. + if (!map) + return WebIDL::SecurityError::create(realm, "localStorage is not available"_string); + + // 4. Let storage be a new Storage object whose map is map. + auto storage = Storage::create(realm, Storage::Type::Session, map.release_nonnull()); + + // 5. Set this's associated Document's local storage holder to storage. + associated_document.set_local_storage_holder(storage); + + // 6. Return storage. + return storage; } // https://html.spec.whatwg.org/multipage/webstorage.html#dom-sessionstorage WebIDL::ExceptionOr> Window::session_storage() { - // See table in: https://storage.spec.whatwg.org/#registered-storage-endpoints - constexpr u64 quota_bytes = 5 * MiB; + auto& realm = this->realm(); - // FIXME: Implement according to spec. - static HashMap> session_storage_per_origin; - auto storage = session_storage_per_origin.ensure(associated_document().origin(), [this]() -> GC::Root { - return Storage::create(realm(), Storage::Type::Session, quota_bytes); - }); - return GC::Ref { *storage }; + // 1. If this's associated Document's session storage holder is non-null, then return this's associated Document's session storage holder. + auto& associated_document = this->associated_document(); + if (auto storage = associated_document.session_storage_holder()) + return GC::Ref { *storage }; + + // 2. Let map be the result of running obtain a session storage bottle map with this's relevant settings object and "sessionStorage". + auto map = StorageAPI::obtain_a_session_storage_bottle_map(relevant_settings_object(*this), "sessionStorage"sv); + + // 3. If map is failure, then throw a "SecurityError" DOMException. + if (!map) + return WebIDL::SecurityError::create(realm, "sessionStorage is not available"_string); + + // 4. Let storage be a new Storage object whose map is map. + auto storage = Storage::create(realm, Storage::Type::Session, map.release_nonnull()); + + // 5. Set this's associated Document's session storage holder to storage. + associated_document.set_session_storage_holder(storage); + + // 6. Return storage. + return storage; } // https://html.spec.whatwg.org/multipage/interaction.html#sticky-activation diff --git a/Libraries/LibWeb/StorageAPI/StorageBottle.cpp b/Libraries/LibWeb/StorageAPI/StorageBottle.cpp new file mode 100644 index 00000000000..5abdd0b3239 --- /dev/null +++ b/Libraries/LibWeb/StorageAPI/StorageBottle.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024-2025, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +namespace Web::StorageAPI { + +StorageBucket::StorageBucket(StorageType type) +{ + // 1. Let bucket be null. + // 2. If type is "local", then set bucket to a new local storage bucket. + // 3. Otherwise: + // 1. Assert: type is "session". + // 2. Set bucket to a new session storage bucket. + + // 4. For each endpoint of registered storage endpoints whose types contain type, set bucket’s bottle map[endpoint’s identifier] to a new storage bottle whose quota is endpoint’s quota. + for (auto const& endpoint : StorageEndpoint::registered_endpoints()) { + if (endpoint.type == type) + bottle_map.set(endpoint.identifier, StorageBottle::create(endpoint.quota)); + } + + // 5. Return bucket. +} + +// https://storage.spec.whatwg.org/#obtain-a-storage-bottle-map +RefPtr obtain_a_storage_bottle_map(StorageType type, HTML::EnvironmentSettingsObject& environment, StringView identifier) +{ + // 1. Let shed be null. + StorageShed* shed = nullptr; + + // 2. If type is "local", then set shed to the user agent’s storage shed. + if (type == StorageType::Local) { + shed = &user_agent_storage_shed(); + } + // 3. Otherwise: + else { + // 1. Assert: type is "session". + VERIFY(type == StorageType::Session); + + // 2. Set shed to environment’s global object’s associated Document’s node navigable’s traversable navigable’s storage shed. + shed = &verify_cast(environment.global_object()).associated_document().navigable()->traversable_navigable()->storage_shed(); + } + + // 4. Let shelf be the result of running obtain a storage shelf, with shed, environment, and type. + VERIFY(shed); + auto shelf = shed->obtain_a_storage_shelf(environment, type); + + // 5. If shelf is failure, then return failure. + if (!shelf.has_value()) + return {}; + + // 6. Let bucket be shelf’s bucket map["default"]. + auto& bucket = shelf->bucket_map.get("default"sv).value(); + + // 7. Let bottle be bucket’s bottle map[identifier]. + auto bottle = bucket.bottle_map.get(identifier).value(); + + // 8. Let proxyMap be a new storage proxy map whose backing map is bottle’s map. + // 9. Append proxyMap to bottle’s proxy map reference set. + // 10. Return proxyMap. + return bottle->proxy(); +} + +// https://storage.spec.whatwg.org/#obtain-a-session-storage-bottle-map +RefPtr obtain_a_session_storage_bottle_map(HTML::EnvironmentSettingsObject& environment, StringView identifier) +{ + // To obtain a session storage bottle map, given an environment settings object environment and storage identifier identifier, + // return the result of running obtain a storage bottle map with "session", environment, and identifier. + return obtain_a_storage_bottle_map(StorageType::Session, environment, identifier); +} + +// https://storage.spec.whatwg.org/#obtain-a-local-storage-bottle-map +RefPtr obtain_a_local_storage_bottle_map(HTML::EnvironmentSettingsObject& environment, StringView identifier) +{ + // To obtain a local storage bottle map, given an environment settings object environment and storage identifier identifier, + // return the result of running obtain a storage bottle map with "local", environment, and identifier. + return obtain_a_storage_bottle_map(StorageType::Local, environment, identifier); +} + +} diff --git a/Libraries/LibWeb/StorageAPI/StorageBottle.h b/Libraries/LibWeb/StorageAPI/StorageBottle.h new file mode 100644 index 00000000000..f1b3f65532d --- /dev/null +++ b/Libraries/LibWeb/StorageAPI/StorageBottle.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024-2025, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Web::StorageAPI { + +// https://storage.spec.whatwg.org/#storage-bottle +struct StorageBottle : public RefCounted { + static NonnullRefPtr create(Optional quota) { return adopt_ref(*new StorageBottle(quota)); } + + // A storage bottle has a map, which is initially an empty map + OrderedHashMap map; + + // A storage bottle also has a proxy map reference set, which is initially an empty set + NonnullRefPtr proxy() { return *this; } + + // A storage bottle also has a quota, which is null or a number representing a conservative estimate of + // the total amount of bytes it can hold. Null indicates the lack of a limit. + Optional quota; + +private: + explicit StorageBottle(Optional quota_) + : quota(quota_) + { + } +}; + +using BottleMap = OrderedHashMap>; + +// https://storage.spec.whatwg.org/#storage-bucket +// A storage bucket is a place for storage endpoints to store data. +struct StorageBucket { + explicit StorageBucket(StorageType); + + // A storage bucket has a bottle map of storage identifiers to storage bottles. + BottleMap bottle_map; +}; + +RefPtr obtain_a_session_storage_bottle_map(HTML::EnvironmentSettingsObject&, StringView storage_identifier); +RefPtr obtain_a_local_storage_bottle_map(HTML::EnvironmentSettingsObject&, StringView storage_identifier); +RefPtr obtain_a_storage_bottle_map(StorageType, HTML::EnvironmentSettingsObject&, StringView storage_identifier); + +} diff --git a/Libraries/LibWeb/StorageAPI/StorageEndpoint.cpp b/Libraries/LibWeb/StorageAPI/StorageEndpoint.cpp new file mode 100644 index 00000000000..0d1c5184659 --- /dev/null +++ b/Libraries/LibWeb/StorageAPI/StorageEndpoint.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024-2025, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace Web::StorageAPI { + +ReadonlySpan StorageEndpoint::registered_endpoints() +{ + // https://storage.spec.whatwg.org/#registered-storage-endpoints + static auto const endpoints = to_array({ + { "caches"_string, StorageType::Local, {} }, + { "indexedDB"_string, StorageType::Local, {} }, + { "localStorage"_string, StorageType::Local, 5 * MiB }, + { "serviceWorkerRegistrations"_string, StorageType::Local, {} }, + { "sessionStorage"_string, StorageType::Session, 5 * MiB }, + }); + return endpoints; +} + +} diff --git a/Libraries/LibWeb/StorageAPI/StorageEndpoint.h b/Libraries/LibWeb/StorageAPI/StorageEndpoint.h new file mode 100644 index 00000000000..450f0c32e2e --- /dev/null +++ b/Libraries/LibWeb/StorageAPI/StorageEndpoint.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024-2025, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Web::StorageAPI { + +// https://storage.spec.whatwg.org/#storage-endpoint +// +// A storage endpoint is a local or session storage API that uses the infrastructure defined by this +// standard, most notably storage bottles, to keep track of its storage needs. +struct StorageEndpoint { + // https://storage.spec.whatwg.org/#storage-endpoint-identifier + // A storage endpoint has an identifier, which is a storage identifier. + String identifier; + + // https://storage.spec.whatwg.org/#storage-endpoint-types + // A storage endpoint also has types, which is a set of storage types. + // NOTE: We do not implement this as a set as it is not neccessary in the current implementation. + StorageType type; + + // https://storage.spec.whatwg.org/#storage-endpoint-quota + // A storage endpoint also has a quota, which is null or a number representing a recommended quota (in bytes) for each storage bottle corresponding to this storage endpoint. + Optional quota; + + static ReadonlySpan registered_endpoints(); +}; + +} diff --git a/Libraries/LibWeb/StorageAPI/StorageShed.cpp b/Libraries/LibWeb/StorageAPI/StorageShed.cpp new file mode 100644 index 00000000000..cdce960aeb3 --- /dev/null +++ b/Libraries/LibWeb/StorageAPI/StorageShed.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024-2025, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Web::StorageAPI { + +// https://storage.spec.whatwg.org/#obtain-a-storage-shelf +Optional StorageShed::obtain_a_storage_shelf(HTML::EnvironmentSettingsObject const& environment, StorageType type) +{ + // 1. Let key be the result of running obtain a storage key with environment. + auto key = obtain_a_storage_key(environment); + + // 2. If key is failure, then return failure. + if (!key.has_value()) + return {}; + + // 3. If shed[key] does not exist, then set shed[key] to the result of running create a storage shelf with type. + // 4. Return shed[key]. + return m_data.ensure(key.value(), [type] { + return StorageShelf { type }; + }); +} + +// https://storage.spec.whatwg.org/#user-agent-storage-shed +StorageShed& user_agent_storage_shed() +{ + // A user agent holds a storage shed, which is a storage shed. A user agent’s storage shed holds all local storage data. + // FIXME: Storing this statically in memory is not the correct place or way of doing this! + static StorageShed storage_shed; + return storage_shed; +} + +} diff --git a/Libraries/LibWeb/StorageAPI/StorageShed.h b/Libraries/LibWeb/StorageAPI/StorageShed.h new file mode 100644 index 00000000000..806199367e7 --- /dev/null +++ b/Libraries/LibWeb/StorageAPI/StorageShed.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024-2025, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Web::StorageAPI { + +// https://storage.spec.whatwg.org/#storage-shed +// A storage shed is a map of storage keys to storage shelves. It is initially empty. +class StorageShed { +public: + Optional obtain_a_storage_shelf(HTML::EnvironmentSettingsObject const&, StorageType); + +private: + OrderedHashMap m_data; +}; + +StorageShed& user_agent_storage_shed(); + +} diff --git a/Libraries/LibWeb/StorageAPI/StorageShelf.cpp b/Libraries/LibWeb/StorageAPI/StorageShelf.cpp new file mode 100644 index 00000000000..09f51c1eaec --- /dev/null +++ b/Libraries/LibWeb/StorageAPI/StorageShelf.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024-2025, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace Web::StorageAPI { + +// https://storage.spec.whatwg.org/#create-a-storage-shelf +StorageShelf::StorageShelf(StorageType type) +{ + // 1. Let shelf be a new storage shelf. + // 2. Set shelf’s bucket map["default"] to the result of running create a storage bucket with type. + bucket_map.set("default"_string, StorageBucket { type }); + // 3. Return shelf. +} + +} diff --git a/Libraries/LibWeb/StorageAPI/StorageShelf.h b/Libraries/LibWeb/StorageAPI/StorageShelf.h new file mode 100644 index 00000000000..67004d04be7 --- /dev/null +++ b/Libraries/LibWeb/StorageAPI/StorageShelf.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024-2025, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Web::StorageAPI { + +// https://storage.spec.whatwg.org/#storage-shelf +// A storage shelf exists for each storage key within a storage shed. It holds a bucket map, which is a map of strings to storage buckets. +using BucketMap = OrderedHashMap; + +struct StorageShelf { + explicit StorageShelf(StorageType); + + BucketMap bucket_map; +}; + +} diff --git a/Libraries/LibWeb/StorageAPI/StorageType.h b/Libraries/LibWeb/StorageAPI/StorageType.h new file mode 100644 index 00000000000..d7bc3fc1c20 --- /dev/null +++ b/Libraries/LibWeb/StorageAPI/StorageType.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2024-2025, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +namespace Web::StorageAPI { + +// https://storage.spec.whatwg.org/#storage-type +// A storage type is "local" or "session". +enum class StorageType { + Local, + Session, +}; + +} diff --git a/Tests/LibWeb/Text/expected/wpt-import/webstorage/event_session_storagearea.txt b/Tests/LibWeb/Text/expected/wpt-import/webstorage/event_session_storagearea.txt new file mode 100644 index 00000000000..fea4db70e0c --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/webstorage/event_session_storagearea.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass storageArea property test of session event - session event is fired due to an invocation of the setItem() method. \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/webstorage/event_session_storagearea.html b/Tests/LibWeb/Text/input/wpt-import/webstorage/event_session_storagearea.html new file mode 100644 index 00000000000..06b4dbbecc5 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/webstorage/event_session_storagearea.html @@ -0,0 +1,40 @@ + + + + WebStorage Test: sessionStorage event - storageArea + + + + +

event_session_storageArea

+
+ + + + diff --git a/Tests/LibWeb/Text/input/wpt-import/webstorage/resources/session_set_item_iframe.html b/Tests/LibWeb/Text/input/wpt-import/webstorage/resources/session_set_item_iframe.html new file mode 100644 index 00000000000..de844cca453 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/webstorage/resources/session_set_item_iframe.html @@ -0,0 +1,16 @@ + + + + + +