diff --git a/moli-renderer-v8/src/callback_invocation.rs b/moli-renderer-v8/src/callback_invocation.rs index 3e3bfbc427..8ef688f301 100644 --- a/moli-renderer-v8/src/callback_invocation.rs +++ b/moli-renderer-v8/src/callback_invocation.rs @@ -210,7 +210,13 @@ impl CallbackInvoker { } if let Some(host_ptr) = invocation.host_ptr { let host = unsafe { &*host_ptr }; - let is_retired = match invocation.relevant_identity { + // Popup dispatch uses scoped registrations in a shared V8 + // context. A callback from another live Window can lack that + // scoped identity while its own concrete realm is still current. + let relevant_identity = invocation.relevant_identity.or_else(|| { + host.window_execution_context_identity_for_access_check(invocation.relevant_context) + }); + let is_retired = match relevant_identity { Some(identity) => !host.window_execution_context_identity_is_current(identity), // A retained Window can keep its V8 global attached after its // registry entry is removed. Missing authority is not an diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/misc/extracted/popup_window.rs b/moli-renderer-v8/src/script_vm/tests/browser_api/misc/extracted/popup_window.rs index 2ffe05e8a8..a7285c5c49 100644 --- a/moli-renderer-v8/src/script_vm/tests/browser_api/misc/extracted/popup_window.rs +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/misc/extracted/popup_window.rs @@ -1105,6 +1105,88 @@ async fn window_open_non_about_returns_lightweight_popup_and_dispatches_load() { ); } #[tokio::test] +async fn lightweight_popup_load_keeps_live_callback_realms_and_rejects_retired_ones() { + let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).expect("loader"); + let mut vm = new_storage_page_task_executor_test_vm_with_loader( + "https://popup-callback-realm.test/page.html", + &loader, + ); + + vm.eval( + r#" +globalThis.__popupCallbackRealmEvents = []; +const makeFrame = () => { + const frame = document.createElement("iframe"); + (document.body || document.documentElement || document).appendChild(frame); + return frame; +}; +globalThis.__popupSourceWindow = makeFrame().contentWindow; +globalThis.__popupLiveCallbackWindow = makeFrame().contentWindow; +globalThis.__popupRetiredCallbackFrame = makeFrame(); +globalThis.__popupRetiredCallbackWindow = __popupRetiredCallbackFrame.contentWindow; +globalThis.__popupRetiredCallback = __popupRetiredCallbackWindow.Function( + "parent.__popupCallbackRealmEvents.push('retired');" +); +__popupRetiredCallbackFrame.remove(); +"removed" +"#, + ) + .expect("popup callback realms should be created before retirement"); + vm.drain_ready_page_task_executor_turns_for_setup(&loader, 128) + .await + .expect("removed callback Window should retire through Page tasks"); + + assert_eq!( + vm.eval( + r#" +(document.body || document.documentElement || document).appendChild(__popupRetiredCallbackFrame); +String(__popupRetiredCallbackWindow !== __popupRetiredCallbackFrame.contentWindow) +"#, + ) + .expect("reinserted iframe should expose a new Window"), + "true" + ); + + vm.eval( + r#" +const popupURL = URL.createObjectURL(new Blob([ + "