js: implement SVGAnimatedString for SVG className and href (#330)
SVG className and href reflected as plain strings, so el.className.animVal and el.href.animVal were undefined, which made new URL(svgEl.href.animVal) throw and broke any SVG-aware script. Add an SVGAnimatedString class (baseVal/animVal both read the live attribute; baseVal writable) and make the className and href getters return it for SVG-namespace elements. href falls back to the legacy xlink:href. HTML elements are unchanged.
This commit is contained in:
1 parent
505738450a
commit
117415848e
1 file changed
+44
-1
Vendored
+44
-1
@@ -959,7 +959,15 @@ class Element extends Node {
|
|||||||
}
|
}
|
||||||
get id() { return this.getAttribute("id") || ""; }
|
get id() { return this.getAttribute("id") || ""; }
|
||||||
set id(v) { this.setAttribute("id", v); }
|
set id(v) { this.setAttribute("id", v); }
|
||||||
get className() { return this.getAttribute("class") || ""; }
|
get className() {
|
||||||
|
// SVG elements reflect class as an SVGAnimatedString (.baseVal/.animVal),
|
||||||
|
// not a plain string. Anti-fraud sensors read el.className.animVal.
|
||||||
|
if (this.namespaceURI === "http://www.w3.org/2000/svg") {
|
||||||
|
if (!this._svgClassName) this._svgClassName = new SVGAnimatedString(this, "class");
|
||||||
|
return this._svgClassName;
|
||||||
|
}
|
||||||
|
return this.getAttribute("class") || "";
|
||||||
|
}
|
||||||
set className(v) { this.setAttribute("class", v); }
|
set className(v) { this.setAttribute("class", v); }
|
||||||
get namespaceURI() {
|
get namespaceURI() {
|
||||||
// createElementNS records the requested namespace on _ns; an empty string
|
// createElementNS records the requested namespace on _ns; an empty string
|
||||||
@@ -1581,6 +1589,16 @@ class Element extends Node {
|
|||||||
// members use. Other elements reflect the raw attribute.
|
// members use. Other elements reflect the raw attribute.
|
||||||
get href() {
|
get href() {
|
||||||
const ln = this.localName;
|
const ln = this.localName;
|
||||||
|
// SVG href-bearing elements reflect href as an SVGAnimatedString (with the
|
||||||
|
// legacy xlink:href as a fallback), not a resolved URL string. Checked
|
||||||
|
// before the HTML <a> path because an SVG <a> also has localName 'a'.
|
||||||
|
if (this.namespaceURI === "http://www.w3.org/2000/svg" &&
|
||||||
|
(ln === 'a' || ln === 'image' || ln === 'use' || ln === 'script' ||
|
||||||
|
ln === 'pattern' || ln === 'filter' || ln === 'textPath' || ln === 'mpath' ||
|
||||||
|
ln === 'linearGradient' || ln === 'radialGradient' || ln === 'feImage' || ln === 'tref')) {
|
||||||
|
if (!this._svgHref) this._svgHref = new SVGAnimatedString(this, "href", "xlink:href");
|
||||||
|
return this._svgHref;
|
||||||
|
}
|
||||||
if (ln === 'a' || ln === 'area') {
|
if (ln === 'a' || ln === 'area') {
|
||||||
const raw = this.getAttribute('href');
|
const raw = this.getAttribute('href');
|
||||||
if (raw === null) return '';
|
if (raw === null) return '';
|
||||||
@@ -4979,6 +4997,31 @@ globalThis.HTMLLegendElement = Element;
|
|||||||
globalThis.HTMLProgressElement = Element;
|
globalThis.HTMLProgressElement = Element;
|
||||||
globalThis.HTMLDetailsElement = Element;
|
globalThis.HTMLDetailsElement = Element;
|
||||||
globalThis.HTMLDialogElement = Element;
|
globalThis.HTMLDialogElement = Element;
|
||||||
|
// SVGAnimatedString backs the className and href reflections on SVG elements.
|
||||||
|
// baseVal and animVal both read the live attribute (no SMIL animation), and
|
||||||
|
// baseVal is writable. Used by the SVG-aware get className()/get href() above.
|
||||||
|
function SVGAnimatedString(el, attr, fallbackAttr) {
|
||||||
|
this._el = el;
|
||||||
|
this._attr = attr;
|
||||||
|
this._fallback = fallbackAttr || null;
|
||||||
|
}
|
||||||
|
SVGAnimatedString.prototype._read = function() {
|
||||||
|
let v = this._el.getAttribute(this._attr);
|
||||||
|
if (v === null && this._fallback) v = this._el.getAttribute(this._fallback);
|
||||||
|
return v == null ? '' : v;
|
||||||
|
};
|
||||||
|
Object.defineProperty(SVGAnimatedString.prototype, 'baseVal', {
|
||||||
|
get() { return this._read(); },
|
||||||
|
set(v) { this._el.setAttribute(this._attr, String(v)); },
|
||||||
|
configurable: true, enumerable: true,
|
||||||
|
});
|
||||||
|
Object.defineProperty(SVGAnimatedString.prototype, 'animVal', {
|
||||||
|
get() { return this._read(); },
|
||||||
|
configurable: true, enumerable: true,
|
||||||
|
});
|
||||||
|
Object.defineProperty(SVGAnimatedString.prototype, Symbol.toStringTag, { value: 'SVGAnimatedString', configurable: true });
|
||||||
|
_markNative(SVGAnimatedString);
|
||||||
|
|
||||||
globalThis.SVGElement = Element;
|
globalThis.SVGElement = Element;
|
||||||
globalThis.SVGSVGElement = Element;
|
globalThis.SVGSVGElement = Element;
|
||||||
globalThis.CharacterData = CharacterData;
|
globalThis.CharacterData = CharacterData;
|
||||||
|
|||||||
Reference in new issue
Block a user