From d54792cd14f4d54ca41a4056128539cd0bc1a5bb Mon Sep 17 00:00:00 2001 From: ldm0 Date: Tue, 1 Sep 2026 05:10:38 +0800 Subject: [PATCH] fix(html): return iframe windows from document named access --- moli-renderer-v8/src/native_bridge/element.rs | 24 ++----- .../native_bridge/element/url_attributes.rs | 4 +- .../element/url_attributes/iframe.rs | 22 ++++++ .../src/native_bridge/named_access.rs | 19 +++-- .../live_document/document_collections.rs | 71 +++++++++++++++++++ 5 files changed, 116 insertions(+), 24 deletions(-) diff --git a/moli-renderer-v8/src/native_bridge/element.rs b/moli-renderer-v8/src/native_bridge/element.rs index 9ae18e3ca..707a556d6 100644 --- a/moli-renderer-v8/src/native_bridge/element.rs +++ b/moli-renderer-v8/src/native_bridge/element.rs @@ -669,13 +669,15 @@ pub(super) use tree_mutation::{ node_insert_adjacent_element_callback, node_insert_adjacent_html_callback, node_insert_adjacent_node_callback, node_insert_adjacent_text_callback, }; -pub(super) use url_attributes::update_iframe_snapshot_navigation; use url_attributes::{ disconnected_iframe_can_materialize_detached_content, iframe_has_inactive_child_context, iframe_is_in_own_child_document, iframe_is_inside_its_own_child_context_document, iframe_uses_detached_content_cache, parsed_url_like_attribute, resolve_url_like_attribute, set_resolved_url_attribute, should_block_dangling_markup_subresource, }; +pub(super) use url_attributes::{ + live_frame_owner_content_window_for_handle, update_iframe_snapshot_navigation, +}; #[derive(WebApiFunctionTemplate)] #[webapi(interface = web_api_interfaces::Element)] @@ -3518,24 +3520,8 @@ fn frame_owner_content_window_getter_function<'s>( } return; } - let runtime = unsafe { &mut *runtime_ptr }; - runtime.refresh_child_browsing_context(scope, handle); - let exposes_same_origin_wrapper = - runtime.child_browsing_context_is_same_origin_with_top(handle); - let window = runtime.child_browsing_context_window_proxy_for_top(scope, handle); - if window.is_some() { - runtime.mark_child_browsing_context_window_wrapper_exposed_to_top(handle); - } - if exposes_same_origin_wrapper && window.is_some() { - runtime.request_child_frame_realm_materialization(handle); - } - match window { - Some(window) => { - if runtime.child_browsing_context_is_same_origin_with_top(handle) { - runtime.set_cached_detached_iframe_content_window(scope, handle, window); - } - rv.set(window.into()); - } + match live_frame_owner_content_window_for_handle(scope, runtime_ptr, handle) { + Some(window) => rv.set(window.into()), None => rv.set_null(), } } diff --git a/moli-renderer-v8/src/native_bridge/element/url_attributes.rs b/moli-renderer-v8/src/native_bridge/element/url_attributes.rs index a91a2edf6..add9a00b0 100644 --- a/moli-renderer-v8/src/native_bridge/element/url_attributes.rs +++ b/moli-renderer-v8/src/native_bridge/element/url_attributes.rs @@ -5,9 +5,11 @@ pub(super) use self::helpers::{ parsed_url_like_attribute, resolve_url_like_attribute, set_resolved_url_attribute, should_block_dangling_markup_subresource, }; -pub(in crate::native_bridge) use self::iframe::update_iframe_snapshot_navigation; pub(super) use self::iframe::{ disconnected_iframe_can_materialize_detached_content, iframe_has_inactive_child_context, iframe_is_in_own_child_document, iframe_is_inside_its_own_child_context_document, iframe_uses_detached_content_cache, }; +pub(in crate::native_bridge) use self::iframe::{ + live_frame_owner_content_window_for_handle, update_iframe_snapshot_navigation, +}; diff --git a/moli-renderer-v8/src/native_bridge/element/url_attributes/iframe.rs b/moli-renderer-v8/src/native_bridge/element/url_attributes/iframe.rs index bde955ffa..bc5d41953 100644 --- a/moli-renderer-v8/src/native_bridge/element/url_attributes/iframe.rs +++ b/moli-renderer-v8/src/native_bridge/element/url_attributes/iframe.rs @@ -137,6 +137,28 @@ pub(in crate::native_bridge::element) fn iframe_has_inactive_child_context( && !runtime.child_browsing_context_is_live(handle) } +pub(in crate::native_bridge) fn live_frame_owner_content_window_for_handle<'s>( + scope: &mut v8::PinScope<'s, '_>, + runtime_ptr: *mut JsContextHost, + handle: DomHandle, +) -> Option> { + let runtime = unsafe { &mut *runtime_ptr }; + runtime.refresh_child_browsing_context(scope, handle); + let exposes_same_origin_wrapper = + runtime.child_browsing_context_is_same_origin_with_top(handle); + let window = runtime.child_browsing_context_window_proxy_for_top(scope, handle); + if window.is_some() { + runtime.mark_child_browsing_context_window_wrapper_exposed_to_top(handle); + } + if exposes_same_origin_wrapper && window.is_some() { + runtime.request_child_frame_realm_materialization(handle); + } + if exposes_same_origin_wrapper && let Some(window) = window { + runtime.set_cached_detached_iframe_content_window(scope, handle, window); + } + window +} + pub(in crate::native_bridge::element) fn iframe_is_inside_its_own_child_context_document( scope: &mut v8::PinScope<'_, '_>, runtime_ptr: *mut JsContextHost, diff --git a/moli-renderer-v8/src/native_bridge/named_access.rs b/moli-renderer-v8/src/native_bridge/named_access.rs index a72f3ba53..16f0bc31d 100644 --- a/moli-renderer-v8/src/native_bridge/named_access.rs +++ b/moli-renderer-v8/src/native_bridge/named_access.rs @@ -3,6 +3,7 @@ use moli_dom::native::DomHost; use super::{ JsContextHost, collections, + element::live_frame_owner_content_window_for_handle, identity::{CollectionKind, LiveCollectionQueryKind}, node::node_runtime_and_handle_from_object, }; @@ -162,10 +163,20 @@ fn document_named_access_value<'s>( ) -> Option> { let (runtime_ptr, document_handle, name, handles) = context; match handles.as_slice() { - [handle] => unsafe { &mut *runtime_ptr } - .native_bridge_mut() - .wrap_handle(scope, runtime_ptr, *handle) - .map(Into::into), + [handle] => { + if unsafe { &*runtime_ptr } + .dom_host() + .is_html_element_named(*handle, "iframe") + && let Some(window) = + live_frame_owner_content_window_for_handle(scope, runtime_ptr, *handle) + { + return Some(window.into()); + } + unsafe { &mut *runtime_ptr } + .native_bridge_mut() + .wrap_handle(scope, runtime_ptr, *handle) + .map(Into::into) + } _ => build_document_named_items_collection(scope, runtime_ptr, document_handle, &name) .map(Into::into), } diff --git a/moli-renderer-v8/src/script_vm/tests/dom_elements/live_document/document_collections.rs b/moli-renderer-v8/src/script_vm/tests/dom_elements/live_document/document_collections.rs index 7d4e3f89a..37ec37ee9 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_elements/live_document/document_collections.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_elements/live_document/document_collections.rs @@ -372,6 +372,77 @@ fn live_document_named_properties_follow_html_candidate_and_liveness_rules() { ); } +#[test] +fn live_document_named_iframe_singletons_return_child_windows() { + let mut vm = new_parsed_test_vm( + "https://example.com/", + r#" + + + + + + + "#, + ); + + let result = vm + .eval( + r#" + (() => { + const single = document.getElementById("single-frame"); + const first = document.getElementById("duplicate-first"); + const second = document.getElementById("duplicate-second"); + const numeric = document.getElementById("numeric-frame"); + const dynamic = document.getElementById("dynamic-frame"); + const duplicate = document.duplicateFrame; + const initial = { + singletonIsWindow: document.singleFrame === single.contentWindow, + singletonClass: Object.prototype.toString.call(document.singleFrame), + duplicateIsCollection: duplicate instanceof HTMLCollection, + duplicateMembers: + duplicate.length === 2 && duplicate[0] === first && duplicate[1] === second, + numericIsWindow: document[42] === numeric.contentWindow, + idOnlyAbsent: + document["id-only-frame"] === undefined && + !("id-only-frame" in document), + }; + + dynamic.name = "dynamicBefore"; + const beforeUpdate = document.dynamicBefore === dynamic.contentWindow; + dynamic.name = "dynamicAfter"; + dynamic.id = "differentId"; + const afterUpdate = + document.dynamicBefore === undefined && + !("dynamicBefore" in document) && + document.dynamicAfter === dynamic.contentWindow && + document.differentId === undefined; + + single.removeAttribute("name"); + const removedName = + document.singleFrame === undefined && !("singleFrame" in document); + dynamic.remove(); + const removedElement = + document.dynamicAfter === undefined && !("dynamicAfter" in document); + + return JSON.stringify({ + initial, + beforeUpdate, + afterUpdate, + removedName, + removedElement, + }); + })() + "#, + ) + .expect("document iframe named-property probe should evaluate"); + + assert_eq!( + result, + r#"{"initial":{"singletonIsWindow":true,"singletonClass":"[object Window]","duplicateIsCollection":true,"duplicateMembers":true,"numericIsWindow":true,"idOnlyAbsent":true},"beforeUpdate":true,"afterUpdate":true,"removedName":true,"removedElement":true}"# + ); +} + #[test] fn legacy_named_access_filters_name_candidates_by_consumer() { let mut vm = new_parsed_test_vm(