fix(dom): resolve foreign reflectors in their owning page

Validation on this branch: cargo fmt --all; cargo clippy --workspace --all-targets --all-features -- -D warnings.
Nextest and WPT reruns omitted at the user's request.

Includes the ablation-confirmed cleanup from c1fe3ea895.
This commit is contained in:
ldm0
2026-10-10 03:34:22 +08:00
committed by Donough Liu
parent 6f2a5b1505
commit 78f43c201b
4 changed files with 50 additions and 2 deletions
@@ -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(
@@ -49,6 +49,20 @@ impl NativeDomBridge {
host_ptr: *mut JsContextHost,
handle: BridgeHandle,
) -> Option<v8::Local<'s, v8::Object>> {
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);
@@ -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 }
@@ -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 {