mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-29 12:19:54 +00:00
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.
This commit is contained in:
parent
c536f65160
commit
2066ed2318
Notes:
github-actions[bot]
2025-01-02 10:39:14 +00:00
Author: https://github.com/shannonbooth
Commit: 2066ed2318
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3070
Reviewed-by: https://github.com/awesomekling
Reviewed-by: https://github.com/konradekk
20 changed files with 503 additions and 44 deletions
|
@ -690,8 +690,12 @@ set(SOURCES
|
||||||
ServiceWorker/ServiceWorkerRegistration.cpp
|
ServiceWorker/ServiceWorkerRegistration.cpp
|
||||||
SRI/SRI.cpp
|
SRI/SRI.cpp
|
||||||
StorageAPI/NavigatorStorage.cpp
|
StorageAPI/NavigatorStorage.cpp
|
||||||
|
StorageAPI/StorageBottle.cpp
|
||||||
|
StorageAPI/StorageEndpoint.cpp
|
||||||
StorageAPI/StorageKey.cpp
|
StorageAPI/StorageKey.cpp
|
||||||
StorageAPI/StorageManager.cpp
|
StorageAPI/StorageManager.cpp
|
||||||
|
StorageAPI/StorageShed.cpp
|
||||||
|
StorageAPI/StorageShelf.cpp
|
||||||
Streams/AbstractOperations.cpp
|
Streams/AbstractOperations.cpp
|
||||||
Streams/ByteLengthQueuingStrategy.cpp
|
Streams/ByteLengthQueuingStrategy.cpp
|
||||||
Streams/CountQueuingStrategy.cpp
|
Streams/CountQueuingStrategy.cpp
|
||||||
|
|
|
@ -116,6 +116,7 @@
|
||||||
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
|
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
|
||||||
#include <LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.h>
|
#include <LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.h>
|
||||||
#include <LibWeb/HTML/SharedResourceRequest.h>
|
#include <LibWeb/HTML/SharedResourceRequest.h>
|
||||||
|
#include <LibWeb/HTML/Storage.h>
|
||||||
#include <LibWeb/HTML/TraversableNavigable.h>
|
#include <LibWeb/HTML/TraversableNavigable.h>
|
||||||
#include <LibWeb/HTML/Window.h>
|
#include <LibWeb/HTML/Window.h>
|
||||||
#include <LibWeb/HTML/WindowProxy.h>
|
#include <LibWeb/HTML/WindowProxy.h>
|
||||||
|
@ -537,6 +538,8 @@ void Document::visit_edges(Cell::Visitor& visitor)
|
||||||
visitor.visit(m_top_layer_pending_removals);
|
visitor.visit(m_top_layer_pending_removals);
|
||||||
visitor.visit(m_console_client);
|
visitor.visit(m_console_client);
|
||||||
visitor.visit(m_editing_host_manager);
|
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
|
// https://w3c.github.io/selection-api/#dom-document-getselection
|
||||||
|
|
|
@ -760,6 +760,12 @@ public:
|
||||||
|
|
||||||
GC::Ptr<DOM::Document> container_document() const;
|
GC::Ptr<DOM::Document> container_document() const;
|
||||||
|
|
||||||
|
GC::Ptr<HTML::Storage> session_storage_holder() { return m_session_storage_holder; }
|
||||||
|
void set_session_storage_holder(GC::Ptr<HTML::Storage> storage) { m_session_storage_holder = storage; }
|
||||||
|
|
||||||
|
GC::Ptr<HTML::Storage> local_storage_holder() { return m_local_storage_holder; }
|
||||||
|
void set_local_storage_holder(GC::Ptr<HTML::Storage> storage) { m_local_storage_holder = storage; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void initialize(JS::Realm&) override;
|
virtual void initialize(JS::Realm&) override;
|
||||||
virtual void visit_edges(Cell::Visitor&) override;
|
virtual void visit_edges(Cell::Visitor&) override;
|
||||||
|
@ -1073,6 +1079,14 @@ private:
|
||||||
|
|
||||||
// https://w3c.github.io/editing/docs/execCommand/#css-styling-flag
|
// https://w3c.github.io/editing/docs/execCommand/#css-styling-flag
|
||||||
bool m_css_styling_flag { false };
|
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<HTML::Storage> 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<HTML::Storage> m_local_storage_holder;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
|
|
@ -752,6 +752,12 @@ struct UnderlyingSource;
|
||||||
namespace Web::StorageAPI {
|
namespace Web::StorageAPI {
|
||||||
class NavigatorStorage;
|
class NavigatorStorage;
|
||||||
class StorageManager;
|
class StorageManager;
|
||||||
|
class StorageShed;
|
||||||
|
|
||||||
|
struct StorageBottle;
|
||||||
|
struct StorageBucket;
|
||||||
|
struct StorageEndpoint;
|
||||||
|
struct StorageShelf;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Web::SVG {
|
namespace Web::SVG {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
|
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
|
||||||
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||||
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
|
* Copyright (c) 2024-2025, Shannon Booth <shannon@serenityos.org>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -25,15 +25,15 @@ static HashTable<GC::RawRef<Storage>>& all_storages()
|
||||||
return storages;
|
return storages;
|
||||||
}
|
}
|
||||||
|
|
||||||
GC::Ref<Storage> Storage::create(JS::Realm& realm, Type type, u64 quota_bytes)
|
GC::Ref<Storage> Storage::create(JS::Realm& realm, Type type, NonnullRefPtr<StorageAPI::StorageBottle> storage_bottle)
|
||||||
{
|
{
|
||||||
return realm.create<Storage>(realm, type, quota_bytes);
|
return realm.create<Storage>(realm, type, move(storage_bottle));
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage::Storage(JS::Realm& realm, Type type, u64 quota_bytes)
|
Storage::Storage(JS::Realm& realm, Type type, NonnullRefPtr<StorageAPI::StorageBottle> storage_bottle)
|
||||||
: Bindings::PlatformObject(realm)
|
: Bindings::PlatformObject(realm)
|
||||||
, m_type(type)
|
, m_type(type)
|
||||||
, m_quota_bytes(quota_bytes)
|
, m_storage_bottle(move(storage_bottle))
|
||||||
{
|
{
|
||||||
m_legacy_platform_object_flags = LegacyPlatformObjectFlags {
|
m_legacy_platform_object_flags = LegacyPlatformObjectFlags {
|
||||||
.supports_indexed_properties = true,
|
.supports_indexed_properties = true,
|
||||||
|
@ -66,18 +66,18 @@ void Storage::finalize()
|
||||||
size_t Storage::length() const
|
size_t Storage::length() const
|
||||||
{
|
{
|
||||||
// The length getter steps are to return this's map's size.
|
// 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
|
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-key
|
||||||
Optional<String> Storage::key(size_t index)
|
Optional<String> Storage::key(size_t index)
|
||||||
{
|
{
|
||||||
// 1. If index is greater than or equal to this's map's size, then return null.
|
// 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 {};
|
return {};
|
||||||
|
|
||||||
// 2. Let keys be the result of running get the keys on this's map.
|
// 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].
|
// 3. Return keys[index].
|
||||||
return keys[index];
|
return keys[index];
|
||||||
|
@ -87,8 +87,8 @@ Optional<String> Storage::key(size_t index)
|
||||||
Optional<String> Storage::get_item(StringView key) const
|
Optional<String> Storage::get_item(StringView key) const
|
||||||
{
|
{
|
||||||
// 1. If this's map[key] does not exist, then return null.
|
// 1. If this's map[key] does not exist, then return null.
|
||||||
auto it = m_map.find(key);
|
auto it = map().find(key);
|
||||||
if (it == m_map.end())
|
if (it == map().end())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
// 2. Return this's map[key].
|
// 2. Return this's map[key].
|
||||||
|
@ -108,7 +108,7 @@ WebIDL::ExceptionOr<void> Storage::set_item(String const& key, String const& val
|
||||||
|
|
||||||
// 3. If this's map[key] exists:
|
// 3. If this's map[key] exists:
|
||||||
auto new_size = m_stored_bytes;
|
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].
|
// 1. Set oldValue to this's map[key].
|
||||||
old_value = it->value;
|
old_value = it->value;
|
||||||
|
|
||||||
|
@ -124,11 +124,11 @@ WebIDL::ExceptionOr<void> Storage::set_item(String const& key, String const& val
|
||||||
|
|
||||||
// 4. If value cannot be stored, then throw a "QuotaExceededError" DOMException exception.
|
// 4. If value cannot be stored, then throw a "QuotaExceededError" DOMException exception.
|
||||||
new_size += value.bytes().size() - old_value.value_or(String {}).bytes().size();
|
new_size += value.bytes().size() - old_value.value_or(String {}).bytes().size();
|
||||||
if (new_size > 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_quota_bytes)));
|
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.
|
// 5. Set this's map[key] to value.
|
||||||
m_map.set(key, value);
|
map().set(key, value);
|
||||||
m_stored_bytes = new_size;
|
m_stored_bytes = new_size;
|
||||||
|
|
||||||
// 6. If reorder is true, then reorder this.
|
// 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.
|
// 1. If this's map[key] does not exist, then return null.
|
||||||
// FIXME: Return null?
|
// FIXME: Return null?
|
||||||
auto it = m_map.find(key);
|
auto it = map().find(key);
|
||||||
if (it == m_map.end())
|
if (it == map().end())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// 2. Set oldValue to this's map[key].
|
// 2. Set oldValue to this's map[key].
|
||||||
auto old_value = it->value;
|
auto old_value = it->value;
|
||||||
|
|
||||||
// 3. Remove this's map[key].
|
// 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();
|
m_stored_bytes = m_stored_bytes - key.bytes().size() - old_value.bytes().size();
|
||||||
|
|
||||||
// 4. Reorder this.
|
// 4. Reorder this.
|
||||||
|
@ -168,7 +168,7 @@ void Storage::remove_item(String const& key)
|
||||||
void Storage::clear()
|
void Storage::clear()
|
||||||
{
|
{
|
||||||
// 1. Clear this's map.
|
// 1. Clear this's map.
|
||||||
m_map.clear();
|
map().clear();
|
||||||
|
|
||||||
// 2. Broadcast this with null, null, and null.
|
// 2. Broadcast this with null, null, and null.
|
||||||
broadcast({}, {}, {});
|
broadcast({}, {}, {});
|
||||||
|
@ -245,8 +245,8 @@ Vector<FlyString> 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.
|
// The supported property names on a Storage object storage are the result of running get the keys on storage's map.
|
||||||
Vector<FlyString> names;
|
Vector<FlyString> names;
|
||||||
names.ensure_capacity(m_map.size());
|
names.ensure_capacity(map().size());
|
||||||
for (auto const& key : m_map.keys())
|
for (auto const& key : map().keys())
|
||||||
names.unchecked_append(key);
|
names.unchecked_append(key);
|
||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
@ -294,9 +294,9 @@ WebIDL::ExceptionOr<void> Storage::set_value_of_named_property(String const& key
|
||||||
|
|
||||||
void Storage::dump() const
|
void Storage::dump() const
|
||||||
{
|
{
|
||||||
dbgln("Storage ({} key(s))", m_map.size());
|
dbgln("Storage ({} key(s))", map().size());
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
for (auto const& it : m_map) {
|
for (auto const& it : map()) {
|
||||||
dbgln("[{}] \"{}\": \"{}\"", i, it.key, it.value);
|
dbgln("[{}] \"{}\": \"{}\"", i, it.key, it.value);
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
|
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
|
||||||
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||||
|
* Copyright (c) 2024-2025, Shannon Booth <shannon@serenityos.org>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -9,6 +10,7 @@
|
||||||
|
|
||||||
#include <AK/HashMap.h>
|
#include <AK/HashMap.h>
|
||||||
#include <LibWeb/Bindings/PlatformObject.h>
|
#include <LibWeb/Bindings/PlatformObject.h>
|
||||||
|
#include <LibWeb/StorageAPI/StorageBottle.h>
|
||||||
#include <LibWeb/WebIDL/ExceptionOr.h>
|
#include <LibWeb/WebIDL/ExceptionOr.h>
|
||||||
|
|
||||||
namespace Web::HTML {
|
namespace Web::HTML {
|
||||||
|
@ -25,7 +27,7 @@ public:
|
||||||
Session,
|
Session,
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] static GC::Ref<Storage> create(JS::Realm&, Type, u64 quota_bytes);
|
[[nodiscard]] static GC::Ref<Storage> create(JS::Realm&, Type, NonnullRefPtr<StorageAPI::StorageBottle>);
|
||||||
|
|
||||||
~Storage();
|
~Storage();
|
||||||
|
|
||||||
|
@ -35,14 +37,14 @@ public:
|
||||||
WebIDL::ExceptionOr<void> set_item(String const& key, String const& value);
|
WebIDL::ExceptionOr<void> set_item(String const& key, String const& value);
|
||||||
void remove_item(String const& key);
|
void remove_item(String const& key);
|
||||||
void clear();
|
void clear();
|
||||||
|
auto const& map() const { return m_storage_bottle->map; }
|
||||||
auto const& map() const { return m_map; }
|
auto& map() { return m_storage_bottle->map; }
|
||||||
Type type() const { return m_type; }
|
Type type() const { return m_type; }
|
||||||
|
|
||||||
void dump() const;
|
void dump() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Storage(JS::Realm&, Type, u64 quota_limit);
|
Storage(JS::Realm&, Type, NonnullRefPtr<StorageAPI::StorageBottle>);
|
||||||
|
|
||||||
virtual void initialize(JS::Realm&) override;
|
virtual void initialize(JS::Realm&) override;
|
||||||
virtual void finalize() override;
|
virtual void finalize() override;
|
||||||
|
@ -58,9 +60,8 @@ private:
|
||||||
void reorder();
|
void reorder();
|
||||||
void broadcast(Optional<String> const& key, Optional<String> const& old_value, Optional<String> const& new_value);
|
void broadcast(Optional<String> const& key, Optional<String> const& old_value, Optional<String> const& new_value);
|
||||||
|
|
||||||
OrderedHashMap<String, String> m_map;
|
|
||||||
Type m_type {};
|
Type m_type {};
|
||||||
u64 m_quota_bytes { 0 };
|
NonnullRefPtr<StorageAPI::StorageBottle> m_storage_bottle;
|
||||||
u64 m_stored_bytes { 0 };
|
u64 m_stored_bytes { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include <LibWeb/HTML/VisibilityState.h>
|
#include <LibWeb/HTML/VisibilityState.h>
|
||||||
#include <LibWeb/Page/Page.h>
|
#include <LibWeb/Page/Page.h>
|
||||||
#include <LibWeb/Painting/DisplayListPlayerSkia.h>
|
#include <LibWeb/Painting/DisplayListPlayerSkia.h>
|
||||||
|
#include <LibWeb/StorageAPI/StorageShed.h>
|
||||||
#include <WebContent/BackingStoreManager.h>
|
#include <WebContent/BackingStoreManager.h>
|
||||||
|
|
||||||
#ifdef AK_OS_MACOS
|
#ifdef AK_OS_MACOS
|
||||||
|
@ -107,6 +108,9 @@ public:
|
||||||
|
|
||||||
RefPtr<Gfx::SkiaBackendContext> skia_backend_context() const { return m_skia_backend_context; }
|
RefPtr<Gfx::SkiaBackendContext> 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:
|
private:
|
||||||
TraversableNavigable(GC::Ref<Page>);
|
TraversableNavigable(GC::Ref<Page>);
|
||||||
|
|
||||||
|
@ -142,6 +146,10 @@ private:
|
||||||
// https://html.spec.whatwg.org/multipage/document-sequences.html#system-visibility-state
|
// https://html.spec.whatwg.org/multipage/document-sequences.html#system-visibility-state
|
||||||
VisibilityState m_system_visibility_state { VisibilityState::Hidden };
|
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<SessionHistoryTraversalQueue> m_session_history_traversal_queue;
|
GC::Ref<SessionHistoryTraversalQueue> m_session_history_traversal_queue;
|
||||||
|
|
||||||
String m_window_handle;
|
String m_window_handle;
|
||||||
|
|
|
@ -67,6 +67,7 @@
|
||||||
#include <LibWeb/Painting/PaintableBox.h>
|
#include <LibWeb/Painting/PaintableBox.h>
|
||||||
#include <LibWeb/RequestIdleCallback/IdleDeadline.h>
|
#include <LibWeb/RequestIdleCallback/IdleDeadline.h>
|
||||||
#include <LibWeb/Selection/Selection.h>
|
#include <LibWeb/Selection/Selection.h>
|
||||||
|
#include <LibWeb/StorageAPI/StorageBottle.h>
|
||||||
#include <LibWeb/WebIDL/AbstractOperations.h>
|
#include <LibWeb/WebIDL/AbstractOperations.h>
|
||||||
|
|
||||||
namespace Web::HTML {
|
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
|
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-localstorage
|
||||||
WebIDL::ExceptionOr<GC::Ref<Storage>> Window::local_storage()
|
WebIDL::ExceptionOr<GC::Ref<Storage>> Window::local_storage()
|
||||||
{
|
{
|
||||||
// See table in: https://storage.spec.whatwg.org/#registered-storage-endpoints
|
auto& realm = this->realm();
|
||||||
constexpr u64 quota_bytes = 5 * MiB;
|
|
||||||
|
|
||||||
// FIXME: Implement according to spec.
|
// 1. If this's associated Document's local storage holder is non-null, then return this's associated Document's local storage holder.
|
||||||
static HashMap<URL::Origin, GC::Root<Storage>> local_storage_per_origin;
|
auto& associated_document = this->associated_document();
|
||||||
auto storage = local_storage_per_origin.ensure(associated_document().origin(), [this]() -> GC::Root<Storage> {
|
if (auto storage = associated_document.local_storage_holder())
|
||||||
return Storage::create(realm(), Storage::Type::Local, quota_bytes);
|
return GC::Ref { *storage };
|
||||||
});
|
|
||||||
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
|
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-sessionstorage
|
||||||
WebIDL::ExceptionOr<GC::Ref<Storage>> Window::session_storage()
|
WebIDL::ExceptionOr<GC::Ref<Storage>> Window::session_storage()
|
||||||
{
|
{
|
||||||
// See table in: https://storage.spec.whatwg.org/#registered-storage-endpoints
|
auto& realm = this->realm();
|
||||||
constexpr u64 quota_bytes = 5 * MiB;
|
|
||||||
|
|
||||||
// FIXME: Implement according to spec.
|
// 1. If this's associated Document's session storage holder is non-null, then return this's associated Document's session storage holder.
|
||||||
static HashMap<URL::Origin, GC::Root<Storage>> session_storage_per_origin;
|
auto& associated_document = this->associated_document();
|
||||||
auto storage = session_storage_per_origin.ensure(associated_document().origin(), [this]() -> GC::Root<Storage> {
|
if (auto storage = associated_document.session_storage_holder())
|
||||||
return Storage::create(realm(), Storage::Type::Session, quota_bytes);
|
return GC::Ref { *storage };
|
||||||
});
|
|
||||||
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
|
// https://html.spec.whatwg.org/multipage/interaction.html#sticky-activation
|
||||||
|
|
88
Libraries/LibWeb/StorageAPI/StorageBottle.cpp
Normal file
88
Libraries/LibWeb/StorageAPI/StorageBottle.cpp
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024-2025, Shannon Booth <shannon@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||||
|
#include <LibWeb/HTML/TraversableNavigable.h>
|
||||||
|
#include <LibWeb/HTML/Window.h>
|
||||||
|
#include <LibWeb/StorageAPI/StorageBottle.h>
|
||||||
|
#include <LibWeb/StorageAPI/StorageEndpoint.h>
|
||||||
|
#include <LibWeb/StorageAPI/StorageShed.h>
|
||||||
|
|
||||||
|
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<StorageBottle> 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<HTML::Window>(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<StorageBottle> 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<StorageBottle> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
52
Libraries/LibWeb/StorageAPI/StorageBottle.h
Normal file
52
Libraries/LibWeb/StorageAPI/StorageBottle.h
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024-2025, Shannon Booth <shannon@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/HashMap.h>
|
||||||
|
#include <AK/String.h>
|
||||||
|
#include <LibWeb/Forward.h>
|
||||||
|
#include <LibWeb/StorageAPI/StorageType.h>
|
||||||
|
|
||||||
|
namespace Web::StorageAPI {
|
||||||
|
|
||||||
|
// https://storage.spec.whatwg.org/#storage-bottle
|
||||||
|
struct StorageBottle : public RefCounted<StorageBottle> {
|
||||||
|
static NonnullRefPtr<StorageBottle> create(Optional<u64> quota) { return adopt_ref(*new StorageBottle(quota)); }
|
||||||
|
|
||||||
|
// A storage bottle has a map, which is initially an empty map
|
||||||
|
OrderedHashMap<String, String> map;
|
||||||
|
|
||||||
|
// A storage bottle also has a proxy map reference set, which is initially an empty set
|
||||||
|
NonnullRefPtr<StorageBottle> 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<u64> quota;
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit StorageBottle(Optional<u64> quota_)
|
||||||
|
: quota(quota_)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using BottleMap = OrderedHashMap<String, NonnullRefPtr<StorageBottle>>;
|
||||||
|
|
||||||
|
// 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<StorageBottle> obtain_a_session_storage_bottle_map(HTML::EnvironmentSettingsObject&, StringView storage_identifier);
|
||||||
|
RefPtr<StorageBottle> obtain_a_local_storage_bottle_map(HTML::EnvironmentSettingsObject&, StringView storage_identifier);
|
||||||
|
RefPtr<StorageBottle> obtain_a_storage_bottle_map(StorageType, HTML::EnvironmentSettingsObject&, StringView storage_identifier);
|
||||||
|
|
||||||
|
}
|
24
Libraries/LibWeb/StorageAPI/StorageEndpoint.cpp
Normal file
24
Libraries/LibWeb/StorageAPI/StorageEndpoint.cpp
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024-2025, Shannon Booth <shannon@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <LibWeb/StorageAPI/StorageEndpoint.h>
|
||||||
|
|
||||||
|
namespace Web::StorageAPI {
|
||||||
|
|
||||||
|
ReadonlySpan<StorageEndpoint> StorageEndpoint::registered_endpoints()
|
||||||
|
{
|
||||||
|
// https://storage.spec.whatwg.org/#registered-storage-endpoints
|
||||||
|
static auto const endpoints = to_array<StorageEndpoint>({
|
||||||
|
{ "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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
38
Libraries/LibWeb/StorageAPI/StorageEndpoint.h
Normal file
38
Libraries/LibWeb/StorageAPI/StorageEndpoint.h
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024-2025, Shannon Booth <shannon@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Array.h>
|
||||||
|
#include <AK/Optional.h>
|
||||||
|
#include <AK/String.h>
|
||||||
|
#include <AK/Types.h>
|
||||||
|
#include <LibWeb/StorageAPI/StorageType.h>
|
||||||
|
|
||||||
|
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<u64> quota;
|
||||||
|
|
||||||
|
static ReadonlySpan<StorageEndpoint> registered_endpoints();
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
38
Libraries/LibWeb/StorageAPI/StorageShed.cpp
Normal file
38
Libraries/LibWeb/StorageAPI/StorageShed.cpp
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024-2025, Shannon Booth <shannon@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||||
|
#include <LibWeb/StorageAPI/StorageShed.h>
|
||||||
|
|
||||||
|
namespace Web::StorageAPI {
|
||||||
|
|
||||||
|
// https://storage.spec.whatwg.org/#obtain-a-storage-shelf
|
||||||
|
Optional<StorageShelf&> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
29
Libraries/LibWeb/StorageAPI/StorageShed.h
Normal file
29
Libraries/LibWeb/StorageAPI/StorageShed.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024-2025, Shannon Booth <shannon@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/HashMap.h>
|
||||||
|
#include <LibWeb/Forward.h>
|
||||||
|
#include <LibWeb/StorageAPI/StorageKey.h>
|
||||||
|
#include <LibWeb/StorageAPI/StorageShelf.h>
|
||||||
|
#include <LibWeb/StorageAPI/StorageType.h>
|
||||||
|
|
||||||
|
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<StorageShelf&> obtain_a_storage_shelf(HTML::EnvironmentSettingsObject const&, StorageType);
|
||||||
|
|
||||||
|
private:
|
||||||
|
OrderedHashMap<StorageKey, StorageShelf> m_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
StorageShed& user_agent_storage_shed();
|
||||||
|
|
||||||
|
}
|
20
Libraries/LibWeb/StorageAPI/StorageShelf.cpp
Normal file
20
Libraries/LibWeb/StorageAPI/StorageShelf.cpp
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024-2025, Shannon Booth <shannon@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <LibWeb/StorageAPI/StorageShelf.h>
|
||||||
|
|
||||||
|
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.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
27
Libraries/LibWeb/StorageAPI/StorageShelf.h
Normal file
27
Libraries/LibWeb/StorageAPI/StorageShelf.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024-2025, Shannon Booth <shannon@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/HashMap.h>
|
||||||
|
#include <AK/String.h>
|
||||||
|
#include <LibWeb/Forward.h>
|
||||||
|
#include <LibWeb/StorageAPI/StorageBottle.h>
|
||||||
|
#include <LibWeb/StorageAPI/StorageType.h>
|
||||||
|
|
||||||
|
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<String, StorageBucket>;
|
||||||
|
|
||||||
|
struct StorageShelf {
|
||||||
|
explicit StorageShelf(StorageType);
|
||||||
|
|
||||||
|
BucketMap bucket_map;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
18
Libraries/LibWeb/StorageAPI/StorageType.h
Normal file
18
Libraries/LibWeb/StorageAPI/StorageType.h
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024-2025, Shannon Booth <shannon@serenityos.org>
|
||||||
|
*
|
||||||
|
* 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,
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -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.
|
|
@ -0,0 +1,40 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>WebStorage Test: sessionStorage event - storageArea</title>
|
||||||
|
<script src="../resources/testharness.js"></script>
|
||||||
|
<script src="../resources/testharnessreport.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>event_session_storageArea</h1>
|
||||||
|
<div id="log"></div>
|
||||||
|
<script>
|
||||||
|
async_test(function(t) {
|
||||||
|
sessionStorage.clear();
|
||||||
|
t.add_cleanup(function() { sessionStorage.clear() });
|
||||||
|
|
||||||
|
self.fail = t.step_func(function(msg) {
|
||||||
|
assert_unreached(msg);
|
||||||
|
t.done();
|
||||||
|
});
|
||||||
|
|
||||||
|
function onStorageEvent(event) {
|
||||||
|
assert_equals(event.storageArea.length, 1);
|
||||||
|
var key = event.storageArea.key(0);
|
||||||
|
var value = event.storageArea.getItem(key);
|
||||||
|
assert_equals(key, "name");
|
||||||
|
assert_equals(value, "user1");
|
||||||
|
t.done();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('storage', t.step_func(onStorageEvent), false);
|
||||||
|
|
||||||
|
var el = document.createElement("iframe");
|
||||||
|
el.setAttribute('id', 'ifrm');
|
||||||
|
el.setAttribute('src', 'resources/session_set_item_iframe.html');
|
||||||
|
document.body.appendChild(el);
|
||||||
|
}, "storageArea property test of session event - session event is fired due to an invocation of the setItem() method.");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
if (('sessionStorage' in window) && window.sessionStorage !== null){
|
||||||
|
try {
|
||||||
|
sessionStorage.setItem('name', 'user1');
|
||||||
|
} catch (e) {
|
||||||
|
parent.fail('setItem method is failed.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parent.fail('sessionStorage is not supported.');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Add table
Add a link
Reference in a new issue