From 21d0a5ffacafeb6b4fa7185ad8eca9441ae20bd0 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Mon, 31 Aug 2026 06:13:26 +0800 Subject: [PATCH] fix(html): expose getSVGDocument on frame owners --- moli-renderer-v8/src/native_bridge/element.rs | 65 ++++++++++++++++++ .../tests/dom_elements/live_document.rs | 6 +- moli-renderer-v8/src/script_vm/tests/mod.rs | 67 +++++++++++++++++++ 3 files changed, 135 insertions(+), 3 deletions(-) diff --git a/moli-renderer-v8/src/native_bridge/element.rs b/moli-renderer-v8/src/native_bridge/element.rs index 00c2af65cb..e5e790f133 100644 --- a/moli-renderer-v8/src/native_bridge/element.rs +++ b/moli-renderer-v8/src/native_bridge/element.rs @@ -3502,6 +3502,53 @@ fn frame_owner_content_document_getter_function<'s>( } } +fn frame_owner_get_svg_document_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + rv: v8::ReturnValue<'_, v8::Value>, + interface: &'static str, + local_name: &'static str, +) { + let Ok((runtime_ptr, handle)) = + node_runtime_and_handle_from_object_or_detached(scope, args.this()) + else { + throw_incompatible_method_receiver(scope, interface, "getSVGDocument"); + return; + }; + if !unsafe { &*runtime_ptr } + .dom_host() + .is_html_element_named(handle, local_name) + { + throw_incompatible_method_receiver(scope, interface, "getSVGDocument"); + return; + } + frame_owner_content_document_getter_function(scope, args, rv); +} + +fn iframe_get_svg_document_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + rv: v8::ReturnValue<'_, v8::Value>, +) { + frame_owner_get_svg_document_callback(scope, args, rv, "HTMLIFrameElement", "iframe"); +} + +fn embed_get_svg_document_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + rv: v8::ReturnValue<'_, v8::Value>, +) { + frame_owner_get_svg_document_callback(scope, args, rv, "HTMLEmbedElement", "embed"); +} + +fn object_get_svg_document_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + rv: v8::ReturnValue<'_, v8::Value>, +) { + frame_owner_get_svg_document_callback(scope, args, rv, "HTMLObjectElement", "object"); +} + fn frame_owner_content_window_getter_function<'s>( scope: &mut v8::PinScope<'s, '_>, args: v8::FunctionCallbackArguments<'s>, @@ -4084,6 +4131,12 @@ struct HtmlEmbedElementUrlPrototypeDeclaration { setter_data = DomStringReflection::EmbedType )] r#type: (), + #[webapi( + method = "getSVGDocument", + length = 0, + callback = embed_get_svg_document_callback + )] + get_svg_document: (), } #[derive(WebApiFunctionTemplate)] @@ -4260,6 +4313,12 @@ struct HtmlIFrameElementPrototypeDeclaration { receiver = web_api_interfaces::HTMLIFrameElement::is_instance )] content_window: (), + #[webapi( + method = "getSVGDocument", + length = 0, + callback = iframe_get_svg_document_callback + )] + get_svg_document: (), } #[derive(WebApiFunctionTemplate)] @@ -5576,6 +5635,12 @@ struct HtmlObjectElementPrototypeDeclaration { getter = frame_owner_content_window_getter_function )] content_window: (), + #[webapi( + method = "getSVGDocument", + length = 0, + callback = object_get_svg_document_callback + )] + get_svg_document: (), #[webapi( accessor_property, getter = html_border_getter_function, diff --git a/moli-renderer-v8/src/script_vm/tests/dom_elements/live_document.rs b/moli-renderer-v8/src/script_vm/tests/dom_elements/live_document.rs index 1b80e29d14..39b56995bd 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_elements/live_document.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_elements/live_document.rs @@ -2802,13 +2802,13 @@ fn detached_specialized_element_surfaces_are_inherited() { [id("details"), ["name", "open"], "details"], [id("dir"), ["compact"], "dir"], [id("dl"), ["compact"], "dl"], - [id("embed"), ["name"], "embed"], + [id("embed"), ["getSVGDocument", "name"], "embed"], [id("fieldset"), ["disabled", "elements", "form", "name", "type", "validity", "validationMessage", "willValidate", "checkValidity", "reportValidity", "setCustomValidity"], "fieldset"], [id("font"), ["color"], "font"], [id("form"), ["acceptCharset", "action", "autocomplete", "elements", "encoding", "enctype", "length", "method", "name", "noValidate", "rel", "relList", "target", "requestSubmit", "submit", "reset", "checkValidity", "reportValidity"], "form"], [detachedFrame, ["frameBorder", "longDesc", "marginHeight", "marginWidth", "name", "scrolling"], "frame"], [id("hr"), ["color", "noShade"], "hr"], - [id("iframe"), ["contentDocument", "contentWindow", "frameBorder", "longDesc", "marginHeight", "marginWidth", "name", "scrolling", "src", "srcdoc"], "iframe"], + [id("iframe"), ["contentDocument", "contentWindow", "frameBorder", "getSVGDocument", "longDesc", "marginHeight", "marginWidth", "name", "scrolling", "src", "srcdoc"], "iframe"], [id("image"), ["alt", "border", "decode", "decoding", "height", "hspace", "longDesc", "lowsrc", "name", "src", "srcset", "useMap", "vspace", "width"], "image"], [id("input"), ["accept", "alt", "autocomplete", "checked", "defaultChecked", "defaultValue", "dirName", "disabled", "files", "form", "formAction", "formEnctype", "formMethod", "formNoValidate", "formTarget", "height", "indeterminate", "labels", "list", "max", "maxLength", "min", "minLength", "multiple", "name", "pattern", "placeholder", "readOnly", "required", "size", "src", "step", "type", "validity", "validationMessage", "value", "valueAsDate", "valueAsNumber", "willValidate", "width", "checkValidity", "reportValidity", "setCustomValidity", "select", "setRangeText", "setSelectionRange", "showPicker", "stepDown", "stepUp"], "input"], [id("ins"), ["cite", "dateTime"], "ins"], @@ -2821,7 +2821,7 @@ fn detached_specialized_element_surfaces_are_inherited() { [id("menu"), ["compact"], "menu"], [id("meta"), ["content", "httpEquiv", "media", "name", "scheme"], "meta"], [id("meter"), ["high", "labels", "low", "max", "min", "optimum", "value"], "meter"], - [id("object"), ["archive", "border", "code", "codeBase", "codeType", "contentDocument", "contentWindow", "data", "declare", "form", "hspace", "name", "standby", "type", "useMap", "validity", "validationMessage", "vspace", "willValidate", "checkValidity", "reportValidity", "setCustomValidity"], "object"], + [id("object"), ["archive", "border", "code", "codeBase", "codeType", "contentDocument", "contentWindow", "data", "declare", "form", "getSVGDocument", "hspace", "name", "standby", "type", "useMap", "validity", "validationMessage", "vspace", "willValidate", "checkValidity", "reportValidity", "setCustomValidity"], "object"], [id("ol"), ["compact", "reversed", "start", "type"], "ol"], [id("optgroup"), ["disabled", "label"], "optgroup"], [id("option"), ["defaultSelected", "disabled", "form", "index", "label", "selected", "text", "value"], "option"], diff --git a/moli-renderer-v8/src/script_vm/tests/mod.rs b/moli-renderer-v8/src/script_vm/tests/mod.rs index fa0916f08e..35e99c089b 100644 --- a/moli-renderer-v8/src/script_vm/tests/mod.rs +++ b/moli-renderer-v8/src/script_vm/tests/mod.rs @@ -5851,6 +5851,73 @@ fn embedded_frame_owners_create_child_contexts_only_for_document_content() { ); } +#[test] +fn frame_owner_get_svg_document_methods_share_content_document_semantics_and_enforce_brands() { + let mut vm = new_storage_test_vm("https://frame-owner-get-svg-document.test/"); + + let result = vm + .eval( + r#" +(() => { + const root = document.body || document.documentElement || document; + const iframe = document.createElement("iframe"); + iframe.srcdoc = "iframe child"; + const embed = document.createElement("embed"); + embed.type = "text/html"; + embed.src = "about:blank"; + const object = document.createElement("object"); + object.type = "text/html"; + object.data = "about:blank"; + root.appendChild(iframe); + root.appendChild(embed); + root.appendChild(object); + + const interfaces = [ + [HTMLIFrameElement.prototype, iframe, "HTMLIFrameElement"], + [HTMLEmbedElement.prototype, embed, "HTMLEmbedElement"], + [HTMLObjectElement.prototype, object, "HTMLObjectElement"] + ]; + const descriptors = interfaces.map(([prototype]) => { + const descriptor = Object.getOwnPropertyDescriptor(prototype, "getSVGDocument"); + return { + type: typeof descriptor.value, + name: descriptor.value.name, + length: descriptor.value.length, + writable: descriptor.writable, + enumerable: descriptor.enumerable, + configurable: descriptor.configurable + }; + }); + const brandErrors = interfaces.map(([prototype], index) => { + try { + prototype.getSVGDocument.call(interfaces[(index + 1) % interfaces.length][1]); + return "accepted"; + } catch (error) { + return error.name; + } + }); + + return JSON.stringify({ + descriptors, + iframeMatches: iframe.getSVGDocument() !== null && + iframe.getSVGDocument() === iframe.contentDocument, + embedHasDocument: embed.getSVGDocument() !== null, + objectMatches: object.getSVGDocument() !== null && + object.getSVGDocument() === object.contentDocument, + brandErrors, + absentFromBase: !("getSVGDocument" in HTMLElement.prototype) + }); +})() +"#, + ) + .expect("frame owner getSVGDocument methods should evaluate"); + + assert_eq!( + result, + r#"{"descriptors":[{"type":"function","name":"getSVGDocument","length":0,"writable":true,"enumerable":true,"configurable":true},{"type":"function","name":"getSVGDocument","length":0,"writable":true,"enumerable":true,"configurable":true},{"type":"function","name":"getSVGDocument","length":0,"writable":true,"enumerable":true,"configurable":true}],"iframeMatches":true,"embedHasDocument":true,"objectMatches":true,"brandErrors":["TypeError","TypeError","TypeError"],"absentFromBase":true}"# + ); +} + #[tokio::test] async fn failed_object_attribute_navigation_enters_fallback_without_recreating_child_context() { let (object_url, request_rx, release_tx, server) =