fix(svg): implement ownerSVGElement

This commit is contained in:
ldm0
2026-10-02 03:27:49 +08:00
parent e131f38dc4
commit e08c77b5cf
3 changed files with 134 additions and 0 deletions
@@ -968,6 +968,12 @@ struct SvgElementPrototypeAccessorsDeclaration {
getter = svg_element_class_name_getter
)]
class_name: (),
#[webapi(
accessor_property = "ownerSVGElement",
getter = svg_element_owner_svg_element_getter
)]
owner_svg_element: (),
}
#[derive(WebApiFunctionTemplate)]
@@ -136,6 +136,63 @@ pub(super) fn svg_element_class_name_getter<'s>(
svg_animated_string_attribute_getter(scope, args, rv, SVG_ELEMENT_CLASS_NAME_SLOT, "class");
}
pub(super) fn svg_element_owner_svg_element_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'s, v8::Value>,
) {
let Ok((runtime_ptr, handle)) =
crate::native_bridge::node_runtime_and_handle_from_object_or_detached(scope, args.this())
else {
webidl::throw_type_error(
scope,
"SVGElement.ownerSVGElement called on incompatible receiver.",
);
return;
};
let owner_handle = {
let runtime = unsafe { &*runtime_ptr };
let Some(node) = runtime.dom_host().node(handle) else {
rv.set_null();
return;
};
if node.namespace() != Some(crate::native_bridge::document::SVG_NS) {
webidl::throw_type_error(
scope,
"SVGElement.ownerSVGElement called on incompatible receiver.",
);
return;
}
let mut current = node.parent_node_id();
let mut owner = None;
while let Some(candidate) = current {
let Some(ancestor) = runtime.dom_host().node(candidate) else {
break;
};
if ancestor.namespace() != Some(crate::native_bridge::document::SVG_NS)
|| ancestor.local_name() == Some("foreignObject")
{
break;
}
if ancestor.local_name() == Some("svg") {
owner = Some(candidate);
break;
}
current = ancestor.parent_node_id();
}
owner
};
let Some(owner) = owner_handle.and_then(|owner| {
crate::native_bridge::document::detached_native_object_for_handle(scope, runtime_ptr, owner)
}) else {
rv.set_null();
return;
};
rv.set(owner.into());
}
pub(super) fn svg_uri_href_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
@@ -284,6 +284,77 @@ fn svg_historical_interfaces_keep_current_element_constructors_only() {
assert_eq!(result, "ok");
}
#[test]
fn svg_owner_svg_element_tracks_the_nearest_svg_fragment_root() {
let mut vm = new_parsed_test_vm(
"https://svg-owner-element.test/",
r#"<!doctype html><html><body><div id="container">
<svg id="outer">
<g><circle id="circle"></circle><svg id="inner"><rect id="rect"></rect></svg></g>
<foreignObject><svg id="foreign-svg"><svg id="foreign-inner"></svg></svg></foreignObject>
</svg>
</div></body></html>"#,
);
let result = vm
.eval(
r##"
(() => {
const assert = (condition, message) => {
if (!condition) throw new Error(message);
};
const ns = "http://www.w3.org/2000/svg";
const outer = document.querySelector("#outer");
const circle = document.querySelector("#circle");
const inner = document.querySelector("#inner");
const rect = document.querySelector("#rect");
const foreignSvg = document.querySelector("#foreign-svg");
const foreignInner = document.querySelector("#foreign-inner");
assert(outer.ownerSVGElement === null, "outer SVG");
assert(circle.ownerSVGElement === outer, "descendant of outer SVG");
assert(inner.ownerSVGElement === outer, "nested SVG");
assert(rect.ownerSVGElement === inner, "descendant of nested SVG");
assert(foreignSvg.ownerSVGElement === null, "foreignObject starts a new SVG fragment");
assert(foreignInner.ownerSVGElement === foreignSvg, "nested foreignObject SVG");
document.querySelector("#container").remove();
assert(circle.ownerSVGElement === outer, "detached outer SVG descendant");
assert(inner.ownerSVGElement === outer, "detached nested SVG");
assert(rect.ownerSVGElement === inner, "detached nested SVG descendant");
const standalone = document.createElementNS(ns, "ellipse");
assert(standalone.ownerSVGElement === null, "standalone SVG element");
const svgDocument = document.implementation.createDocument(ns, "svg", null);
const documentRoot = svgDocument.documentElement;
const documentRect = svgDocument.createElementNS(ns, "rect");
documentRoot.append(documentRect);
assert(documentRoot.ownerSVGElement === null, "SVG document root");
assert(documentRect.ownerSVGElement === documentRoot, "SVG document child");
const descriptor = Object.getOwnPropertyDescriptor(
SVGElement.prototype,
"ownerSVGElement",
);
assert(typeof descriptor.get === "function", "prototype getter");
assert(descriptor.enumerable && descriptor.configurable, "getter flags");
let incompatibleReceiver = false;
try {
descriptor.get.call(document.body);
} catch (error) {
incompatibleReceiver = error instanceof TypeError;
}
assert(incompatibleReceiver, "incompatible receiver");
return "ok";
})()
"##,
)
.expect("SVG ownerSVGElement probe should evaluate");
assert_eq!(result, "ok");
}
#[test]
fn svg_geometry_queries_use_computed_paths_live_tree_and_kurbo_bounds() {
let mut vm = new_parsed_test_vm(