mirror of
https://github.com/lexmount/moli.git
synced 2026-10-25 08:01:49 +00:00
fix(webstorage): preserve StorageEvent WebIDL initialization
This commit is contained in:
@@ -49,16 +49,16 @@ struct InitStorageEventArgs<'s> {
|
||||
bubbles: bool,
|
||||
#[webidl(default = false)]
|
||||
cancelable: bool,
|
||||
#[webidl(index = 3, nullable)]
|
||||
key: Option<String>,
|
||||
#[webidl(name = "oldValue", index = 4, nullable)]
|
||||
old_value: Option<String>,
|
||||
#[webidl(name = "newValue", index = 5, nullable)]
|
||||
new_value: Option<String>,
|
||||
#[webidl(index = 3, nullable, converter = "raw")]
|
||||
key: Option<webidl::DomString16>,
|
||||
#[webidl(name = "oldValue", index = 4, nullable, converter = "raw")]
|
||||
old_value: Option<webidl::DomString16>,
|
||||
#[webidl(name = "newValue", index = 5, nullable, converter = "raw")]
|
||||
new_value: Option<webidl::DomString16>,
|
||||
#[webidl(default = "", index = 6, converter = "usv_string")]
|
||||
url: String,
|
||||
#[webidl(index = 7, converter = "raw", nullable)]
|
||||
storage_area: Option<v8::Local<'s, v8::Value>>,
|
||||
storage_area: Option<super::web_storage::StorageReference<'s>>,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
@@ -508,14 +508,14 @@ pub(super) fn storage_event_init_callback<'s>(
|
||||
) {
|
||||
return;
|
||||
}
|
||||
super::events::define_storage_event_properties(
|
||||
super::events::define_storage_event_properties_utf16(
|
||||
scope,
|
||||
event,
|
||||
parsed.key.as_deref(),
|
||||
parsed.old_value.as_deref(),
|
||||
parsed.new_value.as_deref(),
|
||||
parsed.key.as_ref().map(|value| value.0.as_slice()),
|
||||
parsed.old_value.as_ref().map(|value| value.0.as_slice()),
|
||||
parsed.new_value.as_ref().map(|value| value.0.as_slice()),
|
||||
&parsed.url,
|
||||
parsed.storage_area,
|
||||
parsed.storage_area.map(|value| value.0.into()),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use crate::util::{get_private_value, utf16_units, v8_string, v8_string_from_utf16_units, v8str};
|
||||
use crate::util::{get_private_value, v8_string, v8_string_from_utf16_units, v8str};
|
||||
use moli_webapi_declare::{ObjectLiteralDeclaration, WebApiObject};
|
||||
|
||||
mod base;
|
||||
@@ -181,29 +181,6 @@ pub(crate) fn construct_original_storage_event_utf16<'s>(
|
||||
Some(event)
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn define_storage_event_properties<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
event: v8::Local<'s, v8::Object>,
|
||||
key: Option<&str>,
|
||||
old_value: Option<&str>,
|
||||
new_value: Option<&str>,
|
||||
url: &str,
|
||||
storage_area: Option<v8::Local<'s, v8::Value>>,
|
||||
) {
|
||||
let key = key.map(utf16_units);
|
||||
let old_value = old_value.map(utf16_units);
|
||||
let new_value = new_value.map(utf16_units);
|
||||
define_storage_event_properties_utf16(
|
||||
scope,
|
||||
event,
|
||||
key.as_deref(),
|
||||
old_value.as_deref(),
|
||||
new_value.as_deref(),
|
||||
url,
|
||||
storage_area,
|
||||
);
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn define_storage_event_properties_utf16<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
event: v8::Local<'s, v8::Object>,
|
||||
|
||||
@@ -42,7 +42,18 @@ fn event_subclass_constructor_callback<'s>(
|
||||
init_arg.to_object(scope)
|
||||
}
|
||||
};
|
||||
let (bubbles, cancelable, composed) = read_event_init(scope, &args);
|
||||
let storage_event_init = if kind == EventSubclassKind::StorageEvent {
|
||||
let Some(init) = data::parse_storage_event_init(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
Some(init)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let (bubbles, cancelable, composed) = storage_event_init
|
||||
.as_ref()
|
||||
.map(data::StorageEventInitMembers::event_flags)
|
||||
.unwrap_or_else(|| read_event_init(scope, &args));
|
||||
|
||||
initialize_event_object_with_type(scope, event, event_type, bubbles, cancelable);
|
||||
define_event_property(
|
||||
@@ -113,9 +124,11 @@ fn event_subclass_constructor_callback<'s>(
|
||||
}
|
||||
EventSubclassKind::MessageEvent => data::initialize_message_event(scope, event, init),
|
||||
EventSubclassKind::StorageEvent => {
|
||||
if !data::initialize_storage_event(scope, event, init) {
|
||||
return;
|
||||
}
|
||||
data::initialize_storage_event(
|
||||
scope,
|
||||
event,
|
||||
storage_event_init.expect("StorageEvent init should be parsed"),
|
||||
);
|
||||
}
|
||||
EventSubclassKind::ErrorEvent => data::initialize_error_event(scope, event, init),
|
||||
EventSubclassKind::PromiseRejectionEvent => {
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::context_bootstrap::navigation_handler_callbacks::{
|
||||
use crate::context_bootstrap::navigation_window::{
|
||||
child_browsing_context_handle_for_runtime_owner, runtime_window_is_global, runtime_window_owner,
|
||||
};
|
||||
use crate::context_bootstrap::web_storage::StorageReference;
|
||||
use crate::native_bridge::element::scroll_to_url_fragment_or_top;
|
||||
use crate::native_bridge::throw_dom_exception;
|
||||
use crate::util::context_host_ptr_from_global_bridge;
|
||||
@@ -314,17 +315,47 @@ struct ToggleEventInitMembers<'s> {
|
||||
|
||||
#[derive(Default, webidl::WebIdlDictionary)]
|
||||
#[webidl(prefix = "StorageEventInit")]
|
||||
struct StorageEventInitMembers<'s> {
|
||||
#[webidl(nullable)]
|
||||
key: Option<String>,
|
||||
#[webidl(name = "oldValue", nullable)]
|
||||
old_value: Option<String>,
|
||||
#[webidl(name = "newValue", nullable)]
|
||||
new_value: Option<String>,
|
||||
pub(super) struct StorageEventInitMembers<'s> {
|
||||
#[webidl(default = false)]
|
||||
bubbles: bool,
|
||||
#[webidl(default = false)]
|
||||
cancelable: bool,
|
||||
#[webidl(default = false)]
|
||||
composed: bool,
|
||||
#[webidl(nullable, converter = "raw")]
|
||||
key: Option<webidl::DomString16>,
|
||||
#[webidl(name = "newValue", nullable, converter = "raw")]
|
||||
new_value: Option<webidl::DomString16>,
|
||||
#[webidl(name = "oldValue", nullable, converter = "raw")]
|
||||
old_value: Option<webidl::DomString16>,
|
||||
#[webidl(name = "storageArea", converter = "raw", nullable)]
|
||||
storage_area: Option<StorageReference<'s>>,
|
||||
#[webidl(default = "", converter = "usv_string")]
|
||||
url: String,
|
||||
#[webidl(name = "storageArea", converter = "raw", nullable)]
|
||||
storage_area: Option<v8::Local<'s, v8::Value>>,
|
||||
}
|
||||
|
||||
impl StorageEventInitMembers<'_> {
|
||||
pub(super) fn event_flags(&self) -> (bool, bool, bool) {
|
||||
(self.bubbles, self.cancelable, self.composed)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn parse_storage_event_init<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: &v8::FunctionCallbackArguments<'s>,
|
||||
) -> Option<StorageEventInitMembers<'s>> {
|
||||
let parsed = webidl::dictionary_arg(args, 1, webidl::Context::argument("StorageEvent", 2))
|
||||
.and_then(|object| match object {
|
||||
Some(object) => webidl::parse_dictionary_object(scope, object),
|
||||
None => Ok(StorageEventInitMembers::default()),
|
||||
});
|
||||
match parsed {
|
||||
Ok(init) => Some(init),
|
||||
Err(error) => {
|
||||
webidl::throw_error(scope, &error);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_event_source_member<'s>(
|
||||
@@ -482,30 +513,17 @@ pub(in crate::context_bootstrap::events::subclasses) fn initialize_message_event
|
||||
pub(in crate::context_bootstrap::events::subclasses) fn initialize_storage_event<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
event: v8::Local<'s, v8::Object>,
|
||||
init: Option<v8::Local<'s, v8::Object>>,
|
||||
) -> bool {
|
||||
let parsed = match init {
|
||||
Some(init) => {
|
||||
match webidl::parse_dictionary_object::<StorageEventInitMembers>(scope, init) {
|
||||
Ok(parsed) => parsed,
|
||||
Err(error) => {
|
||||
webidl::throw_error(scope, &error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => StorageEventInitMembers::default(),
|
||||
};
|
||||
crate::context_bootstrap::events::define_storage_event_properties(
|
||||
parsed: StorageEventInitMembers<'s>,
|
||||
) {
|
||||
crate::context_bootstrap::events::define_storage_event_properties_utf16(
|
||||
scope,
|
||||
event,
|
||||
parsed.key.as_deref(),
|
||||
parsed.old_value.as_deref(),
|
||||
parsed.new_value.as_deref(),
|
||||
parsed.key.as_ref().map(|value| value.0.as_slice()),
|
||||
parsed.old_value.as_ref().map(|value| value.0.as_slice()),
|
||||
parsed.new_value.as_ref().map(|value| value.0.as_slice()),
|
||||
&parsed.url,
|
||||
parsed.storage_area,
|
||||
parsed.storage_area.map(|value| value.0.into()),
|
||||
);
|
||||
true
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap::events::subclasses) fn initialize_input_event<'s>(
|
||||
|
||||
@@ -1,4 +1,25 @@
|
||||
use super::*;
|
||||
use crate::webidl;
|
||||
|
||||
pub(crate) struct StorageReference<'scope>(pub(crate) v8::Local<'scope, v8::Object>);
|
||||
|
||||
impl<'scope> webidl::WebIdlConverter<'scope> for StorageReference<'scope> {
|
||||
type Options = ();
|
||||
|
||||
fn convert(
|
||||
scope: &mut v8::PinScope<'scope, '_>,
|
||||
value: v8::Local<'scope, v8::Value>,
|
||||
context: webidl::Context,
|
||||
_options: &Self::Options,
|
||||
) -> Result<Self, webidl::WebIdlError> {
|
||||
if let Ok(object) = v8::Local::<v8::Object>::try_from(value)
|
||||
&& crate::web_api_interfaces::Storage::is_instance(scope, object)
|
||||
{
|
||||
return Ok(Self(object));
|
||||
}
|
||||
Err(webidl::WebIdlError::cannot_convert(context, "Storage"))
|
||||
}
|
||||
}
|
||||
|
||||
mod accessors;
|
||||
mod callbacks;
|
||||
|
||||
@@ -49,6 +49,7 @@ mod session_description;
|
||||
mod simple_handler_object;
|
||||
mod speech_synthesis;
|
||||
mod storage_access;
|
||||
mod storage_event_init;
|
||||
mod structured_clone;
|
||||
mod transferable_streams;
|
||||
mod traversal;
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
(() => {
|
||||
const rows = [];
|
||||
const check = (name, run) => {
|
||||
try {
|
||||
if (run() !== true) throw new Error('assertion failed');
|
||||
rows.push({name, passed: true});
|
||||
} catch (error) {
|
||||
rows.push({name, passed: false, error: String(error), stack: error.stack});
|
||||
}
|
||||
};
|
||||
const throws = (run, expected) => {
|
||||
try { run(); } catch (error) { return error === expected; }
|
||||
return false;
|
||||
};
|
||||
const typeError = (run, realm = globalThis) => {
|
||||
try { run(); } catch (error) { return error instanceof realm.TypeError; }
|
||||
return false;
|
||||
};
|
||||
const legacy = (key, oldValue, newValue, url, storageArea) => {
|
||||
const event = new StorageEvent('before');
|
||||
event.initStorageEvent('after', true, true, key, oldValue, newValue, url, storageArea);
|
||||
return event;
|
||||
};
|
||||
const strings = ['', '\ud800', '\udc00', '\ud83d\ude00', 'a\ud800b\udc00\u0000\ud83d\ude00'];
|
||||
for (const field of ['key', 'oldValue', 'newValue']) {
|
||||
for (let i = 0; i < strings.length; i++) {
|
||||
const text = strings[i];
|
||||
check(`constructor ${field} UTF-16 ${i}`, () => new StorageEvent('x', {[field]: text})[field] === text);
|
||||
check(`legacy ${field} UTF-16 ${i}`, () => legacy(text, text, text, '')[field] === text);
|
||||
}
|
||||
for (const value of [undefined, null]) {
|
||||
check(`constructor ${field} nullable ${String(value)}`, () => new StorageEvent('x', {[field]: value})[field] === null);
|
||||
check(`legacy ${field} nullable ${String(value)}`, () => legacy(value, value, value, '')[field] === null);
|
||||
}
|
||||
check(`constructor ${field} string conversion`, () => {
|
||||
let conversions = 0;
|
||||
const value = {[Symbol.toPrimitive](hint) { if (hint !== 'string') throw new Error(hint); conversions++; return strings[4]; }};
|
||||
const event = new StorageEvent('x', {[field]: value});
|
||||
return conversions === 1 && event[field] === strings[4];
|
||||
});
|
||||
check(`legacy ${field} string conversion`, () => {
|
||||
let conversions = 0;
|
||||
const value = {toString() { conversions++; return strings[4]; }};
|
||||
const values = {key: null, oldValue: null, newValue: null, [field]: value};
|
||||
const event = legacy(values.key, values.oldValue, values.newValue, '');
|
||||
return conversions === 1 && event[field] === strings[4];
|
||||
});
|
||||
check(`constructor ${field} Symbol rejects`, () => typeError(() => new StorageEvent('x', {[field]: Symbol()})));
|
||||
check(`legacy ${field} Symbol rejects`, () => {
|
||||
const values = {key: null, oldValue: null, newValue: null, [field]: Symbol()};
|
||||
return typeError(() => legacy(values.key, values.oldValue, values.newValue, ''));
|
||||
});
|
||||
}
|
||||
check('constructor defaults', () => {
|
||||
const event = new StorageEvent('x');
|
||||
return event.key === null && event.oldValue === null && event.newValue === null && event.url === '' && event.storageArea === null && !event.bubbles && !event.cancelable && !event.composed;
|
||||
});
|
||||
check('legacy defaults', () => {
|
||||
const event = new StorageEvent('before', {key: 'k', oldValue: 'o', newValue: 'n', composed: true});
|
||||
const result = event.initStorageEvent('after');
|
||||
return result === undefined && event.type === 'after' && event.key === null && event.oldValue === null && event.newValue === null && event.url === '' && event.storageArea === null && event.composed;
|
||||
});
|
||||
for (const [value, expected] of [[undefined, ''], [null, 'null'], [strings[4], 'a\ufffdb\ufffd\u0000\ud83d\ude00']]) {
|
||||
check(`constructor url USVString ${String(value)}`, () => new StorageEvent('x', {url: value}).url === expected);
|
||||
check(`legacy url USVString ${String(value)}`, () => legacy(null, null, null, value).url === expected);
|
||||
}
|
||||
for (const value of [undefined, null]) {
|
||||
check(`nullable dictionary ${String(value)}`, () => new StorageEvent('x', value).key === null);
|
||||
}
|
||||
for (const value of [false, true, 0, 1, '', 'init', Symbol('init'), 1n]) {
|
||||
check(`primitive dictionary ${String(value)}`, () => typeError(() => new StorageEvent('x', value)));
|
||||
}
|
||||
check('function dictionary', () => {
|
||||
const init = () => {};
|
||||
init.key = strings[4];
|
||||
return new StorageEvent('x', init).key === strings[4];
|
||||
});
|
||||
const order = ['bubbles', 'cancelable', 'composed', 'key', 'newValue', 'oldValue', 'storageArea', 'url'];
|
||||
check('inherited then own dictionary order', () => {
|
||||
const seen = [];
|
||||
new StorageEvent('x', new Proxy({}, {get(_target, name) { seen.push(name); }}));
|
||||
return seen.join() === order.join();
|
||||
});
|
||||
check('dictionary reads and conversions are interleaved once', () => {
|
||||
const seen = [];
|
||||
const init = new Proxy({}, {get(_target, name) {
|
||||
seen.push(`get:${name}`);
|
||||
if (['key', 'newValue', 'oldValue', 'url'].includes(name)) {
|
||||
return {[Symbol.toPrimitive](hint) { seen.push(`convert:${name}:${hint}`); return strings[4]; }};
|
||||
}
|
||||
}});
|
||||
const event = new StorageEvent('x', init);
|
||||
const expected = order.flatMap(name => ['key', 'newValue', 'oldValue', 'url'].includes(name) ? [`get:${name}`, `convert:${name}:string`] : [`get:${name}`]);
|
||||
return seen.join() === expected.join() && event.key === strings[4] && event.url === 'a\ufffdb\ufffd\u0000\ud83d\ude00';
|
||||
});
|
||||
for (const stop of order) {
|
||||
check(`dictionary getter exception stops at ${stop}`, () => {
|
||||
const sentinel = {};
|
||||
const seen = [];
|
||||
const init = new Proxy({}, {get(_target, name) { seen.push(name); if (name === stop) throw sentinel; }});
|
||||
return throws(() => new StorageEvent('x', init), sentinel) && seen.join() === order.slice(0, order.indexOf(stop) + 1).join();
|
||||
});
|
||||
}
|
||||
for (const stop of ['key', 'newValue', 'oldValue', 'url']) {
|
||||
check(`dictionary conversion exception stops at ${stop}`, () => {
|
||||
const sentinel = {};
|
||||
const seen = [];
|
||||
const init = new Proxy({}, {get(_target, name) { seen.push(name); if (name === stop) return {toString() { throw sentinel; }}; }});
|
||||
return throws(() => new StorageEvent('x', init), sentinel) && seen.join() === order.slice(0, order.indexOf(stop) + 1).join();
|
||||
});
|
||||
}
|
||||
check('inherited dictionary values retain UTF-16', () => new StorageEvent('x', Object.create({key: strings[4], newValue: strings[1]})).key === strings[4]);
|
||||
check('event type converts before dictionary', () => {
|
||||
const seen = [];
|
||||
const type = {toString() { seen.push('type'); return strings[4]; }};
|
||||
const init = new Proxy({}, {get(_target, name) { seen.push(name); }});
|
||||
const event = new StorageEvent(type, init);
|
||||
return event.type === strings[4] && seen.join() === ['type', ...order].join();
|
||||
});
|
||||
check('legacy positional conversion order', () => {
|
||||
const seen = [];
|
||||
const value = name => ({toString() { seen.push(name); return name; }});
|
||||
const event = new StorageEvent('before');
|
||||
event.initStorageEvent(value('type'), true, true, value('key'), value('oldValue'), value('newValue'), value('url'), null);
|
||||
return seen.join() === 'type,key,oldValue,newValue,url' && event.key === 'key' && event.oldValue === 'oldValue' && event.newValue === 'newValue';
|
||||
});
|
||||
for (const field of ['key', 'oldValue', 'newValue', 'url']) {
|
||||
check(`legacy failed ${field} conversion is atomic`, () => {
|
||||
const event = new StorageEvent('before', {bubbles: true, composed: true, key: 'k', oldValue: 'o', newValue: 'n', url: 'u'});
|
||||
const sentinel = {};
|
||||
const seen = [];
|
||||
const names = ['key', 'oldValue', 'newValue', 'url'];
|
||||
const values = names.map(name => ({toString() { seen.push(name); if (name === field) throw sentinel; return name; }}));
|
||||
return throws(() => event.initStorageEvent('after', false, true, ...values, null), sentinel) && seen.join() === names.slice(0, names.indexOf(field) + 1).join() && event.type === 'before' && event.bubbles && !event.cancelable && event.composed && event.key === 'k' && event.oldValue === 'o' && event.newValue === 'n' && event.url === 'u';
|
||||
});
|
||||
}
|
||||
const invalidAreas = [false, 1, 'storage', {}, [], Object.create(Storage.prototype)];
|
||||
for (let i = 0; i < invalidAreas.length; i++) {
|
||||
const storageArea = invalidAreas[i];
|
||||
check(`constructor rejects unbranded storageArea ${i}`, () => typeError(() => new StorageEvent('x', {storageArea})));
|
||||
check(`legacy rejects unbranded storageArea ${i} before mutation`, () => {
|
||||
const event = new StorageEvent('before', {key: 'keep'});
|
||||
return typeError(() => event.initStorageEvent('after', false, false, null, null, null, '', storageArea)) && event.type === 'before' && event.key === 'keep';
|
||||
});
|
||||
}
|
||||
check('invalid storageArea stops before url getter', () => {
|
||||
let reads = 0;
|
||||
return typeError(() => new StorageEvent('x', {storageArea: {}, get url() { reads++; return ''; }})) && reads === 0;
|
||||
});
|
||||
const other = document.getElementById('child').contentWindow;
|
||||
check('constructor conversion TypeError uses callee realm', () => typeError(() => new other.StorageEvent('x', {key: Symbol()}), other));
|
||||
check('legacy conversion TypeError uses callee realm', () => typeError(() => other.StorageEvent.prototype.initStorageEvent.call(new StorageEvent('x'), 'x', false, false, Symbol()), other));
|
||||
check('constructor storageArea TypeError uses callee realm', () => typeError(() => new other.StorageEvent('x', {storageArea: {}}), other));
|
||||
check('legacy rejects forged receiver before converting arguments', () => {
|
||||
let conversions = 0;
|
||||
const type = {toString() { conversions++; return 'x'; }};
|
||||
return typeError(() => StorageEvent.prototype.initStorageEvent.call(Object.create(StorageEvent.prototype), type)) && conversions === 0;
|
||||
});
|
||||
check('legacy dispatch guard follows conversion and preserves state', () => {
|
||||
const target = new EventTarget();
|
||||
const event = new StorageEvent('storage', {key: 'keep'});
|
||||
let conversions = 0;
|
||||
target.addEventListener('storage', current => {
|
||||
current.initStorageEvent('after', true, true, {toString() { conversions++; return strings[4]; }});
|
||||
});
|
||||
target.dispatchEvent(event);
|
||||
return conversions === 1 && event.type === 'storage' && event.key === 'keep' && !event.bubbles && !event.cancelable;
|
||||
});
|
||||
if (location.protocol !== 'data:') {
|
||||
for (const [name, storageArea] of [['local', localStorage], ['session', sessionStorage], ['child', other.localStorage]]) {
|
||||
check(`constructor accepts branded ${name} Storage`, () => new StorageEvent('x', {storageArea}).storageArea === storageArea);
|
||||
check(`legacy accepts branded ${name} Storage`, () => legacy(null, null, null, '', storageArea).storageArea === storageArea);
|
||||
}
|
||||
let traps = 0;
|
||||
const author = new Proxy(localStorage, {get() { traps++; throw new Error('trap'); }, getPrototypeOf() { traps++; throw new Error('trap'); }});
|
||||
const revoked = Proxy.revocable(localStorage, {}); revoked.revoke();
|
||||
for (const [name, storageArea] of [['author proxy', author], ['revoked proxy', revoked.proxy], ['inherited instance', Object.create(localStorage)]]) {
|
||||
check(`constructor rejects ${name} Storage without traps`, () => typeError(() => new StorageEvent('x', {storageArea})) && traps === 0);
|
||||
check(`legacy rejects ${name} Storage without traps`, () => typeError(() => legacy(null, null, null, '', storageArea)) && traps === 0);
|
||||
}
|
||||
check('cross realm constructor accepts genuine parent Storage', () => new other.StorageEvent('x', {storageArea: localStorage, key: strings[4]}).storageArea === localStorage);
|
||||
}
|
||||
globalThis.__storageEventResults = {total: rows.length, passed: rows.filter(row => row.passed).length, rows};
|
||||
const failed = rows.filter(row => !row.passed);
|
||||
return failed.length ? JSON.stringify(failed) : true;
|
||||
})()
|
||||
@@ -0,0 +1,12 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn storage_event_initializers_preserve_dom_strings_and_validate_storage_interfaces() {
|
||||
let mut vm = new_storage_html_test_vm("https://storage-event-init-webidl.test/");
|
||||
vm.eval("document.body.innerHTML = '<iframe id=child></iframe>';")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
vm.eval(include_str!("storage_event_init.js")).unwrap(),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user