From f3e92f2ae5ff696ee0ed25d37c4b505de5036f58 Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Mon, 23 Dec 2024 22:45:19 +1300 Subject: [PATCH] LibWeb/HTML: Implement the StorageEvent interface --- Libraries/LibWeb/CMakeLists.txt | 1 + Libraries/LibWeb/HTML/StorageEvent.cpp | 70 +++++++++++++++++ Libraries/LibWeb/HTML/StorageEvent.h | 60 +++++++++++++++ Libraries/LibWeb/HTML/StorageEvent.idl | 25 ++++++ Libraries/LibWeb/idl_files.cmake | 1 + .../BindingsGenerator/IDLGenerators.cpp | 1 + .../Text/expected/all-window-properties.txt | 1 + .../webstorage/event_constructor.window.txt | 11 +++ .../event_initstorageevent.window.txt | 10 +++ .../webstorage/event_constructor.window.html | 8 ++ .../webstorage/event_constructor.window.js | 77 +++++++++++++++++++ .../event_initstorageevent.window.html | 8 ++ .../event_initstorageevent.window.js | 60 +++++++++++++++ 13 files changed, 333 insertions(+) create mode 100644 Libraries/LibWeb/HTML/StorageEvent.cpp create mode 100644 Libraries/LibWeb/HTML/StorageEvent.h create mode 100644 Libraries/LibWeb/HTML/StorageEvent.idl create mode 100644 Tests/LibWeb/Text/expected/wpt-import/webstorage/event_constructor.window.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/webstorage/event_initstorageevent.window.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/webstorage/event_constructor.window.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/webstorage/event_constructor.window.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/webstorage/event_initstorageevent.window.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/webstorage/event_initstorageevent.window.js diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 90c604b700d..77e1622f0eb 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -482,6 +482,7 @@ set(SOURCES HTML/SharedResourceRequest.cpp HTML/SourceSet.cpp HTML/Storage.cpp + HTML/StorageEvent.cpp HTML/StructuredSerialize.cpp HTML/SubmitEvent.cpp HTML/SyntaxHighlighter/SyntaxHighlighter.cpp diff --git a/Libraries/LibWeb/HTML/StorageEvent.cpp b/Libraries/LibWeb/HTML/StorageEvent.cpp new file mode 100644 index 00000000000..d0745b82fe8 --- /dev/null +++ b/Libraries/LibWeb/HTML/StorageEvent.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace Web::HTML { + +GC_DEFINE_ALLOCATOR(StorageEvent); + +GC::Ref StorageEvent::create(JS::Realm& realm, FlyString const& event_name, StorageEventInit const& event_init) +{ + auto event = realm.create(realm, event_name, event_init); + event->set_is_trusted(true); + return event; +} + +GC::Ref StorageEvent::construct_impl(JS::Realm& realm, FlyString const& event_name, StorageEventInit const& event_init) +{ + return realm.create(realm, event_name, event_init); +} + +StorageEvent::~StorageEvent() = default; + +// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storageevent-initstorageevent +void StorageEvent::init_storage_event(String const& type, bool bubbles, bool cancelable, + Optional const& key, Optional const& old_value, Optional const& new_value, + String const& url, GC::Ptr storage_area) +{ + // The initStorageEvent(type, bubbles, cancelable, key, oldValue, newValue, url, storageArea) method must initialize + // the event in a manner analogous to the similarly-named initEvent() method. [DOM] + if (dispatched()) + return; + + initialize_event(type, bubbles, cancelable); + m_key = key; + m_old_value = old_value; + m_new_value = new_value; + m_url = url; + m_storage_area = storage_area; +} + +StorageEvent::StorageEvent(JS::Realm& realm, FlyString const& event_name, StorageEventInit const& event_init) + : DOM::Event(realm, event_name, event_init) + , m_key(event_init.key) + , m_old_value(event_init.old_value) + , m_new_value(event_init.new_value) + , m_url(event_init.url) + , m_storage_area(event_init.storage_area) +{ +} + +void StorageEvent::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + WEB_SET_PROTOTYPE_FOR_INTERFACE(StorageEvent); +} + +void StorageEvent::visit_edges(Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_storage_area); +} + +} diff --git a/Libraries/LibWeb/HTML/StorageEvent.h b/Libraries/LibWeb/HTML/StorageEvent.h new file mode 100644 index 00000000000..0cc58130d58 --- /dev/null +++ b/Libraries/LibWeb/HTML/StorageEvent.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Web::HTML { + +// https://html.spec.whatwg.org/multipage/webstorage.html#storageeventinit +struct StorageEventInit : public DOM::EventInit { + Optional key; + Optional old_value; + Optional new_value; + String url; + GC::Ptr storage_area; +}; + +// https://html.spec.whatwg.org/multipage/webstorage.html#storageevent +class StorageEvent : public DOM::Event { + WEB_PLATFORM_OBJECT(StorageEvent, DOM::Event); + GC_DECLARE_ALLOCATOR(StorageEvent); + +public: + [[nodiscard]] static GC::Ref create(JS::Realm&, FlyString const& event_name, StorageEventInit const& event_init = {}); + static GC::Ref construct_impl(JS::Realm&, FlyString const& event_name, StorageEventInit const& event_init); + + virtual ~StorageEvent() override; + + Optional const& key() const { return m_key; } + Optional const& old_value() const { return m_old_value; } + Optional const& new_value() const { return m_new_value; } + String const& url() const { return m_url; } + GC::Ptr storage_area() const { return m_storage_area; } + + void init_storage_event(String const& type, bool bubbles = false, bool cancelable = false, + Optional const& key = {}, Optional const& old_value = {}, Optional const& new_value = {}, + String const& url = {}, GC::Ptr storage_area = {}); + +protected: + virtual void visit_edges(Visitor& visitor) override; + virtual void initialize(JS::Realm&) override; + +private: + StorageEvent(JS::Realm&, FlyString const& event_name, StorageEventInit const& event_init); + + Optional m_key; + Optional m_old_value; + Optional m_new_value; + String m_url; + GC::Ptr m_storage_area; +}; + +} diff --git a/Libraries/LibWeb/HTML/StorageEvent.idl b/Libraries/LibWeb/HTML/StorageEvent.idl new file mode 100644 index 00000000000..46ec74382bc --- /dev/null +++ b/Libraries/LibWeb/HTML/StorageEvent.idl @@ -0,0 +1,25 @@ +#import +#import + +// https://html.spec.whatwg.org/multipage/webstorage.html#storageevent +[Exposed=Window] +interface StorageEvent : Event { + constructor(DOMString type, optional StorageEventInit eventInitDict = {}); + + readonly attribute DOMString? key; + readonly attribute DOMString? oldValue; + readonly attribute DOMString? newValue; + readonly attribute USVString url; + readonly attribute Storage? storageArea; + + undefined initStorageEvent(DOMString type, optional boolean bubbles = false, optional boolean cancelable = false, optional DOMString? key = null, optional DOMString? oldValue = null, optional DOMString? newValue = null, optional USVString url = "", optional Storage? storageArea = null); +}; + +// https://html.spec.whatwg.org/multipage/webstorage.html#storageeventinit +dictionary StorageEventInit : EventInit { + DOMString? key = null; + DOMString? oldValue = null; + DOMString? newValue = null; + USVString url = ""; + Storage? storageArea = null; +}; diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index b29a5e71203..95c1f2ff729 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -224,6 +224,7 @@ libweb_js_bindings(HTML/PromiseRejectionEvent) libweb_js_bindings(HTML/RadioNodeList) libweb_js_bindings(HTML/ShadowRealmGlobalScope GLOBAL) libweb_js_bindings(HTML/Storage) +libweb_js_bindings(HTML/StorageEvent) libweb_js_bindings(HTML/SubmitEvent) libweb_js_bindings(HTML/TextMetrics) libweb_js_bindings(HTML/TextTrack) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index fdcd1fabbb4..905a22ebc2b 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -97,6 +97,7 @@ static bool is_platform_object(Type const& type) "SVGTransform"sv, "ShadowRoot"sv, "SourceBuffer"sv, + "Storage"sv, "Table"sv, "Text"sv, "TextMetrics"sv, diff --git a/Tests/LibWeb/Text/expected/all-window-properties.txt b/Tests/LibWeb/Text/expected/all-window-properties.txt index a61ef9f9177..399e330c996 100644 --- a/Tests/LibWeb/Text/expected/all-window-properties.txt +++ b/Tests/LibWeb/Text/expected/all-window-properties.txt @@ -357,6 +357,7 @@ SourceBuffer SourceBufferList StaticRange Storage +StorageEvent StorageManager String StyleSheet diff --git a/Tests/LibWeb/Text/expected/wpt-import/webstorage/event_constructor.window.txt b/Tests/LibWeb/Text/expected/wpt-import/webstorage/event_constructor.window.txt new file mode 100644 index 00000000000..022a4191ccd --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/webstorage/event_constructor.window.txt @@ -0,0 +1,11 @@ +Harness status: OK + +Found 6 tests + +6 Pass +Pass StorageEvent constructor called as normal function +Pass constructor with no arguments +Pass constructor with just type argument +Pass constructor with sensible type argument and members +Pass constructor with null type argument and members +Pass constructor with undefined type argument and members \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/webstorage/event_initstorageevent.window.txt b/Tests/LibWeb/Text/expected/wpt-import/webstorage/event_initstorageevent.window.txt new file mode 100644 index 00000000000..acb54dc660c --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/webstorage/event_initstorageevent.window.txt @@ -0,0 +1,10 @@ +Harness status: OK + +Found 5 tests + +5 Pass +Pass initStorageEvent with 0 arguments +Pass initStorageEvent with 1 argument +Pass initStorageEvent with 8 sensible arguments +Pass initStorageEvent with 8 null arguments +Pass initStorageEvent with 8 undefined arguments \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/webstorage/event_constructor.window.html b/Tests/LibWeb/Text/input/wpt-import/webstorage/event_constructor.window.html new file mode 100644 index 00000000000..1308d2b57d1 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/webstorage/event_constructor.window.html @@ -0,0 +1,8 @@ + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/webstorage/event_constructor.window.js b/Tests/LibWeb/Text/input/wpt-import/webstorage/event_constructor.window.js new file mode 100644 index 00000000000..13f4ca5e97c --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/webstorage/event_constructor.window.js @@ -0,0 +1,77 @@ +test(function() { + assert_throws_js( + TypeError, + () => StorageEvent(""), + "Calling StorageEvent constructor without 'new' must throw" + ); +}, "StorageEvent constructor called as normal function"); + +test(function() { + assert_throws_js(TypeError, () => new StorageEvent()); + // should be redundant, but .length can be wrong with custom bindings + assert_equals(StorageEvent.length, 1, 'StorageEvent.length'); +}, 'constructor with no arguments'); + +test(function() { + var event = new StorageEvent('type'); + assert_equals(event.type, 'type', 'type'); + assert_equals(event.key, null, 'key'); + assert_equals(event.oldValue, null, 'oldValue'); + assert_equals(event.newValue, null, 'newValue'); + assert_equals(event.url, '', 'url'); + assert_equals(event.storageArea, null, 'storageArea'); +}, 'constructor with just type argument'); + +test(function() { + assert_not_equals(localStorage, null, 'localStorage'); // precondition + + var event = new StorageEvent('storage', { + bubbles: true, + cancelable: true, + key: 'key', + oldValue: 'oldValue', + newValue: 'newValue', + url: 'url', // not an absolute URL to ensure it isn't resolved + storageArea: localStorage + }); + assert_equals(event.type, 'storage', 'type'); + assert_equals(event.bubbles, true, 'bubbles'); + assert_equals(event.cancelable, true, 'cancelable'); + assert_equals(event.key, 'key', 'key'); + assert_equals(event.oldValue, 'oldValue', 'oldValue'); + assert_equals(event.newValue, 'newValue', 'newValue'); + assert_equals(event.url, 'url', 'url'); + assert_equals(event.storageArea, localStorage, 'storageArea'); +}, 'constructor with sensible type argument and members'); + +test(function() { + var event = new StorageEvent(null, { + key: null, + oldValue: null, + newValue: null, + url: null, + storageArea: null + }); + assert_equals(event.type, 'null', 'type'); + assert_equals(event.key, null, 'key'); + assert_equals(event.oldValue, null, 'oldValue'); + assert_equals(event.newValue, null, 'newValue'); + assert_equals(event.url, 'null', 'url'); + assert_equals(event.storageArea, null, 'storageArea'); +}, 'constructor with null type argument and members'); + +test(function() { + var event = new StorageEvent(undefined, { + key: undefined, + oldValue: undefined, + newValue: undefined, + url: undefined, + storageArea: undefined + }); + assert_equals(event.type, 'undefined', 'type'); + assert_equals(event.key, null, 'key'); + assert_equals(event.oldValue, null, 'oldValue'); + assert_equals(event.newValue, null, 'newValue'); + assert_equals(event.url, '', 'url'); // not 'undefined'! + assert_equals(event.storageArea, null, 'storageArea'); +}, 'constructor with undefined type argument and members'); diff --git a/Tests/LibWeb/Text/input/wpt-import/webstorage/event_initstorageevent.window.html b/Tests/LibWeb/Text/input/wpt-import/webstorage/event_initstorageevent.window.html new file mode 100644 index 00000000000..2e3385f7fb8 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/webstorage/event_initstorageevent.window.html @@ -0,0 +1,8 @@ + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/webstorage/event_initstorageevent.window.js b/Tests/LibWeb/Text/input/wpt-import/webstorage/event_initstorageevent.window.js new file mode 100644 index 00000000000..ac0a7570210 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/webstorage/event_initstorageevent.window.js @@ -0,0 +1,60 @@ +test(() => { + const event = new StorageEvent('storage'); + assert_throws_js(TypeError, () => event.initStorageEvent()); + // should be redundant, but .length can be wrong with custom bindings + assert_equals(event.initStorageEvent.length, 1, 'event.initStorageEvent.length'); +}, 'initStorageEvent with 0 arguments'); + +test(() => { + const event = new StorageEvent('storage'); + event.initStorageEvent('type'); + assert_equals(event.type, 'type', 'event.type'); + assert_equals(event.bubbles, false, 'event.bubbles'); + assert_equals(event.cancelable, false, 'event.cancelable'); + assert_equals(event.key, null, 'event.key'); + assert_equals(event.oldValue, null, 'event.oldValue'); + assert_equals(event.newValue, null, 'event.newValue'); + assert_equals(event.url, '', 'event.url'); + assert_equals(event.storageArea, null, 'event.storageArea'); +}, 'initStorageEvent with 1 argument'); + +test(() => { + assert_not_equals(localStorage, null, 'localStorage'); // precondition + + const event = new StorageEvent('storage'); + event.initStorageEvent('type', true, true, 'key', 'oldValue', 'newValue', 'url', localStorage); + assert_equals(event.type, 'type', 'event.type'); + assert_equals(event.bubbles, true, 'event.bubbles'); + assert_equals(event.cancelable, true, 'event.cancelable'); + assert_equals(event.key, 'key', 'event.key'); + assert_equals(event.oldValue, 'oldValue', 'event.oldValue'); + assert_equals(event.newValue, 'newValue', 'event.newValue'); + assert_equals(event.url, 'url', 'event.url'); + assert_equals(event.storageArea, localStorage, 'event.storageArea'); +}, 'initStorageEvent with 8 sensible arguments'); + +test(() => { + const event = new StorageEvent('storage'); + event.initStorageEvent(null, null, null, null, null, null, null, null); + assert_equals(event.type, 'null', 'event.type'); + assert_equals(event.bubbles, false, 'event.bubbles'); + assert_equals(event.cancelable, false, 'event.cancelable'); + assert_equals(event.key, null, 'event.key'); + assert_equals(event.oldValue, null, 'event.oldValue'); + assert_equals(event.newValue, null, 'event.newValue'); + assert_equals(event.url, 'null', 'event.url'); + assert_equals(event.storageArea, null, 'event.storageArea'); +}, 'initStorageEvent with 8 null arguments'); + +test(() => { + const event = new StorageEvent('storage'); + event.initStorageEvent(undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined); + assert_equals(event.type, 'undefined', 'event.type'); + assert_equals(event.bubbles, false, 'event.bubbles'); + assert_equals(event.cancelable, false, 'event.cancelable'); + assert_equals(event.key, null, 'event.key'); + assert_equals(event.oldValue, null, 'event.oldValue'); + assert_equals(event.newValue, null, 'event.newValue'); + assert_equals(event.url, '', 'event.url'); + assert_equals(event.storageArea, null, 'event.storageArea'); +}, 'initStorageEvent with 8 undefined arguments');