mirror of
https://github.com/lexmount/moli.git
synced 2026-09-29 00:01:38 +00:00
fix(svg): honor xlink href fallback
This commit is contained in:
@@ -2151,9 +2151,20 @@ pub(super) fn svg_owner_attribute_value<'s>(
|
||||
attribute: &str,
|
||||
) -> Option<String> {
|
||||
let (runtime_ptr, handle) =
|
||||
crate::native_bridge::node_runtime_and_handle_from_object(scope, owner).ok()?;
|
||||
let runtime = unsafe { &mut *runtime_ptr };
|
||||
runtime.dom_host().get_attribute(handle, attribute)
|
||||
crate::native_bridge::node_runtime_and_handle_from_object_or_detached(scope, owner).ok()?;
|
||||
let runtime = unsafe { &*runtime_ptr };
|
||||
if attribute != "href" {
|
||||
return runtime.dom_host().get_attribute(handle, attribute);
|
||||
}
|
||||
let value = runtime.dom_host().get_attribute_ns(handle, None, attribute);
|
||||
if value.is_some() {
|
||||
return value;
|
||||
}
|
||||
runtime.dom_host().get_attribute_ns(
|
||||
handle,
|
||||
Some(crate::native_bridge::document::XLINK_NS),
|
||||
attribute,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn parse_svg_length_value(raw: &str) -> Option<SvgParsedLength> {
|
||||
|
||||
@@ -300,6 +300,7 @@ pub(in crate::native_bridge::document) use structure::set_document_body_for_nati
|
||||
|
||||
pub(crate) const XHTML_NS: &str = "http://www.w3.org/1999/xhtml";
|
||||
pub(crate) const SVG_NS: &str = "http://www.w3.org/2000/svg";
|
||||
pub(crate) const XLINK_NS: &str = "http://www.w3.org/1999/xlink";
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u32)]
|
||||
|
||||
@@ -344,6 +344,21 @@ pub(in crate::native_bridge) fn trusted_attribute_string_value<'s>(
|
||||
)
|
||||
}
|
||||
|
||||
fn svg_animated_string_attribute_namespace(
|
||||
runtime: &JsContextHost,
|
||||
handle: DomHandle,
|
||||
attribute: &str,
|
||||
) -> Option<&'static str> {
|
||||
(attribute == "href"
|
||||
&& !runtime.dom_host().has_attribute_ns(handle, None, attribute)
|
||||
&& runtime.dom_host().has_attribute_ns(
|
||||
handle,
|
||||
Some(crate::native_bridge::document::XLINK_NS),
|
||||
attribute,
|
||||
))
|
||||
.then_some(crate::native_bridge::document::XLINK_NS)
|
||||
}
|
||||
|
||||
pub(crate) fn set_svg_animated_string_base_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
@@ -351,17 +366,43 @@ pub(crate) fn set_svg_animated_string_base_value<'s>(
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
) -> Option<String> {
|
||||
let (runtime_ptr, handle) =
|
||||
crate::native_bridge::node_runtime_and_handle_from_object(scope, owner).ok()?;
|
||||
crate::native_bridge::node_runtime_and_handle_from_object_or_detached(scope, owner).ok()?;
|
||||
let namespace =
|
||||
svg_animated_string_attribute_namespace(unsafe { &*runtime_ptr }, handle, attribute);
|
||||
let value = trusted_attribute_value_string(
|
||||
scope,
|
||||
Some((runtime_ptr, handle)),
|
||||
None,
|
||||
namespace,
|
||||
attribute,
|
||||
value,
|
||||
TrustedAttributeSetter::SvgAnimatedStringBaseVal,
|
||||
)?;
|
||||
let _ =
|
||||
unsafe { &mut *runtime_ptr }.set_attribute(scope, runtime_ptr, handle, attribute, &value);
|
||||
let namespace =
|
||||
svg_animated_string_attribute_namespace(unsafe { &*runtime_ptr }, handle, attribute);
|
||||
if attribute == "href" {
|
||||
let _ = unsafe { &mut *runtime_ptr }.set_attribute_ns(
|
||||
scope,
|
||||
runtime_ptr,
|
||||
handle,
|
||||
namespace,
|
||||
namespace.map(|_| "xlink"),
|
||||
attribute,
|
||||
if namespace.is_some() {
|
||||
"xlink:href"
|
||||
} else {
|
||||
attribute
|
||||
},
|
||||
&value,
|
||||
);
|
||||
} else {
|
||||
let _ = unsafe { &mut *runtime_ptr }.set_attribute(
|
||||
scope,
|
||||
runtime_ptr,
|
||||
handle,
|
||||
attribute,
|
||||
&value,
|
||||
);
|
||||
}
|
||||
Some(value)
|
||||
}
|
||||
|
||||
|
||||
+83
@@ -2934,3 +2934,86 @@ fn svg_svg_element_deselect_all_clears_the_owner_document_selection() {
|
||||
|
||||
assert_eq!(result, "ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn svg_href_animated_string_prefers_href_and_falls_back_to_xlink_href() {
|
||||
let mut vm = new_storage_test_vm("https://svg-href-reflection.test/");
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const assert = (condition, message) => {
|
||||
if (!condition) throw new Error(message);
|
||||
};
|
||||
const svg = "http://www.w3.org/2000/svg";
|
||||
const xlink = "http://www.w3.org/1999/xlink";
|
||||
const unrelated = "https://namespaced-href.test/";
|
||||
const exercise = ownerDocument => {
|
||||
const anchor = ownerDocument.createElementNS(svg, "a");
|
||||
const href = anchor.href;
|
||||
assert(href === anchor.href, "href is SameObject");
|
||||
|
||||
anchor.setAttributeNS(xlink, "href", "xlink-unprefixed");
|
||||
assert(href.baseVal === "xlink-unprefixed", "unprefixed XLink fallback");
|
||||
assert(href.animVal === "xlink-unprefixed", "XLink animVal fallback");
|
||||
href.baseVal = "xlink-updated";
|
||||
assert(anchor.getAttributeNS(xlink, "href") === "xlink-updated",
|
||||
"baseVal updates the XLink attribute");
|
||||
assert(!anchor.hasAttributeNS(null, "href"),
|
||||
"XLink update does not create href");
|
||||
|
||||
anchor.setAttributeNS(null, "href", "preferred");
|
||||
assert(href.baseVal === "preferred", "href wins regardless of insertion order");
|
||||
href.baseVal = "preferred-updated";
|
||||
assert(anchor.getAttributeNS(null, "href") === "preferred-updated",
|
||||
"baseVal updates preferred href");
|
||||
assert(anchor.getAttributeNS(xlink, "href") === "xlink-updated",
|
||||
"preferred href leaves XLink unchanged");
|
||||
|
||||
anchor.removeAttributeNS(null, "href");
|
||||
assert(href.baseVal === "xlink-updated", "removing href restores fallback");
|
||||
anchor.removeAttributeNS(xlink, "href");
|
||||
assert(href.baseVal === "" && href.animVal === "", "removing fallback resets values");
|
||||
|
||||
anchor.setAttributeNS(xlink, "xlink:href", "xlink-prefixed");
|
||||
assert(href.baseVal === "xlink-prefixed", "prefixed XLink fallback");
|
||||
href.baseVal = "xlink-prefixed-updated";
|
||||
assert(anchor.getAttributeNS(xlink, "href") === "xlink-prefixed-updated",
|
||||
"baseVal updates prefixed XLink attribute");
|
||||
assert(anchor.getAttributeNames().includes("xlink:href"),
|
||||
"baseVal preserves the XLink prefix");
|
||||
|
||||
anchor.removeAttributeNS(xlink, "href");
|
||||
anchor.setAttributeNS(unrelated, "href", "unrelated");
|
||||
assert(href.baseVal === "", "unrelated namespaced href is ignored");
|
||||
href.baseVal = "created";
|
||||
assert(anchor.getAttributeNS(null, "href") === "created",
|
||||
"baseVal creates an unnamespaced href");
|
||||
assert(anchor.getAttributeNS(unrelated, "href") === "unrelated",
|
||||
"baseVal leaves unrelated namespaced href unchanged");
|
||||
|
||||
anchor.removeAttributeNS(null, "href");
|
||||
anchor.setAttributeNS(xlink, "xlink:href", "side-effect-fallback");
|
||||
href.baseVal = {
|
||||
toString() {
|
||||
anchor.setAttributeNS(null, "href", "created-during-conversion");
|
||||
return "converted";
|
||||
}
|
||||
};
|
||||
assert(anchor.getAttributeNS(null, "href") === "converted",
|
||||
"baseVal chooses its backing attribute after value conversion");
|
||||
assert(anchor.getAttributeNS(xlink, "href") === "side-effect-fallback",
|
||||
"conversion-created href leaves XLink fallback unchanged");
|
||||
};
|
||||
|
||||
exercise(document);
|
||||
exercise(document.implementation.createHTMLDocument(""));
|
||||
return "ok";
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("SVG href reflection should evaluate");
|
||||
|
||||
assert_eq!(result, "ok");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user