mirror of
https://github.com/lexmount/moli.git
synced 2026-10-08 16:01:05 +00:00
fix(svg): implement ownerSVGElement
This commit is contained in:
@@ -897,6 +897,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)]
|
||||
|
||||
@@ -64,6 +64,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>,
|
||||
|
||||
@@ -1433,6 +1433,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(
|
||||
|
||||
Reference in New Issue
Block a user