<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE bugzilla SYSTEM "https://bugs.webkit.org/page.cgi?id=bugzilla.dtd">

<bugzilla version="5.0.4.1"
          urlbase="https://bugs.webkit.org/"
          
          maintainer="admin@webkit.org"
>

    <bug>
          <bug_id>320108</bug_id>
          
          <creation_ts>2026-07-23 11:17:30 -0700</creation_ts>
          <short_desc>[SVG] &lt;use&gt; shadow tree strips &lt;defs&gt; and other non-rendered elements, breaking selector matching against the instance tree</short_desc>
          <delta_ts>2026-08-18 23:00:01 -0700</delta_ts>
          <reporter_accessible>1</reporter_accessible>
          <cclist_accessible>1</cclist_accessible>
          <classification_id>1</classification_id>
          <classification>Unclassified</classification>
          <product>WebKit</product>
          <component>New Bugs</component>
          <version>WebKit Nightly Build</version>
          <rep_platform>Unspecified</rep_platform>
          <op_sys>Unspecified</op_sys>
          <bug_status>NEW</bug_status>
          <resolution></resolution>
          
          
          <bug_file_loc></bug_file_loc>
          <status_whiteboard></status_whiteboard>
          <keywords>InRadar</keywords>
          <priority>P2</priority>
          <bug_severity>Normal</bug_severity>
          <target_milestone>---</target_milestone>
          <dependson>320107</dependson>
          <blocked>43911</blocked>
    
    <blocked>314320</blocked>
          <everconfirmed>1</everconfirmed>
          <reporter name="Taher">taher_ali</reporter>
          <assigned_to name="Taher">taher_ali</assigned_to>
          <cc>karlcow</cc>
    
    <cc>webkit-bug-importer</cc>
          

      

      

      

          <comment_sort_order>oldest_to_newest</comment_sort_order>  
          <long_desc isprivate="0" >
    <commentid>2233314</commentid>
    <comment_count>0</comment_count>
    <who name="Taher">taher_ali</who>
    <bug_when>2026-07-23 11:17:30 -0700</bug_when>
    <thetext>ssia</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>2233315</commentid>
    <comment_count>1</comment_count>
    <who name="Radar WebKit Bug Importer">webkit-bug-importer</who>
    <bug_when>2026-07-23 11:18:31 -0700</bug_when>
    <thetext>&lt;rdar://problem/183045549&gt;</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>2241682</commentid>
    <comment_count>2</comment_count>
    <who name="Karl Dubost">karlcow</who>
    <bug_when>2026-08-18 22:29:00 -0700</bug_when>
    <thetext>For future readers:

It is not a spec issue or a performance issue. 
The origin was a batch of fuzzer crashes in 2012.

CODE TODAY

Source/WebCore/svg/SVGUseElement.cpp:223-258
https://github.com/WebKit/WebKit/blob/7247fcb1ca9690c80edd9cba7cbe915418987484/Source/WebCore/svg/SVGUseElement.cpp#L223-L258 

`isDisallowedElement()`. 
    A `switch` over 21 element names that returns &quot;allowed&quot;; everything else falls to `default: return true`, meaning disallowed.

Allowed: 
    `a circle desc ellipse g image line metadata path polygon polyline rect svg switch symbol text textPath title tref tspan use`

Applied at :416-437 (removeDisallowedElementsFromSubtree), 
-&gt; called from cloneTarget at :533 and from cloneDataAndChildren at :548. 
It runs on every &lt;use&gt;, same-document and cross-document alike. 
Also used at :510 to reject the target itself.

Everything in SVG 2 that is NOT on that list is deleted from the clone:

clipPath
defs
filter 
foreignObject
linearGradient
marker
mask
pattern
radialGradient
script
stop
style
view
and all 25 filter primitives (`feBlend`, `feGaussianBlur`, …), 
plus the SVG Animations elements (`animate`, `set`, `animateMotion`, `animateTransform`, `mpath`, `discard`), which are not in SVG 2&apos;s own element list but are equally absent from the 21.

`tref` is a dead entry. SVG 2 removed the element, WebKit still has the class and still lists it.

Onto the WHY in the next comment.</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>2241687</commentid>
    <comment_count>3</comment_count>
    <who name="Karl Dubost">karlcow</who>
    <bug_when>2026-08-18 22:41:01 -0700</bug_when>
    <thetext>WHY THIS DISALLOW/ALLOW LIST?

Five commits touch this function, ever. 
git log --follow -S isDisallowedElement -- Source/WebCore/svg/SVGUseElement.cpp

2007-10-12: 
ddfe0ae70e5f, oliver, bug 13611 / bug 14631
isDisallowedElement is born as a denylist of exactly one element:

static bool isDisallowedElement(Node* element)
{
    // &lt;foreignObject&gt; should never be contained in a &lt;use&gt; tree. Too dangerous side effects possible.
    if (element-&gt;hasTagName(SVGNames::foreignObjectTag))
        return true;
    return false;
}

That is the entire original intent: keep &lt;foreignObject&gt; out, because arbitrary HTML inside a cloned tree was thought unsafe. Later a line was added for SMIL animation elements. No spec citation, no performance claim.



2012-02-29:
af16cc1c8cf3, Stephen Chenney (schenney@chromium.org), bug 77764
This is the commit that created today&apos;s behaviour. It inverted the one-element denylist into the 21-name allowlist, unchanged in membership ever since. Its own stated reason, verbatim from the ChangeLog:

&gt; Modify the isDisallowedElement method to disallow all of the disallowed 
&gt; elements, instead of just a few. It is now a whitelist implementation.
&gt; 
&gt; This also fixes bug 78807, bug 78838 and bug 79798 related to 
&gt; memory corruption issues.
&gt;
&gt; These test all use invalid elements in the &lt;use&gt; and crash in the absence 
&gt; of this patch.

Bug 77764 and all three crash bugs are still security-restricted today. They return &quot;You are not authorized to access bug #77764&quot;, 

So the public record of why is only this commit message plus the three tests it landed. Those tests are readable. All three put a &lt;style&gt; element inside the referenced subtree and combine it with editing or counters. That is the real defect class: an element with a document-wide side effect (registering a stylesheet) being cloned into a non-document tree. &lt;glyph&gt;(an SVG Fonts element WebKit has since removed) is the other one.

So the allowlist is a wide net thrown to catch a narrow class of bugs. Two elements were actually implicated. Nineteen-plus categories were removed.



2012-05-01:
5a99f824e7af, bug 85202
Reuses the predicate to skip building the instance tree for a disallowed target. No membership change.



2015-01-26 / 2015-02-08 / 2015-02-12:
b6027563341b, 257551de9b73, 8a4c131c2266
Darin Adler&apos;s three-part rewrite: bugs 140875, bug 141374, bug 141382. 
* SVGElementInstance is deleted entirely and the shadow tree becomes the only representation. 
* subtreeContainsDisallowedElement (a scan-then-bail) is replaced by removeDisallowedElementsFromSubtree (clone, then delete), 
* and the data structure goes HashSet&lt;String&gt; → HashSet&lt;QualifiedName&gt; → a static array loop → today&apos;s switch.

Membership: unchanged, all three times. Nobody re-examined the list; it was carried through as-is.


2026:
unchanged
isDisallowedElement is byte-identical between my checkout HEAD and origin/main (0 commits behind, verified today). SVGUseElement.cpp&apos;s most recent change is a mechanical smart-pointer sweep.


The 2012 crashes were crashes in the SVGElementInstance machinery.
That machinery no longer exists. 
The reason the list was widened in 2012 was deleted in 2015.</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>2241688</commentid>
    <comment_count>4</comment_count>
    <who name="Karl Dubost">karlcow</who>
    <bug_when>2026-08-18 22:49:17 -0700</bug_when>
    <thetext>SPEC PROSE on all of this.

SVG 1.1

The comment misreads SVG 1.1
The comment above the allowlist, in both WebKit and Blink, opens with:

&gt; &quot;Any &apos;svg&apos;, &apos;symbol&apos;, &apos;g&apos;, graphics element or other &apos;use&apos; is potentially 
&gt; a template object that can be re-used (i.e., &quot;instanced&quot;) 
&gt; in the SVG document via a &apos;use&apos; element.&quot;

That sentence is real. It is the first sentence of the descriptive lead-in to SVG 1.1 §5.6. But it describes what a &lt;use&gt; may usefully point at. It is not a conformance requirement, and it says nothing about what may sit inside the thing pointed at. The very next paragraph of the same section says the opposite of filtering:

&gt; The effect of a &apos;use&apos; element is as if the contents of the referenced element 
&gt; were deeply cloned into a separate non-exposed DOM tree

and §5.11.9 says nested references produce 

&gt; &quot;recursive expansion of the indirect references to form a complete tree&quot;. 

There is no restriction on shadow-tree contents in SVG 1.1.


SVG 2

SVG 2 removed the restriction explicitly
SVG 2 5.5.1 The use-element shadow tree, a note:

&gt; Previous versions of SVG restricted the contents of the shadow tree 
&gt; to SVG graphics elements. This specification allows any valid SVG document subtree 
&gt; to be cloned. 
&gt; Cloning non-graphical content, however, will not usually have any visible effect.

And immediately above it, 

&gt; Within a use-element shadow tree, &apos;script&apos; elements are inert (do not execute).

SVG 2 expects &lt;script&gt; to be cloned and then neutered, not deleted. WebKit deletes it.


SVG 2 handles &lt;script&gt; and says nothing about anything else that can execute code or load a resource. 
Emilio filed svgwg#876 on 2022-02-25, &quot;Clarify &lt;iframe&gt; behavior in &lt;svg:use&gt;&quot;:
https://github.com/w3c/svgwg/issues/876

&gt; But &lt;iframe&gt; isn&apos;t, which means I can load an &lt;iframe&gt; and execute script.

Open, zero replies, four and a half years later. So the 2007 comment&apos;s worry (&quot;Too dangerous side effects possible&quot;) still needs an answer from the WG.  &lt;use&gt; adding &lt;foreignObject&gt; is an interesting issue too.</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>2241695</commentid>
    <comment_count>5</comment_count>
    <who name="Karl Dubost">karlcow</who>
    <bug_when>2026-08-18 22:57:03 -0700</bug_when>
    <thetext>Other Browsers:

BLINK

the same list, minus one
third_party/blink/renderer/core/svg/svg_use_element.cc:292-318. 
Same comment, same 20 names (no tref, which Blink removed from the engine), same unconditional pruning at :445 and :488. It is literally the same code: Chenney&apos;s 2012 patch landed in shared WebKit before the fork, and git log -S allowed_element_tags in Chromium finds only the 2018 file move.

* Blink just finished modernising &lt;use&gt; shadow trees for SVG 2. 
  Svg2Cascade shipped to stable in M144 and the flag was deleted on 2026-03-06 (31915b25f73c0, Chromium bug 40550039). Part of that work was 1cadd18aaa22f (2025-11-05), &quot;Stop instantiating &lt;symbol&gt; as &lt;svg&gt; in use shadow trees&quot; 

* They went through all of that and left the allowlist alone. So the allowlist is not something Blink has decided to keep on the merits; it is something a deliberate SVG 2 pass walked past.


GECKO

dom/svg/SVGUseElement.cpp. Three differences that matter:

1. The predicate is a class test, not a name list. IsForbiddenUseNode, :344-350 is !svg || !svg-&gt;IsSVGGraphicsElement(). Which means Gecko&apos;s kept-set is the SVGGraphicsElement subclasses: 
* a 
* defs 
* foreignObject 
* g + geometry (circle ellipse line path polygon polyline rect) + image + text content (text tspan textPath) + switch + viewport (svg symbol) + use.

The two sets cross over. 
Gecko keeps defs and foreignObject ( bug 320108 and bug 43911 ). 
WebKit keeps desc, title, metadata, which Gecko drops. Neither is a subset of the other.

2. It is behind a pref, svg.use-element.graphics-element-restrictions, default 1 = restrict cross-document only. 
So for same-document &lt;use&gt; (the common case, every icon sprite in a page) Gecko filters nothing at all and has shipped that way for years. The permissive behaviour is already in the field.

3. They wrote down the reasoning. The comment above RemoveForbiddenNodes, :366-386 quotes the SVG 1.1 sentence, quotes the SVG 2 note that lifted it, cites svgwg#876, and ends &quot;So, fairly confusing, all-in-all.&quot; The pref comment says value 0 &quot;maps to what SVG 2 seems to call for&quot;, value 2 to &quot;SVG 1.1 / the behavior of other UAs&quot;, and that the default 1 is &quot;a compromise&quot;.

The commit is 23030960cbda, Emilio Cobos Álvarez, 2022-02-26, Gecko bug 1754522, &quot;Limit cross-document content loadable via &lt;svg:use&gt;&quot;:

&gt; It seems Blink / WebKit run this even if in the same document, 
&gt; but it seems less-potentially-breaking to restrict this to the cross-document case.

So Gecko&apos;s restriction was added for cross-origin/cross-document security, in 2022, and deliberately not extended to same-document content.


INKSCAPE

prunes nothing

 src/object/sp-use.cpp, SPUse::href_changed gates only the top-level target (must cast to SPItem) and never filters descendants. As an SVG Generator (SVG 2 1.4) it is not an authority on viewer behaviour, but it tells us what an authoring tool emits and expects.</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>2241696</commentid>
    <comment_count>6</comment_count>
    <who name="Karl Dubost">karlcow</who>
    <bug_when>2026-08-18 23:00:01 -0700</bug_when>
    <thetext>WPT 

* svg/struct/reftests/use-foreignObject.html (WPT) requires a &lt;symbol&gt; containing a &lt;foreignObject&gt; to render green
* svg/linking/reftests/use-descendant-combinator-003.html requires that a defs … selector not match an instance.


Adding &lt;defs&gt; back to the allowlist will not, on its own, change any selector result. Bug 320107 (the styling/scoping bug) has to land first, or the two changes have to go together.



Basically it would be possible to add the elements back.</thetext>
  </long_desc>
      
      

    </bug>

</bugzilla>