diff --git a/moli-renderer-v8/src/context_bootstrap.rs b/moli-renderer-v8/src/context_bootstrap.rs index 977f1dc2ae..87713361b6 100644 --- a/moli-renderer-v8/src/context_bootstrap.rs +++ b/moli-renderer-v8/src/context_bootstrap.rs @@ -176,7 +176,7 @@ mod window_accessors; mod window_events; mod window_lazy_surface; mod window_receiver; -pub(crate) use window_receiver::is_window_receiver; +pub(crate) use window_receiver::{is_window_receiver, mark_window_receiver}; mod window_runtime; pub(crate) use form_data_runtime::{ diff --git a/moli-renderer-v8/src/context_bootstrap/runtime_state.rs b/moli-renderer-v8/src/context_bootstrap/runtime_state.rs index dc2d3493e7..557a8753cd 100644 --- a/moli-renderer-v8/src/context_bootstrap/runtime_state.rs +++ b/moli-renderer-v8/src/context_bootstrap/runtime_state.rs @@ -57,6 +57,7 @@ pub(crate) const ORIGINAL_WEBASSEMBLY_GLOBAL_VALUE_GETTER_SLOT: &str = "__moliOriginalWebAssemblyGlobalValueGetter"; const WINDOW_INDEXED_DB_SURFACE_SLOT: &str = "moli.Window.indexedDB"; const WINDOW_ORIGIN_RUNTIME_SLOT: &str = "__moliWindowOriginRuntime"; +const WINDOW_STATUS_RUNTIME_SLOT: &str = "__moliWindowStatusRuntime"; const WINDOW_INTRINSIC_EVAL_SLOT: &str = "__moliWindowIntrinsicEval"; pub(in crate::context_bootstrap) const WINDOW_SECURE_CONTEXT_AVAILABLE_SLOT: &str = "__moliWindowSecureContextAvailable"; @@ -266,6 +267,13 @@ struct WindowPublicSurfaceAccessorsDeclaration<'scope> { setter = window_name_runtime_setter )] name: (), + #[webapi( + accessor_property, + enumerable, + getter = window_status_runtime_getter, + setter = window_status_runtime_setter + )] + status: (), } #[derive(Default, WebApiObject)] @@ -505,20 +513,25 @@ fn legacy_unforgeable_window_getter<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'s, v8::Value>, ) { - rv.set(legacy_unforgeable_window_slot_value( - scope, - args.this(), - WINDOW_SELF_SLOT, - )); + if let Some(value) = legacy_unforgeable_window_slot_value(scope, args.this(), WINDOW_SELF_SLOT) + { + rv.set(value); + } } fn legacy_unforgeable_window_slot_value<'s>( scope: &mut v8::PinScope<'s, '_>, receiver: v8::Local<'s, v8::Object>, slot: &'static str, -) -> v8::Local<'s, v8::Value> { - object_hidden_value(scope, receiver, slot) - .unwrap_or_else(|| scope.get_current_context().global(scope).into()) +) -> Option> { + if !super::is_window_receiver(scope, receiver) { + throw_type_error(scope, "Window getter called on incompatible receiver."); + return None; + } + Some( + object_hidden_value(scope, receiver, slot) + .unwrap_or_else(|| scope.get_current_context().global(scope).into()), + ) } fn document_fullscreen_enabled_getter<'s>( @@ -727,11 +740,10 @@ fn legacy_unforgeable_self_getter<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'s, v8::Value>, ) { - rv.set(legacy_unforgeable_window_slot_value( - scope, - args.this(), - WINDOW_SELF_SLOT, - )); + if let Some(value) = legacy_unforgeable_window_slot_value(scope, args.this(), WINDOW_SELF_SLOT) + { + rv.set(value); + } } fn replaceable_window_alias_set<'s>( @@ -739,6 +751,10 @@ fn replaceable_window_alias_set<'s>( args: v8::FunctionCallbackArguments<'s>, name: &'static str, ) { + if !super::is_window_receiver(scope, args.this()) { + throw_type_error(scope, "Window setter called on incompatible receiver."); + return; + } define_replaceable_window_property(scope, args.this(), name, args.get(0)); } @@ -755,11 +771,11 @@ fn legacy_unforgeable_parent_getter<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'s, v8::Value>, ) { - rv.set(legacy_unforgeable_window_slot_value( - scope, - args.this(), - WINDOW_PARENT_SLOT, - )); + if let Some(value) = + legacy_unforgeable_window_slot_value(scope, args.this(), WINDOW_PARENT_SLOT) + { + rv.set(value); + } } fn replaceable_parent_setter<'s>( @@ -775,11 +791,9 @@ fn legacy_unforgeable_top_getter<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'s, v8::Value>, ) { - rv.set(legacy_unforgeable_window_slot_value( - scope, - args.this(), - WINDOW_TOP_SLOT, - )); + if let Some(value) = legacy_unforgeable_window_slot_value(scope, args.this(), WINDOW_TOP_SLOT) { + rv.set(value); + } } fn legacy_unforgeable_frames_getter<'s>( @@ -787,11 +801,11 @@ fn legacy_unforgeable_frames_getter<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'s, v8::Value>, ) { - rv.set(legacy_unforgeable_window_slot_value( - scope, - args.this(), - WINDOW_FRAMES_SLOT, - )); + if let Some(value) = + legacy_unforgeable_window_slot_value(scope, args.this(), WINDOW_FRAMES_SLOT) + { + rv.set(value); + } } fn replaceable_frames_setter<'s>( @@ -892,6 +906,10 @@ fn window_surface_replaceable_setter<'s>( args: v8::FunctionCallbackArguments<'s>, _rv: v8::ReturnValue<'s, v8::Value>, ) { + if !super::is_window_receiver(scope, args.this()) { + throw_type_error(scope, "Window setter called on incompatible receiver."); + return; + } let Some(name) = callback_data_item( scope, &args, @@ -910,6 +928,10 @@ fn window_name_runtime_getter<'s>( mut rv: v8::ReturnValue<'s, v8::Value>, ) { let receiver = callback_this_object(scope, &args); + if !super::is_window_receiver(scope, receiver) { + throw_type_error(scope, "Window.name getter called on incompatible receiver."); + return; + } let value = object_hidden_value(scope, receiver, WINDOW_NAME_SLOT) .unwrap_or_else(|| v8::String::empty(scope).into()); rv.set(value); @@ -921,6 +943,10 @@ fn window_name_runtime_setter<'s>( _rv: v8::ReturnValue<'s, v8::Value>, ) { let receiver = callback_this_object(scope, &args); + if !super::is_window_receiver(scope, receiver) { + throw_type_error(scope, "Window.name setter called on incompatible receiver."); + return; + } let next = args .get(0) .to_string(scope) @@ -934,6 +960,44 @@ fn window_name_runtime_setter<'s>( define_non_enumerable_string_property(scope, receiver, WINDOW_NAME_SLOT, &next); } +fn window_status_runtime_getter<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'s, v8::Value>, +) { + let receiver = callback_this_object(scope, &args); + if !super::is_window_receiver(scope, receiver) { + throw_type_error( + scope, + "Window.status getter called on incompatible receiver.", + ); + return; + } + rv.set( + get_private_value(scope, receiver, WINDOW_STATUS_RUNTIME_SLOT) + .unwrap_or_else(|| v8::String::empty(scope).into()), + ); +} + +fn window_status_runtime_setter<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s, v8::Value>, +) { + let receiver = callback_this_object(scope, &args); + if !super::is_window_receiver(scope, receiver) { + throw_type_error( + scope, + "Window.status setter called on incompatible receiver.", + ); + return; + } + let Some(next) = args.get(0).to_string(scope) else { + return; + }; + set_private_value(scope, receiver, WINDOW_STATUS_RUNTIME_SLOT, next.into()); +} + fn install_public_window_surface_accessors<'s>( scope: &mut v8::PinScope<'s, '_>, global: v8::Local<'s, v8::Object>, @@ -1058,6 +1122,7 @@ fn install_public_window_surface_accessors<'s>( session_storage: (), indexed_db: (), name: (), + status: (), } .initialize(scope, global)?; WindowAdditionalReplaceableAccessorsDeclaration { diff --git a/moli-renderer-v8/src/context_bootstrap/window_receiver.rs b/moli-renderer-v8/src/context_bootstrap/window_receiver.rs index b619477b1e..57cb93f02b 100644 --- a/moli-renderer-v8/src/context_bootstrap/window_receiver.rs +++ b/moli-renderer-v8/src/context_bootstrap/window_receiver.rs @@ -1,9 +1,11 @@ use super::shared::WINDOW_NAVIGATOR_SLOT; use crate::util::{ callback_data_index_value, context_host_ptr_from_context_slot, - context_host_ptr_from_window_object, get_private_value, throw_type_error, + context_host_ptr_from_window_object, get_private_value, set_private_value, throw_type_error, }; +const WINDOW_BRAND_SLOT: &str = "__moliWindowBrand"; + /// Recognizes a native Window receiver for WebIDL brand checks. /// /// A retained, detached global proxy can lose both its creation context and @@ -15,7 +17,9 @@ pub(crate) fn is_window_receiver<'s>( scope: &mut v8::PinScope<'s, '_>, receiver: v8::Local<'s, v8::Object>, ) -> bool { - if is_live_window_receiver(scope, receiver) { + if is_live_window_receiver(scope, receiver) + || get_private_value(scope, receiver, WINDOW_BRAND_SLOT).is_some() + { return true; } let current_context = scope.get_current_context(); @@ -23,6 +27,16 @@ pub(crate) fn is_window_receiver<'s>( && context_host_ptr_from_context_slot(current_context).is_some() } +/// Marks a Window-shaped native object that intentionally has no execution +/// context, such as an iframe owned by a DOMParser-created Document. +pub(crate) fn mark_window_receiver( + scope: &mut v8::PinScope<'_, '_>, + receiver: v8::Local<'_, v8::Object>, +) { + let branded = v8::Boolean::new(scope, true); + set_private_value(scope, receiver, WINDOW_BRAND_SLOT, branded.into()); +} + /// Recognizes a Window whose V8 object still exposes its live realm markers. /// /// This is deliberately narrower than the WebIDL brand check above. Bound diff --git a/moli-renderer-v8/src/context_bootstrap/window_runtime.rs b/moli-renderer-v8/src/context_bootstrap/window_runtime.rs index 54d95624d6..f47456b4b4 100644 --- a/moli-renderer-v8/src/context_bootstrap/window_runtime.rs +++ b/moli-renderer-v8/src/context_bootstrap/window_runtime.rs @@ -14,7 +14,8 @@ pub(super) use base64::{window_atob_callback, window_btoa_callback}; pub(super) use dialogs::entered_window_api_base_url; pub(super) use dialogs::{window_alert_callback, window_confirm_callback, window_prompt_callback}; pub(crate) use dialogs::{ - window_const_false_callback, window_noop_callback, window_open_callback, window_stop_callback, + window_blur_callback, window_const_false_callback, window_focus_callback, window_noop_callback, + window_open_callback, window_stop_callback, }; pub(crate) use navigator::{ LegacyStorageQuotaCallbackOutcome, LegacyStorageQuotaCallbackTask, @@ -68,7 +69,7 @@ struct ChildWindowOwnMethodsDeclaration { open: (), #[webapi(method, length = 0, callback = window_noop_callback)] close: (), - #[webapi(method, length = 0, callback = window_noop_callback)] + #[webapi(method, length = 0, callback = window_blur_callback)] blur: (), #[webapi(method, length = 0, callback = window_const_false_callback)] find: (), diff --git a/moli-renderer-v8/src/context_bootstrap/window_runtime/dialogs.rs b/moli-renderer-v8/src/context_bootstrap/window_runtime/dialogs.rs index 1796538c16..9627e920c7 100644 --- a/moli-renderer-v8/src/context_bootstrap/window_runtime/dialogs.rs +++ b/moli-renderer-v8/src/context_bootstrap/window_runtime/dialogs.rs @@ -68,6 +68,39 @@ pub(crate) fn window_noop_callback( ) { } +pub(crate) fn window_focus_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'_, v8::Value>, +) { + let receiver = args.this(); + if !crate::context_bootstrap::is_window_receiver(scope, receiver) { + webidl::throw_type_error(scope, "Window.focus called on incompatible receiver."); + return; + } + let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else { + return; + }; + if let Some(crate::native_bridge::OwnerDispatchScope::Child(handle)) = + super::super::navigation_window::runtime_window_dispatch_scope(scope, receiver) + { + crate::native_bridge::element::focus_element(scope, host_ptr, handle); + } + rv.set_undefined(); +} + +pub(crate) fn window_blur_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'_, v8::Value>, +) { + if !crate::context_bootstrap::is_window_receiver(scope, args.this()) { + webidl::throw_type_error(scope, "Window.blur called on incompatible receiver."); + return; + } + rv.set_undefined(); +} + pub(crate) fn window_stop_callback<'s>( scope: &mut v8::PinScope<'s, '_>, args: v8::FunctionCallbackArguments<'s>, diff --git a/moli-renderer-v8/src/context_bootstrap/window_template.rs b/moli-renderer-v8/src/context_bootstrap/window_template.rs index 9c909d00a8..e8b0648de3 100644 --- a/moli-renderer-v8/src/context_bootstrap/window_template.rs +++ b/moli-renderer-v8/src/context_bootstrap/window_template.rs @@ -72,10 +72,10 @@ struct WindowEarlyTemplateMethodsDeclaration { #[webapi(method, length = 0, callback = window_noop_callback)] close: (), - #[webapi(method, length = 0, callback = window_noop_callback)] + #[webapi(method, length = 0, callback = window_focus_callback)] focus: (), - #[webapi(method, length = 0, callback = window_noop_callback)] + #[webapi(method, length = 0, callback = window_blur_callback)] blur: (), #[webapi(method, length = 0, callback = window_const_false_callback)] diff --git a/moli-renderer-v8/src/native_bridge/context_host/child_frame_runtime/window.rs b/moli-renderer-v8/src/native_bridge/context_host/child_frame_runtime/window.rs index fb8a4ea71c..4cdb9e7d5a 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/child_frame_runtime/window.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/child_frame_runtime/window.rs @@ -606,20 +606,13 @@ unsafe extern "C" fn window_access_check_callback( return true; } - let Some((accessing_host_ptr, accessing_identity)) = (|| { - let host_ptr = crate::util::context_host_ptr_from_context_slot(accessing_context)?; - let identity = unsafe { &*host_ptr } - .window_execution_context_identity_for_access_check(accessing_context)?; - Some((host_ptr, identity)) - })() else { + let Some(accessing_host_ptr) = + crate::util::context_host_ptr_from_context_slot(accessing_context) + else { return false; }; - let Some((accessed_host_ptr, accessed_identity)) = (|| { - let host_ptr = crate::util::context_host_ptr_from_context_slot(accessed_context)?; - let identity = unsafe { &*host_ptr } - .window_execution_context_identity_for_access_check(accessed_context)?; - Some((host_ptr, identity)) - })() else { + let Some(accessed_host_ptr) = crate::util::context_host_ptr_from_context_slot(accessed_context) + else { return false; }; if accessing_host_ptr != accessed_host_ptr { @@ -627,7 +620,34 @@ unsafe extern "C" fn window_access_check_callback( } let host = unsafe { &*accessing_host_ptr }; - host.window_execution_context_can_access(accessing_identity, accessed_identity) + let Some(accessing_identity) = + host.window_execution_context_identity_for_access_check(accessing_context) + else { + return false; + }; + if let Some(accessed_identity) = + host.window_execution_context_identity_for_access_check(accessed_context) + && host.window_execution_context_can_access(accessing_identity, accessed_identity) + { + return true; + } + if !host.window_execution_context_identity_is_current(accessing_identity) + || accessed_context + .get_slot::() + .is_none() + { + return false; + } + + // Removing a same-origin iframe retires its LocalWindow registration, but + // JavaScript can still retain that inner global long enough to clean up + // listeners and other realm-owned state. The context's internalized + // security token is the last live effective-origin snapshot, so equal + // tokens preserve that access without reopening retired cross-origin or + // opaque realms. + accessing_context + .get_security_token(scope) + .strict_equals(accessed_context.get_security_token(scope)) } impl JsContextHost { diff --git a/moli-renderer-v8/src/native_bridge/document/detached_install/accessors/iframe_window.rs b/moli-renderer-v8/src/native_bridge/document/detached_install/accessors/iframe_window.rs index 27de69593f..ebdf83da54 100644 --- a/moli-renderer-v8/src/native_bridge/document/detached_install/accessors/iframe_window.rs +++ b/moli-renderer-v8/src/native_bridge/document/detached_install/accessors/iframe_window.rs @@ -45,6 +45,7 @@ pub(super) fn build_detached_iframe_content_window<'s>( let window = DetachedIframeWindowDeclaration::new(parent, top, document) .bind(scope) .ok()?; + crate::context_bootstrap::mark_window_receiver(scope, window); set_document_associated_window(scope, document, window); crate::network_host::install_fetch_constructors_for_base_url(scope, window, base_url); install_detached_iframe_window_messaging(scope, window); diff --git a/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface.rs b/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface.rs index 011bae5b1a..4b4bc466e5 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface.rs @@ -14493,7 +14493,7 @@ __domainAccessFrame.src = globalThis.__replacementDomainChildUrl; async fn one_sided_document_domain_disables_original_tuple_origin_fast_path() { const HOST: &str = "www.example.test"; - let server = StaticHttpServer::spawn(1).await; + let server = StaticHttpServer::spawn(2).await; let parent_url = server.url_for_host(HOST, "/page.html"); let child_url = server.url_for_host(HOST, "/child.html"); let loader = static_http_loader([server.resolve_entry(HOST)]); @@ -14573,7 +14573,52 @@ globalThis.__probeOneSidedDomainFrame = () => { .expect("restored exact-domain access probe should evaluate"), "www.example.test" ); - assert_eq!(server.finish_targets().await, vec!["/child.html"]); + + vm.exec( + r#" +const retiredFrame = document.createElement("iframe"); +globalThis.__retiredOneSidedDomainChildLoaded = false; +retiredFrame.onload = () => { globalThis.__retiredOneSidedDomainChildLoaded = true; }; +retiredFrame.src = globalThis.__oneSidedDomainChildUrl; +(document.body || document.documentElement || document).appendChild(retiredFrame); +globalThis.__retiredOneSidedDomainWindow = retiredFrame.contentWindow; +"#, + None, + ) + .expect("one-sided document.domain retired Window setup should run"); + advance_page_task_executor_until_eval_equals( + &mut vm, + &loader, + "String(globalThis.__retiredOneSidedDomainChildLoaded)", + "true", + "fresh child without document.domain should load", + ) + .await; + + assert_eq!( + vm.eval( + r#" +(() => { + const probe = () => { + try { + return __retiredOneSidedDomainWindow.document.domain; + } catch (error) { + return error && error.name; + } + }; + const beforeRemoval = probe(); + document.querySelectorAll("iframe")[1].remove(); + return [beforeRemoval, probe()].join("|"); +})() +"#, + ) + .expect("retired one-sided document.domain Window probe should evaluate"), + "SecurityError|SecurityError" + ); + assert_eq!( + server.finish_targets().await, + vec!["/child.html", "/child.html"] + ); } #[test] diff --git a/moli-renderer-v8/src/script_vm/tests/dom_xhr/misc.rs b/moli-renderer-v8/src/script_vm/tests/dom_xhr/misc.rs index 220f3b8738..a2c11f7e47 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_xhr/misc.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_xhr/misc.rs @@ -371,6 +371,126 @@ fn window_global_accessors_use_the_borrowed_window_receiver() { ); } +#[test] +fn cross_realm_window_members_apply_global_interface_receiver_semantics() { + let mut vm = new_storage_test_vm("https://window-global-receiver.test/"); + + vm.eval( + r#" +(() => { + const root = document.documentElement || + document.appendChild(document.createElement("html")); + const body = document.body || root.appendChild(document.createElement("body")); + const frame = document.createElement("iframe"); + frame.id = "global-receiver-frame"; + frame.name = "dummy"; + body.appendChild(frame); +})() +"#, + ) + .expect("cross-realm Window receiver child frame should be created"); + materialize_single_child_default_realm_for_test( + &mut vm, + "cross-realm Window receiver child Realm", + ); + + let result = vm + .eval( + r#" +(() => { + const frame = document.getElementById("global-receiver-frame"); + const other = frame.contentWindow; + const notWindow = Object.create(Object.getPrototypeOf(other)); + const throwsOtherTypeError = callback => { + try { + callback(); + return false; + } catch (error) { + return error instanceof other.TypeError && !(error instanceof TypeError); + } + }; + + const invalidGetters = [ + () => { Object.create(other).window; }, + () => Object.getOwnPropertyDescriptor(other, "history").get.call(notWindow), + () => Reflect.get(other, "screen", notWindow), + () => new Proxy(other, {}).onclick + ].every(throwsOtherTypeError); + + const invalidSetters = [ + () => { Object.create(other).name = "ignored"; }, + () => Object.getOwnPropertyDescriptor(other, "status").set.call(notWindow, "ignored"), + () => Reflect.set(other, "parent", window, notWindow), + () => { new Proxy(other, {}).location = location; } + ].every(throwsOtherTypeError); + + const invalidOperations = [ + () => Object.create(other).focus(), + () => other.clearInterval.call(notWindow, 0), + () => Reflect.apply(other.blur, notWindow, []), + () => new Proxy(other, {}).removeEventListener("foo", () => {}) + ].every(throwsOtherTypeError); + + const nameGetter = Object.getOwnPropertyDescriptor(other, "name").get; + const statusDescriptor = Object.getOwnPropertyDescriptor(other, "status"); + const nullGetters = + Reflect.get(other, "self", null) === other && + Reflect.get(other, "document", undefined) === other.document && + nameGetter.call(null) === "dummy"; + + const newSelf = {}; + const nullSetters = + Reflect.set(other, "self", newSelf, null) && + Reflect.set(other, "name", "newName", undefined); + statusDescriptor.set.call(null, "ready"); + + const extractedFocus = other.focus; + extractedFocus(); + + let caughtEvent; + const eventListener = event => { + caughtEvent = event; + }; + other.addEventListener.call(null, "global-receiver", eventListener); + const dispatchedEvent = new other.Event("global-receiver"); + const extractedDispatch = other.dispatchEvent; + const dispatchResult = extractedDispatch(dispatchedEvent); + const focusedChild = document.activeElement === frame; + frame.remove(); + let detachedCleanup = true; + try { + other.removeEventListener("global-receiver", eventListener); + } catch (error) { + detachedCleanup = false; + } + + return [ + invalidGetters, + invalidSetters, + invalidOperations, + nullGetters, + nullSetters, + other.self === newSelf, + other.name === "newName", + statusDescriptor.get.call(null) === "ready", + focusedChild, + dispatchResult, + caughtEvent === dispatchedEvent, + detachedCleanup + ].join("|"); +})() +"#, + ) + .expect("cross-realm Window receiver semantics should evaluate"); + + assert_eq!( + result, + std::iter::repeat_n("true", 12) + .collect::>() + .join("|") + ); +} + #[test] fn child_document_body_with_scope_exposes_insert_before() { let mut vm = new_storage_test_vm("https://child-window-with-body.test/"); diff --git a/moli-renderer-v8/src/window_host.rs b/moli-renderer-v8/src/window_host.rs index 0aca892206..1119b1e348 100644 --- a/moli-renderer-v8/src/window_host.rs +++ b/moli-renderer-v8/src/window_host.rs @@ -416,6 +416,9 @@ pub(super) fn event_target_remove_event_listener_callback<'s>( event_target_handle_from_this(scope, &args, host_ptr, host) }; let Some(target) = target else { + if crate::context_bootstrap::is_window_receiver(scope, args.this()) { + return; + } throw_type_error(scope, "Illegal invocation"); return; }; @@ -746,6 +749,10 @@ pub(super) fn window_clear_timer_callback<'s>( args: v8::FunctionCallbackArguments<'s>, _rv: v8::ReturnValue<'_, v8::Value>, ) { + if !crate::context_bootstrap::is_window_receiver(scope, args.this()) { + throw_type_error(scope, "Illegal invocation"); + return; + } let id_val = args.get(0); let id = id_val.number_value(scope).unwrap_or(0.0) as u32; cancel_window_timer_for_receiver(scope, args.this(), id);