fix(html): return iframe windows from document named access

This commit is contained in:
ldm0
2026-09-28 01:47:00 +08:00
parent f42965f837
commit d54792cd14
5 changed files with 116 additions and 24 deletions
+5 -19
View File
@@ -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(),
}
}
@@ -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,
};
@@ -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<v8::Local<'s, v8::Object>> {
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,
@@ -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<v8::Local<'s, v8::Value>> {
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),
}
@@ -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#"<!doctype html><html><body>
<iframe id="single-frame" name="singleFrame"></iframe>
<iframe id="duplicate-first" name="duplicateFrame"></iframe>
<iframe id="duplicate-second" name="duplicateFrame"></iframe>
<iframe id="numeric-frame" name="42"></iframe>
<iframe id="id-only-frame"></iframe>
<iframe id="dynamic-frame"></iframe>
</body></html>"#,
);
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(