diff --git a/moli-protocol-server/src/protocol_server/tests/cdp_dynamic_page/auxiliary_page_identity.rs b/moli-protocol-server/src/protocol_server/tests/cdp_dynamic_page/auxiliary_page_identity.rs index e8af315407..d6c2d510d6 100644 --- a/moli-protocol-server/src/protocol_server/tests/cdp_dynamic_page/auxiliary_page_identity.rs +++ b/moli-protocol-server/src/protocol_server/tests/cdp_dynamic_page/auxiliary_page_identity.rs @@ -999,6 +999,29 @@ async fn auxiliary_cross_origin_functions_and_descriptors_use_each_accessing_rea abort_test_cdp_server(server).await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn auxiliary_page_adoption_preserves_native_node_and_owner_document_identity() { + let (addr, server) = spawn_test_protocol_server().await; + let (mut browser, _) = + connect_async(format!("ws://{addr}/devtools/browser/{DEFAULT_BROWSER_ID}")) + .await + .unwrap(); + let opener_id = create_dynamic_target(&mut browser, 1).await; + let mut opener = connect_dynamic_page(addr, &opener_id).await; + let (_, _child) = open_auxiliary(addr, &mut opener, "").await; + assert_eq!(evaluate_window_name_probe(&mut opener, 2, r#"(()=>{ + window.div=document.createElement('div'); + window.span=document.createElement('span');div.append(span); + p.document.body.append(div); + return [div.ownerDocument===p.document,span.ownerDocument===p.document,p.document.body.firstChild===div,div.ownerDocument===document,div.ownerDocument.defaultView===p,p.document.defaultView===p]; + })()"#).await, json!([true,true,true,false,true,true])); + assert_eq!(evaluate_window_name_probe(&mut opener, 3, r#"(()=>{ + document.body.append(div); + return [div.ownerDocument===document,span.ownerDocument===document,document.body.lastChild===div]; + })()"#).await, json!([true,true,true])); + abort_test_cdp_server(server).await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn initial_same_origin_popup_commit_preserves_window_and_replaces_document() { let (fixture_addr, _fixture) = spawn_dedicated_fixture_server( diff --git a/moli-renderer-v8/src/native_bridge/bridge/wrappers.rs b/moli-renderer-v8/src/native_bridge/bridge/wrappers.rs index e58a220328..3500ed64cb 100644 --- a/moli-renderer-v8/src/native_bridge/bridge/wrappers.rs +++ b/moli-renderer-v8/src/native_bridge/bridge/wrappers.rs @@ -49,6 +49,20 @@ impl NativeDomBridge { host_ptr: *mut JsContextHost, handle: BridgeHandle, ) -> Option> { + if crate::util::context_host_ptr_from_context_slot(scope.get_current_context()) + .is_some_and(|current| current != host_ptr) + { + // Reflector IDs are local to a DOM bridge. Related Pages share + // an isolate, but must never consult one another's wrapper cache. + let host = unsafe { &*host_ptr }; + let dispatch_scope = crate::native_bridge::OwnerDispatchScope::Top; + let owner = host.current_window_execution_context_owner(dispatch_scope)?; + let context = host + .window_execution_context(scope, owner, dispatch_scope)? + .1; + let scope = &mut v8::ContextScope::new(scope, context); + return self.wrap_bridge_handle(scope, host_ptr, handle); + } let reflector_id = self.identity.reflector_id(&handle); if let Some(wrapper) = self.identity.cached_wrapper(scope, reflector_id) { return Some(wrapper); diff --git a/moli-renderer-v8/src/native_bridge/helpers/identity.rs b/moli-renderer-v8/src/native_bridge/helpers/identity.rs index 1f160120b8..cdbb01ac50 100644 --- a/moli-renderer-v8/src/native_bridge/helpers/identity.rs +++ b/moli-renderer-v8/src/native_bridge/helpers/identity.rs @@ -1,5 +1,5 @@ use super::super::{BridgeHandle, JsContextHost, ReflectorId}; -use crate::util::context_host_ptr_from_global_bridge; +use crate::util::{context_host_ptr_from_context_slot, context_host_ptr_from_global_bridge}; use anyhow::{Context, bail, ensure}; fn object_reflector_id( @@ -32,7 +32,12 @@ pub(in crate::native_bridge) fn bridge_handle_from_object( scope: &mut v8::PinScope<'_, '_>, object: v8::Local<'_, v8::Object>, ) -> anyhow::Result<(*mut JsContextHost, BridgeHandle)> { - let runtime_ptr = context_host_ptr_from_global_bridge(scope) + // Related Pages share an isolate, but reflector IDs belong to the + // wrapper's original DOM bridge rather than the calling Window's bridge. + let runtime_ptr = object + .get_creation_context(scope) + .and_then(context_host_ptr_from_context_slot) + .or_else(|| context_host_ptr_from_global_bridge(scope)) .context("current context has no JsContextHost")?; let reflector_id = object_reflector_id(scope, object)?; let handle = unsafe { &*runtime_ptr } diff --git a/moli-renderer-v8/src/native_bridge/node.rs b/moli-renderer-v8/src/native_bridge/node.rs index fe21bfc4f6..195f07ccbb 100644 --- a/moli-renderer-v8/src/native_bridge/node.rs +++ b/moli-renderer-v8/src/native_bridge/node.rs @@ -1216,6 +1216,12 @@ pub(crate) fn node_runtime_and_handle_from_object( scope: &mut v8::PinScope<'_, '_>, object: v8::Local<'_, v8::Object>, ) -> anyhow::Result<(*mut JsContextHost, DomHandle)> { + let object = v8::Local::new(scope, object); + if let Some(delegate) = + crate::util::get_private_object(scope, object, super::document::DETACHED_LIVE_DELEGATE_SLOT) + { + return node_runtime_and_handle_from_object(scope, delegate); + } let (runtime_ptr, handle) = super::bridge_handle_from_object(scope, object) .context("failed to resolve Node wrapper")?; match handle {