From 3a4e3f7d7dc7d9f3f263fe6ddaea01d3680e7344 Mon Sep 17 00:00:00 2001 From: ulises2k Date: Sat, 25 Jul 2026 22:30:38 -0300 Subject: [PATCH] fix(js,dom): keep parsed SVG in the SVG namespace so href reflects an SVGAnimatedString get namespaceURI() derived the namespace from the tag name and only ever recognised the root, so every element parsed inside an subtree reported the XHTML namespace. get href() gates the SVGAnimatedString reflection on the SVG namespace, so for parsed content the gate never opened: href returned a plain string and .animVal/.baseVal were undefined, making new URL(el.href.animVal) throw. Only createElementNS-built elements worked, because createElementNS records _ns explicitly. The tree builder already applies the HTML5 foreign-content rules, so QualName.ns is correct for parsed SVG; it just was not exposed to JS. Add a namespace_uri DOM op and read it in get namespaceURI(), keeping _ns as the override and the old tag-name heuristic as the fallback when no element name is recorded. Namespaces are immutable, so the result is cached like _lname. Fixes #484 --- crates/obscura-js/js/bootstrap.js | 18 ++++++++++++++---- crates/obscura-js/src/ops.rs | 8 ++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/crates/obscura-js/js/bootstrap.js b/crates/obscura-js/js/bootstrap.js index 4a0f53f..3ef371a 100644 --- a/crates/obscura-js/js/bootstrap.js +++ b/crates/obscura-js/js/bootstrap.js @@ -1210,11 +1210,21 @@ class Element extends Node { set className(v) { this.setAttribute("class", v); } get namespaceURI() { // createElementNS records the requested namespace on _ns; an empty string - // maps to the null namespace per spec. Elements made via createElement (or - // parsed) have no _ns: default to XHTML, except which is SVG. + // maps to the null namespace per spec. if (this._ns !== undefined) return this._ns === "" ? null : this._ns; - if (this.localName === "svg") return "http://www.w3.org/2000/svg"; - return "http://www.w3.org/1999/xhtml"; + // Otherwise use the namespace the HTML tree builder assigned. Foreign + // content puts the WHOLE / subtree in that namespace, not just + // the root, so deriving it from the tag name (the old `localName === "svg"` + // check) left every descendant looking like HTML and skipped the SVG-only + // reflections -- notably `get href()`, which then returned a plain string + // instead of an SVGAnimatedString. An element's namespace never changes, + // so cache it like _lname. + if (this._nsCache !== undefined) return this._nsCache; + let ns = _domParse("namespace_uri", this._nid) || ""; + // Nodes with no element name recorded fall back to the previous heuristic. + if (!ns) ns = this.localName === "svg" ? "http://www.w3.org/2000/svg" : "http://www.w3.org/1999/xhtml"; + this._nsCache = ns; + return ns; } // `inner_html` resolves a