fix(html): expose getSVGDocument on frame owners

This commit is contained in:
ldm0
2026-09-22 21:43:34 +08:00
parent 333bbd1039
commit 21d0a5ffac
3 changed files with 135 additions and 3 deletions
@@ -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,
@@ -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"],
@@ -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 = "<body>iframe child</body>";
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) =