From 9dc80da2cacf32872ecae6811f9ee3f0cc4e5d20 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Tue, 1 Sep 2026 12:34:30 +0800 Subject: [PATCH] fix(window): hide shadow frames from indexed access --- .../context_host/child_frames/lookup.rs | 20 +++++++++-- .../extracted/frame_navigation_and_history.rs | 34 +++++++++++++++++++ .../extracted/window_and_frame_surfaces.rs | 28 --------------- 3 files changed, 52 insertions(+), 30 deletions(-) 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 8d46dab64..6b2fd4bed 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 @@ -19,7 +19,7 @@ impl JsContextHost { } pub(crate) fn child_browsing_context_count_for_document(&self, document: DomHandle) -> usize { - self.child_browsing_context_direct_frame_handles_for_document(document) + self.window_child_browsing_context_handles_for_document(document) .len() } @@ -136,6 +136,22 @@ impl JsContextHost { .collect() } + fn window_child_browsing_context_handles_for_document( + &self, + document: DomHandle, + ) -> Vec { + // A frame inside a shadow tree still owns a browsing context, but it is + // not exposed through the containing Window's indexed properties. + self.child_browsing_context_direct_frame_handles_for_document(document) + .into_iter() + .filter(|handle| { + self.dom_host() + .node(*handle) + .is_some_and(|node| node.flags().in_document_tree()) + }) + .collect() + } + #[cfg(test)] pub(crate) fn child_browsing_context_handle_by_index(&self, index: usize) -> Option { // Tests use this to inspect the global frame-tree projection rather @@ -154,7 +170,7 @@ impl JsContextHost { if self.child_browsing_contexts.is_empty() { return None; } - self.child_browsing_context_direct_frame_handles_for_document(document) + self.window_child_browsing_context_handles_for_document(document) .into_iter() .nth(index) } diff --git a/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface/extracted/frame_navigation_and_history.rs b/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface/extracted/frame_navigation_and_history.rs index 43cc58949..fabdc11fb 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface/extracted/frame_navigation_and_history.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface/extracted/frame_navigation_and_history.rs @@ -2857,3 +2857,37 @@ async fn embed_and_object_javascript_attributes_use_resource_fetch_not_script_na "resource failures must not execute javascript or load, and object must enter fallback" ); } + +#[test] +fn iframe_in_shadow_tree_is_not_a_window_child_property() { + let mut vm = new_storage_test_vm("https://shadow-iframe-named-property.test/"); + + let result = vm + .eval( + r#" +const host = document.createElement('div'); +(document.body || document.documentElement || document).appendChild(host); +const shadow = host.attachShadow({ mode: 'open' }); +const shadowFrame = document.createElement('iframe'); +shadowFrame.name = 'shadowTarget'; +shadow.appendChild(shadowFrame); +const lightFrame = document.createElement('iframe'); +lightFrame.name = 'lightTarget'; +(document.body || document.documentElement || document).appendChild(lightFrame); +[ + window.length, + window.frames.length, + window[0] === lightFrame.contentWindow, + window[1] === undefined, + 'shadowTarget' in window, + window.shadowTarget === undefined, + shadowFrame.contentWindow !== null, + 'lightTarget' in window, + window.lightTarget === lightFrame.contentWindow +].join('|') +"#, + ) + .expect("shadow iframe named property probe should evaluate"); + + assert_eq!(result, "1|1|true|true|false|true|true|true|true"); +} diff --git a/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface/extracted/window_and_frame_surfaces.rs b/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface/extracted/window_and_frame_surfaces.rs index 3d7e4fa68..9930ffff0 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface/extracted/window_and_frame_surfaces.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface/extracted/window_and_frame_surfaces.rs @@ -470,35 +470,7 @@ frame.name = 'target'; assert_eq!(result, "[object Window]|true|object"); } -#[test] -fn iframe_in_shadow_tree_is_not_a_named_window_property() { - let mut vm = new_storage_test_vm("https://shadow-iframe-named-property.test/"); - let result = vm - .eval( - r#" -const host = document.createElement('div'); -(document.body || document.documentElement || document).appendChild(host); -const shadow = host.attachShadow({ mode: 'open' }); -const shadowFrame = document.createElement('iframe'); -shadowFrame.name = 'shadowTarget'; -shadow.appendChild(shadowFrame); -const lightFrame = document.createElement('iframe'); -lightFrame.name = 'lightTarget'; -(document.body || document.documentElement || document).appendChild(lightFrame); -[ - 'shadowTarget' in window, - window.shadowTarget === undefined, - shadowFrame.contentWindow !== null, - 'lightTarget' in window, - window.lightTarget === lightFrame.contentWindow -].join('|') -"#, - ) - .expect("shadow iframe named property probe should evaluate"); - - assert_eq!(result, "false|true|true|true|true"); -} #[test] fn child_webassembly_native_values_use_public_intrinsic_prototypes() { let mut vm = new_storage_test_vm("https://child-wasm-intrinsics.test/");