fix(events): preserve construction realms across wrapper worlds

This commit is contained in:
ldm0
2026-10-11 18:06:49 +08:00
parent c43b91ed09
commit e9ee19d638
2 changed files with 85 additions and 6 deletions
@@ -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<v8::Local<'s, v8::Context>> {
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)
}
@@ -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 = '<iframe></iframe>'; \
const frame = document.querySelector('iframe'); \
frame.srcdoc = '<button id=target></button>'; 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}");