<?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>314992</bug_id>
          
          <creation_ts>2026-05-17 15:18:51 -0700</creation_ts>
          <short_desc>SVGTransformList re-parse mutates existing same-type items in place, violating SVG2  SVGTransform.matrix [SameObject] semantics</short_desc>
          <delta_ts>2026-09-30 20:07:02 -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>SVG</component>
          <version>WebKit Nightly Build</version>
          <rep_platform>Unspecified</rep_platform>
          <op_sys>Unspecified</op_sys>
          <bug_status>RESOLVED</bug_status>
          <resolution>WONTFIX</resolution>
          
          <see_also>https://bugs.webkit.org/show_bug.cgi?id=207421</see_also>
    
    <see_also>https://bugs.webkit.org/show_bug.cgi?id=322973</see_also>
    
    <see_also>https://bugs.webkit.org/show_bug.cgi?id=322974</see_also>
    
    <see_also>https://bugs.webkit.org/show_bug.cgi?id=322976</see_also>
          <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>
          
          
          <everconfirmed>1</everconfirmed>
          <reporter name="Ahmad Saleem">ahmad.saleem792</reporter>
          <assigned_to name="Nobody">webkit-unassigned</assigned_to>
          <cc>karlcow</cc>
    
    <cc>sabouhallawa</cc>
    
    <cc>webkit-bug-importer</cc>
    
    <cc>zimmermann</cc>
          

      

      

      

          <comment_sort_order>oldest_to_newest</comment_sort_order>  
          <long_desc isprivate="0" >
    <commentid>2211577</commentid>
    <comment_count>0</comment_count>
    <who name="Ahmad Saleem">ahmad.saleem792</who>
    <bug_when>2026-05-17 15:18:51 -0700</bug_when>
    <thetext>Summary

  Re-parsing a reflected SVG transform attribute (e.g. setAttribute(&quot;transform&quot;, …)) mutates an
  existing SVGTransform in the list in place when the new token has the same transform type at the
  same index. Per SVG 2, list items should be detached on re-parse and retain their pre-reparse
  values, with [SameObject] SVGTransform.matrix tear-offs continuing to reflect the (now-detached)
  SVGTransform. WebKit&apos;s in-place mutation makes a script-held tear-off observably switch values,
  which the spec disallows.

  This is not a regression from 294342@main (Remove some of the ref-counting churns when parsing
  SVGTransformList) — that commit refactored the existing in-place-reuse path into two helpers
  (parseAndReplaceTransform / parseTransform) without changing semantics.

  Affected APIs

  The bug affects every reflected SVGTransformList attribute that goes through
  SVGTransformList::parse(StringView) from attributeChanged:
  - transform on SVGGraphicsElement
  - gradientTransform on SVGGradientElement
  - patternTransform on SVGPatternElement

  ---
  Spec

  - SVG 2 — SVGTransform interface, matrix IDL:
  https://svgwg.org/svg2-draft/coords.html#InterfaceSVGTransform
  ▎ [SameObject] readonly attribute DOMMatrix matrix;
  ▎ &quot;When the matrix object is first created, its values are set to match the SVGTransform&apos;s transform
   function value, and is set to reflect the SVGTransform.&quot;
  - SVG 2 — DOMMatrix extensions for reflecting SVGTransform:
  https://svgwg.org/svg2-draft/coords.html#DOMMatrixExtensions
  ▎ &quot;Every DOMMatrix object operates in one of two modes. It can: reflect an SVGTransform … or be
  detached …&quot;
  ▎ Mutating a reflecting matrix updates the SVGTransform&apos;s value; if the SVGTransform reflects an
  element of a presentation attribute, the host attribute is reserialized.
  - SVG 2 — list interface &quot;detached&quot; definition:
  https://svgwg.org/svg2-draft/types.html#TermDetachedObject
  ▎ &quot;A detached object retains its current values…&quot;

  ---
  Minimal reproduction

  &lt;!DOCTYPE html&gt;
  &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
    &lt;g id=&quot;g&quot; transform=&quot;translate(10, 20)&quot;&gt;&lt;/g&gt;
  &lt;/svg&gt;
  &lt;script&gt;
  const g = document.getElementById(&quot;g&quot;);
  const t = g.transform.baseVal.getItem(0);
  const m = t.matrix;     // [SameObject] tear-off, reflects t

  console.log(m.e, m.f);  // 10 20

  g.setAttribute(&quot;transform&quot;, &quot;translate(500, 600)&quot;);
  // Per SVG2: t is detached, retains its old value. m still reflects t.
  // Per SVG2: g.transform.baseVal.getItem(0) is a *new* SVGTransform.

  console.log(m.e, m.f);                                  // EXPECTED: 10 20
  console.log(g.transform.baseVal.getItem(0).matrix.e);   // EXPECTED: 500
  console.log(t.matrix === m);                            // EXPECTED: true ([SameObject])
  &lt;/script&gt;

  Expected output (per spec, also produced by Firefox/WebKit only after this fix):
  10 20
  10 20
  500
  true

  Actual output (current WebKit, also Firefox):
  10 20
  500 600        &lt;-- BUG: detached SVGTransform&apos;s value was mutated under the script&apos;s feet
  500
  true

  A type-changing reparse (setAttribute(&quot;transform&quot;, &quot;matrix(1,0,0,1,500,600)&quot;)) does not exhibit the
  bug, because the type-mismatch branch in parseGeneric already takes the spec-conformant
  detach-and-insert path. This narrows the bug precisely to the same-type-same-index fast path.

  ---
  Real-world symptom

  A common drag-handler pattern that captures getItem(0).matrix once at mousedown and re-reads it on
  every mousemove is silently corrupted: because the captured tear-off shares identity with the one
  re-read in the move handler, writes alias each other, and after each setAttribute the captured
  &quot;anchor&quot; matrix advances with the live value. The dragged element accelerates off-screen. Repro at
  https://jsfiddle.net/8xvobd43/3/. Where trying to drag makes `rect` go out of the view (in Safari &amp; Firefox).

  ---
  Source

  Source/WebCore/svg/SVGTransformList.cpp:138 — the same-type fast path:

  if (currentListReplacement == ListReplacement::Replace &amp;&amp; itemIndex &lt; m_items.size()
      &amp;&amp; parsedTransformType == m_items[itemIndex]-&gt;type()) {
      if (!parseAndReplaceTransform(*parsedTransformType, buffer, m_items[itemIndex]))
          return false;
  } else {
      // Switch to `Append` mode and remove the existing SVGTransforms starting from `itemIndex`.
      ...
  }

  The else branch already does the right thing (resize(itemIndex) detaches existing items, then a
  fresh SVGTransform is appended). The if branch should be removed; every iteration should take the
  detach-and-append path. Removing the fast path also makes parseAndReplaceTransform dead code.

 ---
  Proposed fix

  Drop the same-type fast path in parseGeneric, always detach existing items on the first iteration of
   a Replace parse, and remove the now-unused parseAndReplaceTransform (declaration in
  SVGTransformList.h, definition in SVGTransformListInlines.h).</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>2213711</commentid>
    <comment_count>1</comment_count>
    <who name="Radar WebKit Bug Importer">webkit-bug-importer</who>
    <bug_when>2026-05-24 15:19:10 -0700</bug_when>
    <thetext>&lt;rdar://problem/177837909&gt;</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>2244641</commentid>
    <comment_count>2</comment_count>
    <who name="Karl Dubost">karlcow</who>
    <bug_when>2026-08-27 20:04:00 -0700</bug_when>
    <thetext>TLDR:

I suggest closing this and filing the reverse bugs for SVGNumberList, SVGLengthList and SVGPointList.

Details:

SVG2 doesn&apos;t seem to request this.
A re-parse detaches list items only when the list shrinks, and for this case the algorithm explicitly updates the matrix object the script is holding.

Taking the repro from comment 0:

  &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
    &lt;g id=&quot;g&quot; transform=&quot;translate(10, 20)&quot;&gt;&lt;/g&gt;
  &lt;/svg&gt;

  const g    = document.getElementById(&quot;g&quot;);
  const list = g.transform.baseVal;   // the list interface object
  const t    = list.getItem(0);       // the SVGTransform at index 0
  const m    = t.matrix;              // its matrix object

  g.setAttribute(&quot;transform&quot;, &quot;translate(500, 600)&quot;);


That last line changes the base value of a reflected content attribute, which triggers:

&quot;Whenever a reflected content attribute&apos;s base value changes, then the reflecting object must be synchronized, immediately after the value changed&quot;
step 1: &quot;If the reflecting object is a list interface object, then run the steps for synchronizing a list interface object.&quot;
https://w3c.github.io/svgwg/svg2-draft/types.html#SynchronizingReflectedValues


Walking those steps against the example:
https://w3c.github.io/svgwg/svg2-draft/types.html#TermSynchronizeList



  step 1   value is translate(500, 600)
  step 2   length is 1, the list holds one SVGTransform, which is t
  step 3   new length is 1
  step 4   the element type is SVGTransform, so run 4.1 to 4.4

  step 4.1 &quot;If length &gt; new length&quot; is 1 &gt; 1, false. THIS IS THE ONLY DETACH IN THE ALGORITHM AND IT DOES NOT RUN.
  step 4.2 &quot;While length &lt; new length&quot; is 1 &lt; 1, false. No object is created, so index 0 still holds t.
  step 4.3 index is 0
  step 4.4 index &lt; length, so:
             4.4.1  &quot;Let item be the object in the list at index index&quot;  -&gt; item is t, the object the script holds
             4.4.2  &quot;Let v be the value in value at index index&quot;         -&gt; v is translate(500, 600)
             4.4.3  &quot;Set item&apos;s value to v&quot;                             -&gt; t&apos;s value becomes translate(500, 600)
             4.4.4  &quot;If item is an SVGTransform object, then set the components of its matrix object to match the new transform function value&quot;

Step 4.4.4 is the whole question. It says set the components of ITS matrix object, which is m. Not replace m, not leave m alone: set its components. So after the setAttribute:

  list.getItem(0) === t;   // true,  nothing was detached (4.1) and nothing was created (4.2)
  t.matrix === m;          // true,  4.4.4 updates this matrix object rather than replacing it
  m.e;                     // 500,   4.4.4 set its components

Step 5, &quot;Replace the list with a new list&quot;, is the DOMString branch and is not reached.

Measured, attached testcase, the same observable on all four object-typed lists:

                    in place (conformant)   detached
STP 251                    1 of 4              3
Firefox 156                4 of 4              0
Chrome 154                 0 of 4              4

Chrome prints the expected output listed in comment 0, including the 10 20 on the second read. No other engine does.

No WPT test covers this. Nothing in the corpus holds a list item across a re-parse, in either direction. The near misses all re-fetch instead of holding, so they pass under both readings: SVGTransformList.list-01.html only sets the attribute during setup, SVGLengthList-basics.html sets x then re-reads getItem(0).value, and the SMIL list tests re-read animVal.getItem(i).value at every sample.

There is one test that asserts the reading in comment 0, and it is ours, not WPT: LayoutTests/svg/dom/SVGLengthList-xml-dom-modifications.xhtml, added 2010-10-21. It holds all three items of x=&quot;500 1000 1500&quot;, sets x=&quot;50 100&quot;, and asserts under &quot;Assure that the wrappers still point to the OLD values&quot; that all three keep their old values.

That case is a shrink, 3 items to 2, so step 4.1 does run. It detaches &quot;each object in the list at an index greater than or equal to new length&quot;, which is index 2 only. Indices 0 and 1 survive the truncation and step 4.4 then sets their values to 50 and 100. So that test is right about item 2 and wrong about items 0 and 1, and it needs a partial rebaseline rather than deletion.

The one list WebKit gets right is SVGTransformList, which is the one this bug proposes to change. Dropping the same-type fast path would take WebKit to 0 of 4 and away from Firefox. The real defect is the other three: rotate, x and points detach where they should update, so a script holding an item silently keeps a stale value.

On the drag handler: &quot;An SVGTransform object&apos;s matrix object is always kept synchronized with its value&quot;, https://w3c.github.io/svgwg/svg2-draft/coords.html#TransformMatrixObject, which is the same statement as 4.4.4 from the other direction. getItem(0).matrix is a live handle rather than an anchor. Copying the numbers at mousedown is the fix on the page side.

Suggest closing this and filing the reverse for SVGNumberList, SVGLengthList and SVGPointList.</thetext>
  </long_desc>
      
      

    </bug>

</bugzilla>