diff --git a/moli-renderer-v8/src/context_bootstrap/navigation_window.rs b/moli-renderer-v8/src/context_bootstrap/navigation_window.rs index 250f1b572c..707e686053 100644 --- a/moli-renderer-v8/src/context_bootstrap/navigation_window.rs +++ b/moli-renderer-v8/src/context_bootstrap/navigation_window.rs @@ -106,7 +106,7 @@ pub(in crate::context_bootstrap) fn child_browsing_context_handle_for_runtime_ow }) } -pub(super) fn runtime_window_dispatch_scope<'s>( +pub(in crate::context_bootstrap) fn runtime_window_dispatch_scope<'s>( scope: &mut v8::PinScope<'s, '_>, window: v8::Local<'s, v8::Object>, ) -> Option { diff --git a/moli-renderer-v8/src/context_bootstrap/runtime_state.rs b/moli-renderer-v8/src/context_bootstrap/runtime_state.rs index dd8ec8d090..ab4fd1bb8a 100644 --- a/moli-renderer-v8/src/context_bootstrap/runtime_state.rs +++ b/moli-renderer-v8/src/context_bootstrap/runtime_state.rs @@ -663,9 +663,23 @@ fn window_length_replaceable_getter<'s>( return; }; let host = unsafe { &mut *host_ptr }; - let count = child_context_handle_from_owner(scope, args.this()) - .map(|handle| host.child_browsing_context_child_frame_count(handle)) - .unwrap_or_else(|| host.child_browsing_context_count()); + let document = match super::navigation_window::runtime_window_dispatch_scope(scope, args.this()) + { + Some(crate::native_bridge::OwnerDispatchScope::Top) => Some(host.document_handle()), + Some(crate::native_bridge::OwnerDispatchScope::Child(handle)) => { + host.child_browsing_context_document_handle(handle) + } + Some(crate::native_bridge::OwnerDispatchScope::LightweightPopup(popup_id)) => { + host.lightweight_popup_document_handle(popup_id) + } + None => None, + }; + let count = document + .map(|document| { + host.sync_child_browsing_context_subtree(scope, document); + host.child_browsing_context_count_for_document(document) + }) + .unwrap_or(0); rv.set(v8::Number::new(scope, count as f64).into()); } diff --git a/moli-renderer-v8/src/context_bootstrap/window_accessors/child_context.rs b/moli-renderer-v8/src/context_bootstrap/window_accessors/child_context.rs index eb71616a53..81f70afec6 100644 --- a/moli-renderer-v8/src/context_bootstrap/window_accessors/child_context.rs +++ b/moli-renderer-v8/src/context_bootstrap/window_accessors/child_context.rs @@ -1,5 +1,6 @@ use super::helpers::{ - window_child_context_handle, window_hidden_value, window_host_ptr, window_receiver, + window_child_context_handle, window_document_handle, window_hidden_value, window_host_ptr, + window_receiver, }; use super::*; @@ -16,11 +17,12 @@ pub(in crate::context_bootstrap) fn window_length_getter<'s>( return; }; let runtime = unsafe { &mut *host_ptr }; - let count = if let Some(handle) = window_child_context_handle(scope, receiver) { - runtime.child_browsing_context_child_frame_count(handle) - } else { - runtime.child_browsing_context_count() - }; + let count = window_document_handle(scope, receiver, runtime) + .map(|document| { + runtime.sync_child_browsing_context_subtree(scope, document); + runtime.child_browsing_context_count_for_document(document) + }) + .unwrap_or(0); rv.set(v8::Number::new(scope, count as f64).into()); } diff --git a/moli-renderer-v8/src/context_bootstrap/window_accessors/helpers.rs b/moli-renderer-v8/src/context_bootstrap/window_accessors/helpers.rs index 19860fa70a..7238c3eac7 100644 --- a/moli-renderer-v8/src/context_bootstrap/window_accessors/helpers.rs +++ b/moli-renderer-v8/src/context_bootstrap/window_accessors/helpers.rs @@ -92,6 +92,33 @@ pub(in crate::context_bootstrap) fn window_child_context_handle<'s>( None } +pub(super) fn window_owner_dispatch_scope<'s>( + scope: &mut v8::PinScope<'s, '_>, + receiver: v8::Local<'s, v8::Object>, +) -> Option { + super::super::navigation_window::runtime_window_dispatch_scope(scope, receiver).or_else(|| { + receiver + .strict_equals(scope.get_current_context().global(scope).into()) + .then_some(crate::native_bridge::OwnerDispatchScope::Top) + }) +} + +pub(super) fn window_document_handle<'s>( + scope: &mut v8::PinScope<'s, '_>, + receiver: v8::Local<'s, v8::Object>, + host: &JsContextHost, +) -> Option { + match window_owner_dispatch_scope(scope, receiver)? { + crate::native_bridge::OwnerDispatchScope::Top => Some(host.document_handle()), + crate::native_bridge::OwnerDispatchScope::Child(handle) => { + host.child_browsing_context_document_handle(handle) + } + crate::native_bridge::OwnerDispatchScope::LightweightPopup(popup_id) => { + host.lightweight_popup_document_handle(popup_id) + } + } +} + pub(crate) fn window_host_ptr( scope: &mut v8::PinScope<'_, '_>, receiver: v8::Local<'_, v8::Object>, diff --git a/moli-renderer-v8/src/context_bootstrap/window_accessors/interceptors.rs b/moli-renderer-v8/src/context_bootstrap/window_accessors/interceptors.rs index e4311f9fed..084dd5bd72 100644 --- a/moli-renderer-v8/src/context_bootstrap/window_accessors/interceptors.rs +++ b/moli-renderer-v8/src/context_bootstrap/window_accessors/interceptors.rs @@ -1,4 +1,7 @@ -use super::helpers::{window_child_context_handle, window_host_ptr}; +use super::helpers::{ + window_child_context_handle, window_document_handle, window_host_ptr, + window_owner_dispatch_scope, +}; use crate::native_bridge::named_access::{ build_window_named_items_collection, window_named_item_handles, }; @@ -11,24 +14,34 @@ fn is_reserved_window_name(name: &str) -> bool { fn window_indexed_child_handle<'s>( scope: &mut v8::PinScope<'s, '_>, - holder: v8::Local<'s, v8::Object>, + receiver: v8::Local<'s, v8::Object>, index: u32, ) -> Option<( *mut crate::native_bridge::JsContextHost, crate::document_runtime::DomHandle, + crate::native_bridge::OwnerDispatchScope, )> { - let host_ptr = window_host_ptr(scope, holder)?; + let host_ptr = window_host_ptr(scope, receiver)?; let host = unsafe { &mut *host_ptr }; - let handle = if let Some(parent) = window_child_context_handle(scope, holder) { - if let Some(document) = host.child_browsing_context_document_handle(parent) { - host.sync_child_browsing_context_subtree(scope, document); - } - host.child_browsing_context_child_frame_handle_by_index(parent, index as usize) + let owner_scope = window_owner_dispatch_scope(scope, receiver)?; + let document = window_document_handle(scope, receiver, host)?; + host.sync_child_browsing_context_subtree(scope, document); + let handle = + host.child_browsing_context_handle_by_index_for_document(document, index as usize)?; + Some((host_ptr, handle, owner_scope)) +} + +fn window_child_projection_for_owner<'s>( + scope: &mut v8::PinScope<'s, '_>, + host: &mut crate::native_bridge::JsContextHost, + handle: crate::document_runtime::DomHandle, + owner_scope: crate::native_bridge::OwnerDispatchScope, +) -> Option> { + if owner_scope == crate::native_bridge::OwnerDispatchScope::Top { + host.child_browsing_context_window_proxy_for_top(scope, handle) } else { - host.sync_child_browsing_context_subtree(scope, host.document_handle()); - host.child_browsing_context_handle_by_index(index as usize) - }?; - Some((host_ptr, handle)) + host.child_browsing_context_window_wrapper(scope, handle) + } } pub(in crate::context_bootstrap) fn window_indexed_property_getter<'s>( @@ -37,16 +50,13 @@ pub(in crate::context_bootstrap) fn window_indexed_property_getter<'s>( args: v8::PropertyCallbackArguments<'s>, mut rv: v8::ReturnValue<'_, v8::Value>, ) -> v8::Intercepted { - let holder = args.holder(); - let Some((host_ptr, handle)) = window_indexed_child_handle(scope, holder, index) else { + let Some((host_ptr, handle, owner_scope)) = + window_indexed_child_handle(scope, args.holder(), index) + else { return v8::Intercepted::kNo; }; let host = unsafe { &mut *host_ptr }; - let window = if window_child_context_handle(scope, holder).is_none() { - host.child_browsing_context_window_proxy_for_top(scope, handle) - } else { - host.child_browsing_context_window_wrapper(scope, handle) - }; + let window = window_child_projection_for_owner(scope, host, handle, owner_scope); let Some(window) = window else { return v8::Intercepted::kNo; }; @@ -72,22 +82,18 @@ pub(in crate::context_bootstrap) fn window_indexed_property_enumerator<'s>( args: v8::PropertyCallbackArguments<'s>, mut rv: v8::ReturnValue<'_, v8::Array>, ) { - let holder = args.holder(); - let Some(host_ptr) = window_host_ptr(scope, holder) else { + let receiver = args.holder(); + let Some(host_ptr) = window_host_ptr(scope, receiver) else { rv.set(v8::Array::new(scope, 0)); return; }; let host = unsafe { &mut *host_ptr }; - let count = if let Some(parent) = window_child_context_handle(scope, holder) { - if let Some(document) = host.child_browsing_context_document_handle(parent) { + let count = window_document_handle(scope, receiver, host) + .map(|document| { host.sync_child_browsing_context_subtree(scope, document); - } - host.child_browsing_context_child_frame_handles(parent) - .len() - } else { - host.sync_child_browsing_context_subtree(scope, host.document_handle()); - host.child_browsing_context_count() - }; + host.child_browsing_context_count_for_document(document) + }) + .unwrap_or(0); let array = serialize_v8_iter_array(scope, (0..count).map(|index| index as u32)) .unwrap_or_else(|| v8::Array::new(scope, 0)); rv.set(array); @@ -99,16 +105,13 @@ pub(in crate::context_bootstrap) fn window_indexed_property_descriptor<'s>( args: v8::PropertyCallbackArguments<'s>, mut rv: v8::ReturnValue<'_, v8::Value>, ) -> v8::Intercepted { - let Some((host_ptr, handle)) = window_indexed_child_handle(scope, args.holder(), index) else { + let Some((host_ptr, handle, owner_scope)) = + window_indexed_child_handle(scope, args.holder(), index) + else { return v8::Intercepted::kNo; }; - let holder = args.holder(); let host = unsafe { &mut *host_ptr }; - let window = if window_child_context_handle(scope, holder).is_none() { - host.child_browsing_context_window_proxy_for_top(scope, handle) - } else { - host.child_browsing_context_window_wrapper(scope, handle) - }; + let window = window_child_projection_for_owner(scope, host, handle, owner_scope); let Some(window) = window else { return v8::Intercepted::kNo; }; diff --git a/moli-renderer-v8/src/native_bridge/context_host/child_frames/lookup.rs b/moli-renderer-v8/src/native_bridge/context_host/child_frames/lookup.rs index 11f24ae3a9..8d46dab64c 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/child_frames/lookup.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/child_frames/lookup.rs @@ -18,6 +18,11 @@ impl JsContextHost { .len() } + pub(crate) fn child_browsing_context_count_for_document(&self, document: DomHandle) -> usize { + self.child_browsing_context_direct_frame_handles_for_document(document) + .len() + } + pub(crate) fn child_browsing_context_handles_in_document_order(&self) -> Vec { let mut handles = Vec::new(); self.collect_child_browsing_context_handles_in_document_order_from_document( @@ -118,22 +123,38 @@ impl JsContextHost { .collect() } + fn child_browsing_context_direct_frame_handles_for_document( + &self, + document: DomHandle, + ) -> Vec { + self.child_browsing_contexts + .keys() + .copied() + .filter(|handle| { + self.dom_host().node(*handle).and_then(Node::owner_document) == Some(document) + }) + .collect() + } + + #[cfg(test)] pub(crate) fn child_browsing_context_handle_by_index(&self, index: usize) -> Option { - // Window indexed/named interceptors hit this on every miss. - if self.child_browsing_contexts.is_empty() { - return None; - } + // Tests use this to inspect the global frame-tree projection rather + // than one Window's scoped indexed properties. self.top_level_child_browsing_context_handles_in_frame_tree_order() .into_iter() .nth(index) } - pub(crate) fn child_browsing_context_child_frame_handle_by_index( + pub(crate) fn child_browsing_context_handle_by_index_for_document( &self, - parent: DomHandle, + document: DomHandle, index: usize, ) -> Option { - self.child_browsing_context_child_frame_handles(parent) + // Window indexed/named interceptors hit this on every miss. + if self.child_browsing_contexts.is_empty() { + return None; + } + self.child_browsing_context_direct_frame_handles_for_document(document) .into_iter() .nth(index) } @@ -142,11 +163,9 @@ impl JsContextHost { &self, parent: DomHandle, ) -> Vec { - self.child_browsing_contexts - .keys() - .copied() - .filter(|handle| self.child_browsing_context_parent_handle(*handle) == Some(parent)) - .collect() + self.child_browsing_context_document_handle(parent) + .map(|document| self.child_browsing_context_direct_frame_handles_for_document(document)) + .unwrap_or_default() } pub(crate) fn child_browsing_context_direct_host_handles( diff --git a/moli-renderer-v8/src/native_bridge/context_host/popups.rs b/moli-renderer-v8/src/native_bridge/context_host/popups.rs index 4f3e187a24..fd7c10ce8f 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/popups.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/popups.rs @@ -653,8 +653,7 @@ impl JsContextHost { creator_base_url: Url, creator_policy_container: DocumentPolicyContainer, ) -> Option> { - if opener.is_some() - && let Some(name) = trackable_lightweight_popup_window_name(target_name) + if let Some(name) = trackable_lightweight_popup_window_name(target_name) && let Some(popup_id) = self.lightweight_popup_window_names.get(&name).copied() && self.lightweight_popup_is_open(popup_id) && let Some(window) = self.reopen_lightweight_popup_window( 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..3538dcecad 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 @@ -2901,3 +2901,103 @@ async fn lightweight_popup_external_script_redirect_final_url_obeys_csp() { format!("{final_script_url}|script-src-elem|enforce|true||load:true") ); } + +#[tokio::test] +async fn lightweight_popup_window_child_queries_stay_in_the_popup_document() { + let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).expect("loader"); + let mut vm = + new_page_task_executor_test_vm_with_loader("https://example.com/base/page.html", &loader); + + let result = vm + .eval( + r#" +(() => { + globalThis.__popupFrameScopeProbe = "pending"; + const topFrame = document.createElement("iframe"); + topFrame.name = "top-child"; + document.body.appendChild(topFrame); + topFrame.contentDocument.body.innerHTML = '

'; + + const popupMarkup = ` + + +