diff --git a/moli-renderer-v8/src/context_bootstrap/event_worlds.rs b/moli-renderer-v8/src/context_bootstrap/event_worlds.rs index 29eb005811..de5af61149 100644 --- a/moli-renderer-v8/src/context_bootstrap/event_worlds.rs +++ b/moli-renderer-v8/src/context_bootstrap/event_worlds.rs @@ -5,6 +5,23 @@ use super::{events, platform_object_worlds, shared_event_targets, world_wrappers}; use crate::native_bridge::identity::contexts_share_wrapper_world; +fn construction_context_in_world<'s>( + scope: &mut v8::PinScope<'s, '_>, + event: v8::Local<'s, v8::Object>, + world: v8::Local<'s, v8::Context>, +) -> Option> { + let creation = event.get_creation_context(scope)?; + if contexts_share_wrapper_world(creation, world) { + return Some(creation); + } + let host_ptr = crate::util::context_host_ptr_from_context_slot(creation)?; + let host = unsafe { &*host_ptr }; + let identity = host.window_execution_context_identity_for_access_check(creation)?; + let (_, default) = + host.window_execution_context(scope, identity.owner(), identity.dispatch_scope())?; + contexts_share_wrapper_world(default, world).then_some(default) +} + pub(crate) fn target_in_realm<'s>( scope: &mut v8::PinScope<'s, '_>, target: v8::Local<'s, v8::Object>, @@ -29,15 +46,19 @@ pub(crate) fn event_in_realm<'s>( } let backing = events::event_backing(scope, event); let target = target_in_realm(scope, target, context)?; - // Preserve owner identity within a wrapper world. A foreign Window without - // a local view must not move the Event into that Window's different world. - let context = target - .get_creation_context(scope) - .filter(|creation| contexts_share_wrapper_world(*creation, context)) - .unwrap_or(context); if let Some(wrapper) = world_wrappers::get(scope, backing, context) { return Some(wrapper); } + // Project into the construction Window's counterpart when it belongs to + // the listener's world. Adoption retains the target's prototype realm, + // which cannot determine an independently constructed Event's realm. + let context = construction_context_in_world(scope, backing, context) + .or_else(|| { + target + .get_creation_context(scope) + .filter(|creation| contexts_share_wrapper_world(*creation, context)) + }) + .unwrap_or(context); let scope = &mut v8::ContextScope::new(scope, context); events::new_event_wrapper(scope, backing) } diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/dom_event_worlds.rs b/moli-renderer-v8/src/script_vm/tests/browser_api/dom_event_worlds.rs index bb8b2c8534..9bb16420b2 100644 --- a/moli-renderer-v8/src/script_vm/tests/browser_api/dom_event_worlds.rs +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/dom_event_worlds.rs @@ -2,6 +2,64 @@ use super::*; const WORLD_PROBE: &str = include_str!("dom_event_worlds.js"); +#[tokio::test] +async fn isolated_event_default_view_uses_its_construction_window() { + let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).unwrap(); + let mut vm = + new_storage_page_task_executor_test_vm_with_loader("https://event-worlds.test/", &loader); + vm.eval( + "document.body.innerHTML = ''; \ + const frame = document.querySelector('iframe'); \ + frame.srcdoc = ''; void frame.contentWindow; 'ready'", + ) + .unwrap(); + vm.run_one_child_frame_task_executor_turn( + ChildFrameSemanticTurnKind::RealmMaterialization, + &loader, + ) + .await + .unwrap(); + vm.drain_ready_page_task_executor_turns_for_setup(&loader, 128) + .await + .unwrap(); + let child = vm + .live_child_default_runtime_realm_inventory() + .into_iter() + .next() + .unwrap() + .context_id; + vm.eval_in_child_default_context( + child, + r#" + document.getElementById('target').addEventListener('construction', function(event) { + globalThis.constructionFacts = [ + event instanceof parent.Event, !(event instanceof Event), + event.createdIn === undefined, event.target === this, + event.currentTarget === this, this === document.getElementById('target') + ]; + }); 'ready' + "#, + ) + .unwrap(); + let isolated = vm + .create_isolated_world("construction-world", false) + .unwrap(); + vm.eval_in_isolated_context( + isolated, + r#" + const event = new Event('construction'); event.createdIn = 'isolated'; + document.querySelector('iframe').contentDocument.getElementById('target').dispatchEvent(event); + 'dispatched' + "#, + ) + .unwrap(); + assert_eq!( + vm.eval_in_child_default_context(child, "JSON.stringify(constructionFacts)") + .unwrap(), + "[true,true,true,true,true,true]" + ); +} + fn assert_world_probe(result: &str, label: &str) { let facts: serde_json::Value = serde_json::from_str(result).unwrap(); assert_eq!(facts["errors"], serde_json::json!([]), "{label}: {facts}");