LibWeb/HTML: Implement the StorageEvent interface

This commit is contained in:
Shannon Booth 2024-12-23 22:45:19 +13:00 committed by Andreas Kling
parent c5f9710492
commit f3e92f2ae5
Notes: github-actions[bot] 2024-12-25 10:58:07 +00:00
13 changed files with 333 additions and 0 deletions

View file

@ -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

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/StorageEventPrototype.h>
#include <LibWeb/HTML/Storage.h>
#include <LibWeb/HTML/StorageEvent.h>
namespace Web::HTML {
GC_DEFINE_ALLOCATOR(StorageEvent);
GC::Ref<StorageEvent> StorageEvent::create(JS::Realm& realm, FlyString const& event_name, StorageEventInit const& event_init)
{
auto event = realm.create<StorageEvent>(realm, event_name, event_init);
event->set_is_trusted(true);
return event;
}
GC::Ref<StorageEvent> StorageEvent::construct_impl(JS::Realm& realm, FlyString const& event_name, StorageEventInit const& event_init)
{
return realm.create<StorageEvent>(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<String> const& key, Optional<String> const& old_value, Optional<String> const& new_value,
String const& url, GC::Ptr<Storage> 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);
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/FlyString.h>
#include <AK/Optional.h>
#include <LibGC/Ptr.h>
#include <LibWeb/DOM/Event.h>
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/webstorage.html#storageeventinit
struct StorageEventInit : public DOM::EventInit {
Optional<String> key;
Optional<String> old_value;
Optional<String> new_value;
String url;
GC::Ptr<Storage> 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<StorageEvent> create(JS::Realm&, FlyString const& event_name, StorageEventInit const& event_init = {});
static GC::Ref<StorageEvent> construct_impl(JS::Realm&, FlyString const& event_name, StorageEventInit const& event_init);
virtual ~StorageEvent() override;
Optional<String> const& key() const { return m_key; }
Optional<String> const& old_value() const { return m_old_value; }
Optional<String> const& new_value() const { return m_new_value; }
String const& url() const { return m_url; }
GC::Ptr<Storage const> storage_area() const { return m_storage_area; }
void init_storage_event(String const& type, bool bubbles = false, bool cancelable = false,
Optional<String> const& key = {}, Optional<String> const& old_value = {}, Optional<String> const& new_value = {},
String const& url = {}, GC::Ptr<Storage> 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<String> m_key;
Optional<String> m_old_value;
Optional<String> m_new_value;
String m_url;
GC::Ptr<Storage> m_storage_area;
};
}

View file

@ -0,0 +1,25 @@
#import <DOM/Event.idl>
#import <HTML/Storage.idl>
// 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;
};

View file

@ -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)

View file

@ -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,

View file

@ -357,6 +357,7 @@ SourceBuffer
SourceBufferList
StaticRange
Storage
StorageEvent
StorageManager
String
StyleSheet

View file

@ -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

View file

@ -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

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/event_constructor.window.js"></script>

View file

@ -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');

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/event_initstorageevent.window.js"></script>

View file

@ -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');