Repository: WebKit/WebKit
Pull request #42822: Make parsing SVG transform lists more efficient
Author: login: smfr
Labels: name: SVG
description: For bugs in the SVG implementation.
Created: 2025-03-21T17:34:05Z | Merged: 2025-03-22T21:20:21Z

#### bef74b7519fb97bcf4bc14a23641412ec593b926
<pre>
Make parsing SVG transform lists more efficient
<a href="https://bugs.webkit.org/show_bug.cgi?id=290192">https://bugs.webkit.org/show_bug.cgi?id=290192</a>
<a href="https://rdar.apple.com/147591008">rdar://147591008</a>

Reviewed by Said Abou-Hallawa.

Content that animates SVG transforms will often keep the same list of transform operations, but just
change the values. Currently, `SVGTransformList::parse()` implements this by clearing the list and
re-build it, but this involves a lot of object churn: each SVGTransform is heap-allocated, and itself
has a heap-allocated SVGMatrix. In addition, `detach()` and `attach()` have overhead.

Make this more efficient by re-using the existing SVGTransforms when possible. Do this by moving the
list management into `SVGTransformList::parseGeneric()`. Callers of `parse()` expect the list to be preserved
between calls, so the `ListReplacement` argument controls that behavior. Pass the existing object to
`SVGTransformable::parseTransform` if the type matches; we compare that with the return value to know
if the item was re-used. If not, we go back to appending.

Well tested by existing SVG tests.

* Source/WebCore/svg/SVGTransformList.cpp:
(WebCore::SVGTransformList::parseGeneric):
(WebCore::SVGTransformList::parse):
* Source/WebCore/svg/SVGTransformList.h:
* Source/WebCore/svg/SVGTransformable.cpp:
(WebCore::parseTransformGeneric):
(WebCore::SVGTransformable::parseTransform):
(WebCore::parseTransformValueGeneric): Deleted.
(WebCore::SVGTransformable::parseTransformValue): Deleted.
* Source/WebCore/svg/SVGTransformable.h:

Canonical link: <a href="https://commits.webkit.org/292545@main">https://commits.webkit.org/292545@main</a>
</pre>
<!--EWS-Status-Bubble-Start-->
https://github.com/WebKit/WebKit/commit/09770fb56a699a8744d90a69df40ede317606b63

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/96377 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/15991 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/5987 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/101445 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/46896 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/98422 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/16287 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/24424 "Built successfully") | [✅ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/73458 "Passed tests") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/30689 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/99380 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/12232 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/87080 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/53795 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/11986 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/4849 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/46224 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/82096 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/4945 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/103472 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/23444 "Built successfully") | [✅ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/17072 "Passed tests") | [✅ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/82505 "Passed tests") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/23695 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/83105 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/81880 "Passed tests") | 
| [✅ 🛠 🧪 merge](https://ews-build.webkit.org/#/builders/19/builds/20546 "Built successfully and passed tests") | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/26521 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/3975 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/16845 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/23407 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/28562 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/23066 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/26546 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/24807 "Built successfully") | | | 
<!--EWS-Status-Bubble-End-->


Diff:
diff --git a/Source/WebCore/svg/SVGTransformList.cpp b/Source/WebCore/svg/SVGTransformList.cpp
index 5da3b3f5482f..632dfd4a9ec3 100644
--- a/Source/WebCore/svg/SVGTransformList.cpp
+++ b/Source/WebCore/svg/SVGTransformList.cpp
@@ -62,23 +62,36 @@ AffineTransform SVGTransformList::concatenate() const
     return result;
 }
 
-template<typename CharacterType> bool SVGTransformList::parseGeneric(StringParsingBuffer<CharacterType>& buffer)
+template<typename CharacterType> bool SVGTransformList::parseGeneric(StringParsingBuffer<CharacterType>& buffer, ListReplacement listReplacement)
 {
     bool delimParsed = false;
     skipOptionalSVGSpaces(buffer);
 
+    size_t itemIndex = 0;
+    auto currentListReplacement = listReplacement;
+
     while (buffer.hasCharactersRemaining()) {
         delimParsed = false;
-        
+
         auto parsedTransformType = SVGTransformable::parseTransformType(buffer);
         if (!parsedTransformType)
             return false;
 
-        auto parsedTransformValue = SVGTransformable::parseTransformValue(*parsedTransformType, buffer);
-        if (!parsedTransformValue)
+        RefPtr<SVGTransform> existingTransform;
+        if (currentListReplacement == ListReplacement::Replace && itemIndex < m_items.size() && parsedTransformType == m_items[itemIndex]->type())
+            existingTransform = m_items[itemIndex].ptr();
+
+        RefPtr parsedTransform = SVGTransformable::parseTransform(*parsedTransformType, buffer, existingTransform);
+        if (!parsedTransform)
             return false;
 
-        append(SVGTransform::create(WTFMove(*parsedTransformValue)));
+        if (currentListReplacement == ListReplacement::Replace && parsedTransform.get() != existingTransform.get()) {
+            currentListReplacement = ListReplacement::Append;
+            resize(itemIndex);
+        }
+
+        if (currentListReplacement == ListReplacement::Append)
+            append(parsedTransform.releaseNonNull());
 
         skipOptionalSVGSpaces(buffer);
 
@@ -86,30 +99,36 @@ template<typename CharacterType> bool SVGTransformList::parseGeneric(StringParsi
             delimParsed = true;
 
         skipOptionalSVGSpaces(buffer);
+
+        ++itemIndex;
     }
+
+    if (itemIndex < m_items.size()) {
+        ASSERT(currentListReplacement == ListReplacement::Replace);
+        resize(itemIndex);
+    }
+
     return !delimParsed;
 }
 
 void SVGTransformList::parse(StringView value)
 {
-    clearItems();
-
     bool parsingSucceeded = readCharactersForParsing(value, [&](auto buffer) {
-        return parseGeneric(buffer);
+        return parseGeneric(buffer, ListReplacement::Replace);
     });
-    
+
     if (!parsingSucceeded)
         clearItems();
 }
 
 bool SVGTransformList::parse(StringParsingBuffer<LChar>& buffer)
 {
-    return parseGeneric(buffer);
+    return parseGeneric(buffer, ListReplacement::Append);
 }
 
 bool SVGTransformList::parse(StringParsingBuffer<UChar>& buffer)
 {
-    return parseGeneric(buffer);
+    return parseGeneric(buffer, ListReplacement::Append);
 }
 
 String SVGTransformList::valueAsString() const
diff --git a/Source/WebCore/svg/SVGTransformList.h b/Source/WebCore/svg/SVGTransformList.h
index b8fc0776693f..f4dfe36c4b18 100644
--- a/Source/WebCore/svg/SVGTransformList.h
+++ b/Source/WebCore/svg/SVGTransformList.h
@@ -63,7 +63,12 @@ class SVGTransformList final : public SVGValuePropertyList<SVGTransform> {
     String valueAsString() const override;
 
 private:
-    template<typename CharacterType> bool parseGeneric(StringParsingBuffer<CharacterType>&);
+
+    enum class ListReplacement : bool {
+        Append,
+        Replace
+    };
+    template<typename CharacterType> bool parseGeneric(StringParsingBuffer<CharacterType>&, ListReplacement = ListReplacement::Append);
     bool parse(StringParsingBuffer<LChar>&);
     bool parse(StringParsingBuffer<UChar>&);
 };
diff --git a/Source/WebCore/svg/SVGTransformable.cpp b/Source/WebCore/svg/SVGTransformable.cpp
index 6028ddc4d422..6d171748826d 100644
--- a/Source/WebCore/svg/SVGTransformable.cpp
+++ b/Source/WebCore/svg/SVGTransformable.cpp
@@ -99,67 +99,119 @@ template<typename CharacterType> static int parseTransformParamList(StringParsin
 static constexpr std::array requiredValuesForType { 0, 6, 1, 1, 1, 1, 1 };
 static constexpr std::array optionalValuesForType { 0, 0, 1, 1, 2, 0, 0 };
 
-template<typename CharacterType> static std::optional<SVGTransformValue> parseTransformValueGeneric(SVGTransformValue::SVGTransformType type, StringParsingBuffer<CharacterType>& buffer)
+template<typename CharacterType> static RefPtr<SVGTransform> parseTransformGeneric(SVGTransformValue::SVGTransformType type, StringParsingBuffer<CharacterType>& buffer, RefPtr<SVGTransform> reusableTransform)
 {
+    ASSERT_IMPLIES(reusableTransform, type == reusableTransform->value().type());
+
     if (type == SVGTransformValue::SVG_TRANSFORM_UNKNOWN)
-        return std::nullopt;
+        return nullptr;
 
     int valueCount = 0;
     std::array<float, 6> values { 0, 0, 0, 0, 0, 0 };
     if ((valueCount = parseTransformParamList(buffer, values, requiredValuesForType[type], optionalValuesForType[type])) < 0)
-        return std::nullopt;
+        return nullptr;
 
     switch (type) {
     case SVGTransformValue::SVG_TRANSFORM_UNKNOWN:
         ASSERT_NOT_REACHED();
-        return std::nullopt;
+        return nullptr;
 
     case SVGTransformValue::SVG_TRANSFORM_SKEWX: {
+        if (reusableTransform) {
+            ASSERT(reusableTransform->value().type() == SVGTransformValue::SVG_TRANSFORM_SKEWX);
+            reusableTransform->value().setSkewX(values[0]);
+            return reusableTransform;
+        }
+
         SVGTransformValue transform;
         transform.setSkewX(values[0]);
-        return transform;
+        return SVGTransform::create(WTFMove(transform));
     }
     case SVGTransformValue::SVG_TRANSFORM_SKEWY: {
+        if (reusableTransform) {
+            reusableTransform->value().setSkewY(values[0]);
+            return reusableTransform;
+        }
+
         SVGTransformValue transform;
         transform.setSkewY(values[0]);
-        return transform;
+        return SVGTransform::create(WTFMove(transform));
     }
-    case SVGTransformValue::SVG_TRANSFORM_SCALE:
-        if (valueCount == 1) // Spec: if only one param given, assume uniform scaling
-            return SVGTransformValue::scaleTransformValue({ values[0], values[0] });
+    case SVGTransformValue::SVG_TRANSFORM_SCALE: {
+        if (reusableTransform) {
+            if (valueCount == 1)
+                reusableTransform->value().setScale(values[0], values[0]);
+            else
+                reusableTransform->value().setScale(values[0], values[1]);
+
+            return reusableTransform;
+        }
+
+        auto resultValue = [&]() {
+            if (valueCount == 1) // Spec: if only one param given, assume uniform scaling
+                return SVGTransformValue::scaleTransformValue({ values[0], values[0] });
 
-        return SVGTransformValue::scaleTransformValue({ values[0], values[1] });
+            return SVGTransformValue::scaleTransformValue({ values[0], values[1] });
+        };
+
+        return SVGTransform::create(resultValue());
+    }
+    case SVGTransformValue::SVG_TRANSFORM_TRANSLATE: {
+        if (reusableTransform) {
+            if (valueCount == 1)
+                reusableTransform->value().setTranslate(values[0], values[0]);
+            else
+                reusableTransform->value().setTranslate(values[0], values[1]);
+
+            return reusableTransform;
+        }
 
-    case SVGTransformValue::SVG_TRANSFORM_TRANSLATE:
         if (valueCount == 1) // Spec: if only one param given, assume 2nd param to be 0
-            return SVGTransformValue::translateTransformValue({ values[0], 0 });
+            return SVGTransform::create(SVGTransformValue::translateTransformValue({ values[0], 0 }));
 
-        return SVGTransformValue::translateTransformValue({ values[0], values[1] });
+        return SVGTransform::create(SVGTransformValue::translateTransformValue({ values[0], values[1] }));
+    }
+    case SVGTransformValue::SVG_TRANSFORM_ROTATE: {
+        if (reusableTransform) {
+            if (valueCount == 1)
+                reusableTransform->value().setRotate(values[0], 0, 0);
+            else
+                reusableTransform->value().setRotate(values[0], values[1], values[2]);
 
-    case SVGTransformValue::SVG_TRANSFORM_ROTATE:
-        if (valueCount == 1)
-            return SVGTransformValue::rotateTransformValue(values[0], { });
+            return reusableTransform;
+        }
 
-        return SVGTransformValue::rotateTransformValue(values[0], { values[1], values[2] });
+        auto resultValue = [&]() {
+            if (valueCount == 1)
+                return SVGTransformValue::rotateTransformValue(values[0], { });
 
+            return SVGTransformValue::rotateTransformValue(values[0], { values[1], values[2] });
+        };
+        return SVGTransform::create(resultValue());
+    }
     case SVGTransformValue::SVG_TRANSFORM_MATRIX: {
+        if (reusableTransform) {
+            reusableTransform->value().setMatrix(AffineTransform(values[0], values[1], values[2], values[3], values[4], values[5]));
+            return reusableTransform;
+        }
+
         SVGTransformValue transform;
         transform.setMatrix(AffineTransform(values[0], values[1], values[2], values[3], values[4], values[5]));
-        return transform;
+        return SVGTransform::create(transform);
     }
     }
 
-    return std::nullopt;
+    return nullptr;
 }
 
-std::optional<SVGTransformValue> SVGTransformable::parseTransformValue(SVGTransformValue::SVGTransformType type, StringParsingBuffer<LChar>& buffer)
+RefPtr<SVGTransform> SVGTransformable::parseTransform(SVGTransformValue::SVGTransformType type, StringParsingBuffer<LChar>& buffer, RefPtr<SVGTransform> reusableTransform)
 {
-    return parseTransformValueGeneric(type, buffer);
+    return parseTransformGeneric(type, buffer, reusableTransform);
 }
 
-std::optional<SVGTransformValue> SVGTransformable::parseTransformValue(SVGTransformValue::SVGTransformType type, StringParsingBuffer<UChar>& buffer)
+RefPtr<SVGTransform> SVGTransformable::parseTransform(SVGTransformValue::SVGTransformType type, StringParsingBuffer<UChar>& buffer, RefPtr<SVGTransform> reusableTransform)
 {
-    return parseTransformValueGeneric(type, buffer);
+    return parseTransformGeneric(type, buffer, reusableTransform);
 }
 
 template<typename CharacterType> static constexpr std::array<CharacterType, 5> skewXDesc  { 's', 'k', 'e', 'w', 'X' };
diff --git a/Source/WebCore/svg/SVGTransformable.h b/Source/WebCore/svg/SVGTransformable.h
index b05d7d330351..d95c15406128 100644
--- a/Source/WebCore/svg/SVGTransformable.h
+++ b/Source/WebCore/svg/SVGTransformable.h
@@ -26,13 +26,14 @@
 namespace WebCore {
     
 class AffineTransform;
+class SVGTransform;
 
 class SVGTransformable : public SVGLocatable {
 public:
     virtual ~SVGTransformable();
 
-    static std::optional<SVGTransformValue> parseTransformValue(SVGTransformValue::SVGTransformType, StringParsingBuffer<LChar>&);
-    static std::optional<SVGTransformValue> parseTransformValue(SVGTransformValue::SVGTransformType, StringParsingBuffer<UChar>&);
+    static RefPtr<SVGTransform> parseTransform(SVGTransformValue::SVGTransformType, StringParsingBuffer<LChar>&, RefPtr<SVGTransform> reusableValue);
+    static RefPtr<SVGTransform> parseTransform(SVGTransformValue::SVGTransformType, StringParsingBuffer<UChar>&, RefPtr<SVGTransform> reusableValue);
 
     static std::optional<SVGTransformValue::SVGTransformType> parseTransformType(StringView);
     static std::optional<SVGTransformValue::SVGTransformType> parseTransformType(StringParsingBuffer<LChar>&);


Reviews:
user: login: shallawa
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T18:49:15Z
user: login: smfr
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T21:02:21Z
user: login: shallawa
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-22T12:33:12Z

Review comments:
diff_hunk: @@ -62,54 +62,75 @@ AffineTransform SVGTransformList::concatenate() const
     return result;
 }
 
-template<typename CharacterType> bool SVGTransformList::parseGeneric(StringParsingBuffer<CharacterType>& buffer)
+template<typename CharacterType> bool SVGTransformList::parseGeneric(StringParsingBuffer<CharacterType>& buffer, ListReplacement listReplacement)
path: Source/WebCore/svg/SVGTransformList.cpp
user: login: shallawa
body: I think `ListReplacement` makes things a little bit more complicated. Why do not we make this function work in the replace mode as long as the type of the `existingTransform` matches the type of  `parsedTransform`? And once the type differ, just call `resize(itemIndex);` and this will delete the remaining of all the `existingTransforms`
created_at: 2025-03-21T18:04:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -62,54 +62,75 @@ AffineTransform SVGTransformList::concatenate() const
     return result;
 }
 
-template<typename CharacterType> bool SVGTransformList::parseGeneric(StringParsingBuffer<CharacterType>& buffer)
+template<typename CharacterType> bool SVGTransformList::parseGeneric(StringParsingBuffer<CharacterType>& buffer, ListReplacement listReplacement)
 {
     bool delimParsed = false;
     skipOptionalSVGSpaces(buffer);
 
-    while (buffer.hasCharactersRemaining()) {
-        delimParsed = false;
-        
-        auto parsedTransformType = SVGTransformable::parseTransformType(buffer);
-        if (!parsedTransformType)
-            return false;
+    auto parsingSucceeded = [&]() {
+        size_t itemIndex = 0;
+        auto currentListReplacement = listReplacement;
 
-        auto parsedTransformValue = SVGTransformable::parseTransformValue(*parsedTransformType, buffer);
-        if (!parsedTransformValue)
-            return false;
+        while (buffer.hasCharactersRemaining()) {
+            delimParsed = false;
 
-        append(SVGTransform::create(WTFMove(*parsedTransformValue)));
+            auto parsedTransformType = SVGTransformable::parseTransformType(buffer);
+            if (!parsedTransformType)
+                return false;
 
-        skipOptionalSVGSpaces(buffer);
+            RefPtr<SVGTransform> existingTransform;
+            if (currentListReplacement == ListReplacement::Replace && itemIndex < m_items.size() && parsedTransformType == m_items[itemIndex]->type())
+                existingTransform = m_items[itemIndex].ptr();
 
-        if (skipExactly(buffer, ','))
-            delimParsed = true;
+            RefPtr parsedTransform = SVGTransformable::parseTransform(*parsedTransformType, buffer, existingTransform);
+            if (!parsedTransform)
+                return false;
 
-        skipOptionalSVGSpaces(buffer);
-    }
-    return !delimParsed;
+            if (currentListReplacement == ListReplacement::Replace && parsedTransform.get() != existingTransform.get()) {
+                currentListReplacement = ListReplacement::Append;
+                resize(itemIndex);
+            }
+
+            if (currentListReplacement == ListReplacement::Append)
+                append(WTFMove(parsedTransform.releaseNonNull()));
+
+            skipOptionalSVGSpaces(buffer);
+
+            if (skipExactly(buffer, ','))
+                delimParsed = true;
+
+            skipOptionalSVGSpaces(buffer);
+
+            ++itemIndex;
+        }
+
+        if (currentListReplacement == ListReplacement::Replace && itemIndex < m_items.size())
+            resize(itemIndex);
+
+        return !delimParsed;
+    }();
+
+    if (!parsingSucceeded && listReplacement == ListReplacement::Replace)
+        clearItems();
path: Source/WebCore/svg/SVGTransformList.cpp
user: login: shallawa
body: It is not clear why we `clearItems()` when an error happens but only when `ListReplacement::Replace`. I understand you want to keep the existing behavior. But I think moving `clearItems()` back to `SVGTransformList::parse(StringView value)` make reading this function easier.
created_at: 2025-03-21T18:08:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -62,54 +62,75 @@ AffineTransform SVGTransformList::concatenate() const
     return result;
 }
 
-template<typename CharacterType> bool SVGTransformList::parseGeneric(StringParsingBuffer<CharacterType>& buffer)
+template<typename CharacterType> bool SVGTransformList::parseGeneric(StringParsingBuffer<CharacterType>& buffer, ListReplacement listReplacement)
 {
     bool delimParsed = false;
     skipOptionalSVGSpaces(buffer);
 
-    while (buffer.hasCharactersRemaining()) {
-        delimParsed = false;
-        
-        auto parsedTransformType = SVGTransformable::parseTransformType(buffer);
-        if (!parsedTransformType)
-            return false;
+    auto parsingSucceeded = [&]() {
+        size_t itemIndex = 0;
+        auto currentListReplacement = listReplacement;
 
-        auto parsedTransformValue = SVGTransformable::parseTransformValue(*parsedTransformType, buffer);
-        if (!parsedTransformValue)
-            return false;
+        while (buffer.hasCharactersRemaining()) {
path: Source/WebCore/svg/SVGTransformList.cpp
user: login: shallawa
body: Can be a for loop:
```
for (size_t itemIndex = 0; buffer.hasCharactersRemaining(); ++itemIndex)
```
created_at: 2025-03-21T18:10:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -62,54 +62,75 @@ AffineTransform SVGTransformList::concatenate() const
     return result;
 }
 
-template<typename CharacterType> bool SVGTransformList::parseGeneric(StringParsingBuffer<CharacterType>& buffer)
+template<typename CharacterType> bool SVGTransformList::parseGeneric(StringParsingBuffer<CharacterType>& buffer, ListReplacement listReplacement)
 {
     bool delimParsed = false;
     skipOptionalSVGSpaces(buffer);
 
-    while (buffer.hasCharactersRemaining()) {
-        delimParsed = false;
-        
-        auto parsedTransformType = SVGTransformable::parseTransformType(buffer);
-        if (!parsedTransformType)
-            return false;
+    auto parsingSucceeded = [&]() {
path: Source/WebCore/svg/SVGTransformList.cpp
user: login: shallawa
body: If calling `clearItems();` was moved back to `SVGTransformList::parse(StringView value)`, there is no need for put the parsing code in a lambda.
created_at: 2025-03-21T18:37:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -62,54 +62,75 @@ AffineTransform SVGTransformList::concatenate() const
     return result;
 }
 
-template<typename CharacterType> bool SVGTransformList::parseGeneric(StringParsingBuffer<CharacterType>& buffer)
+template<typename CharacterType> bool SVGTransformList::parseGeneric(StringParsingBuffer<CharacterType>& buffer, ListReplacement listReplacement)
 {
     bool delimParsed = false;
     skipOptionalSVGSpaces(buffer);
 
-    while (buffer.hasCharactersRemaining()) {
-        delimParsed = false;
-        
-        auto parsedTransformType = SVGTransformable::parseTransformType(buffer);
-        if (!parsedTransformType)
-            return false;
+    auto parsingSucceeded = [&]() {
+        size_t itemIndex = 0;
+        auto currentListReplacement = listReplacement;
 
-        auto parsedTransformValue = SVGTransformable::parseTransformValue(*parsedTransformType, buffer);
-        if (!parsedTransformValue)
-            return false;
+        while (buffer.hasCharactersRemaining()) {
+            delimParsed = false;
 
-        append(SVGTransform::create(WTFMove(*parsedTransformValue)));
+            auto parsedTransformType = SVGTransformable::parseTransformType(buffer);
+            if (!parsedTransformType)
+                return false;
 
-        skipOptionalSVGSpaces(buffer);
+            RefPtr<SVGTransform> existingTransform;
+            if (currentListReplacement == ListReplacement::Replace && itemIndex < m_items.size() && parsedTransformType == m_items[itemIndex]->type())
+                existingTransform = m_items[itemIndex].ptr();
 
-        if (skipExactly(buffer, ','))
-            delimParsed = true;
+            RefPtr parsedTransform = SVGTransformable::parseTransform(*parsedTransformType, buffer, existingTransform);
+            if (!parsedTransform)
+                return false;
 
-        skipOptionalSVGSpaces(buffer);
-    }
-    return !delimParsed;
+            if (currentListReplacement == ListReplacement::Replace && parsedTransform.get() != existingTransform.get()) {
path: Source/WebCore/svg/SVGTransformList.cpp
user: login: shallawa
body: I think this equivalent to 
```
if (existingTransform && parsedTransform.get() != existingTransform.get()) {
```

created_at: 2025-03-21T18:39:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -99,67 +99,126 @@ template<typename CharacterType> static int parseTransformParamList(StringParsin
 static constexpr std::array requiredValuesForType { 0, 6, 1, 1, 1, 1, 1 };
 static constexpr std::array optionalValuesForType { 0, 0, 1, 1, 2, 0, 0 };
 
-template<typename CharacterType> static std::optional<SVGTransformValue> parseTransformValueGeneric(SVGTransformValue::SVGTransformType type, StringParsingBuffer<CharacterType>& buffer)
+template<typename CharacterType> static RefPtr<SVGTransform> parseTransformGeneric(SVGTransformValue::SVGTransformType type, StringParsingBuffer<CharacterType>& buffer, RefPtr<SVGTransform> reusableValue)
 {
     if (type == SVGTransformValue::SVG_TRANSFORM_UNKNOWN)
-        return std::nullopt;
+        return nullptr;
 
     int valueCount = 0;
     std::array<float, 6> values { 0, 0, 0, 0, 0, 0 };
     if ((valueCount = parseTransformParamList(buffer, values, requiredValuesForType[type], optionalValuesForType[type])) < 0)
-        return std::nullopt;
+        return nullptr;
 
     switch (type) {
     case SVGTransformValue::SVG_TRANSFORM_UNKNOWN:
         ASSERT_NOT_REACHED();
-        return std::nullopt;
+        return nullptr;
 
     case SVGTransformValue::SVG_TRANSFORM_SKEWX: {
+        if (reusableValue) {
+            ASSERT(reusableValue->value().type() == SVGTransformValue::SVG_TRANSFORM_SKEWX);
path: Source/WebCore/svg/SVGTransformable.cpp
user: login: shallawa
body: Instead of adding ASSERT to each switch case, we can add one ASSERT at the beginning
```
ASSERT_IMPLIES(reusableValue, type == reusableValue->value().type());
```
created_at: 2025-03-21T18:41:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -99,67 +99,126 @@ template<typename CharacterType> static int parseTransformParamList(StringParsin
 static constexpr std::array requiredValuesForType { 0, 6, 1, 1, 1, 1, 1 };
 static constexpr std::array optionalValuesForType { 0, 0, 1, 1, 2, 0, 0 };
 
-template<typename CharacterType> static std::optional<SVGTransformValue> parseTransformValueGeneric(SVGTransformValue::SVGTransformType type, StringParsingBuffer<CharacterType>& buffer)
+template<typename CharacterType> static RefPtr<SVGTransform> parseTransformGeneric(SVGTransformValue::SVGTransformType type, StringParsingBuffer<CharacterType>& buffer, RefPtr<SVGTransform> reusableValue)
path: Source/WebCore/svg/SVGTransformable.cpp
user: login: shallawa
body: Should we use the same name the caller passes above? reusableValue -> existingTransform?

Or at least do not use Value in the name to not confuse it with SVGTransformValue. reusableValue -> reusableTransform?
created_at: 2025-03-21T18:43:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -62,54 +62,75 @@ AffineTransform SVGTransformList::concatenate() const
     return result;
 }
 
-template<typename CharacterType> bool SVGTransformList::parseGeneric(StringParsingBuffer<CharacterType>& buffer)
+template<typename CharacterType> bool SVGTransformList::parseGeneric(StringParsingBuffer<CharacterType>& buffer, ListReplacement listReplacement)
path: Source/WebCore/svg/SVGTransformList.cpp
user: login: smfr
body: I don't think that's possible, because some callers call it multiple times and expect the transforms to accumulate.
created_at: 2025-03-21T21:02:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -62,54 +62,71 @@ AffineTransform SVGTransformList::concatenate() const
     return result;
 }
 
-template<typename CharacterType> bool SVGTransformList::parseGeneric(StringParsingBuffer<CharacterType>& buffer)
+template<typename CharacterType> bool SVGTransformList::parseGeneric(StringParsingBuffer<CharacterType>& buffer, ListReplacement listReplacement)
 {
     bool delimParsed = false;
     skipOptionalSVGSpaces(buffer);
 
+    size_t itemIndex = 0;
+    auto currentListReplacement = listReplacement;
+
     while (buffer.hasCharactersRemaining()) {
         delimParsed = false;
-        
+
         auto parsedTransformType = SVGTransformable::parseTransformType(buffer);
         if (!parsedTransformType)
             return false;
 
-        auto parsedTransformValue = SVGTransformable::parseTransformValue(*parsedTransformType, buffer);
-        if (!parsedTransformValue)
+        RefPtr<SVGTransform> existingTransform;
+        if (currentListReplacement == ListReplacement::Replace && itemIndex < m_items.size() && parsedTransformType == m_items[itemIndex]->type())
+            existingTransform = m_items[itemIndex].ptr();
+
+        RefPtr parsedTransform = SVGTransformable::parseTransform(*parsedTransformType, buffer, existingTransform);
+        if (!parsedTransform)
             return false;
 
-        append(SVGTransform::create(WTFMove(*parsedTransformValue)));
+        if (currentListReplacement == ListReplacement::Replace && parsedTransform.get() != existingTransform.get()) {
+            currentListReplacement = ListReplacement::Append;
+            resize(itemIndex);
+        }
+
+        if (currentListReplacement == ListReplacement::Append)
+            append(WTFMove(parsedTransform.releaseNonNull()));
 
         skipOptionalSVGSpaces(buffer);
 
         if (skipExactly(buffer, ','))
             delimParsed = true;
 
         skipOptionalSVGSpaces(buffer);
+
+        ++itemIndex;
     }
+
+    if (currentListReplacement == ListReplacement::Replace && itemIndex < m_items.size())
+        resize(itemIndex);
path: Source/WebCore/svg/SVGTransformList.cpp
user: login: shallawa
body: Good that you caught this case. But `itemIndex < m_items.size()` implies `currentListReplacement == ListReplacement::Replace` i.e. it is not possible that the first clause is true and the second one is false.
created_at: 2025-03-22T12:33:04Z
author_association: CONTRIBUTOR

PR comments:
user: login: webkit-early-warning-system
created_at: 2025-03-21T17:34:15Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/3d5370c107a2642558c7720cd2158e0cce33522e)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/96222 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/15836 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/5804 "Built successfully") | [❌ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/101287 "Hash 3d5370c1 for PR 42822 does not build (failure)") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/46741 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/98267 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/16132 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/24269 "Built successfully") | [❌ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/5/builds/101287 "Hash 3d5370c1 for PR 42822 does not build (failure)") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/30585 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/99225 "Passed tests") | [  ~~🧪 ios-wk2~~](https://ews-build.webkit.org/#/builders/132/builds/12100 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/86927 "Passed tests") | [❌ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/5/builds/101287 "Hash 3d5370c1 for PR 42822 does not build (failure)") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/11851 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/4688 "Passed tests") | [❌ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/46067 "Hash 3d5370c1 for PR 42822 does not build (failure)") | 
| | [  ~~🧪 api-ios~~](https://ews-build.webkit.org/#/builders/13/builds/81976 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/4785 "Passed tests") | [❌ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/103321 "Hash 3d5370c1 for PR 42822 does not build (failure)") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/23289 "Built successfully") | [⏳ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/macOS-Sequoia-Debug-WK2-Tests-EWS "Waiting to run tests") | [❌ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/2/builds/103321 "Hash 3d5370c1 for PR 42822 does not build (failure)") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/23540 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/82946 "Passed tests") | [❌ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/2/builds/103321 "Hash 3d5370c1 for PR 42822 does not build (failure)") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/26389 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/3830 "Passed tests") | [  ~~🛠 playstation~~](https://ews-build.webkit.org/#/builders/134/builds/16663 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/23252 "Built successfully") | [  ~~🛠 mac-safer-cpp~~](https://ews-build.webkit.org/#/builders/120/builds/28407 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/22911 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/26391 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/24652 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-03-21T19:14:46Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/96b298f16212d6e2e88108c2a3730baf39661b0e)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/96247 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/15861 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/5829 "Built successfully") | [❌ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/101314 "Hash 96b298f1 for PR 42822 does not build (failure)") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/46766 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/98292 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/16157 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/24294 "Built successfully") | [❌ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/5/builds/101314 "Hash 96b298f1 for PR 42822 does not build (failure)") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/30598 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/99250 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/12124 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/86952 "Passed tests") | [❌ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/5/builds/101314 "Hash 96b298f1 for PR 42822 does not build (failure)") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/11886 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/4713 "Passed tests") | [❌ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/46093 "Hash 96b298f1 for PR 42822 does not build (failure)") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/81993 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/4811 "Passed tests") | [❌ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/103342 "Hash 96b298f1 for PR 42822 does not build (failure)") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/23314 "Built successfully") | [⏳ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/macOS-Sequoia-Debug-WK2-Tests-EWS "Waiting to run tests") | [❌ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/2/builds/103342 "Hash 96b298f1 for PR 42822 does not build (failure)") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/23565 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/82971 "Passed tests") | [❌ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/2/builds/103342 "Hash 96b298f1 for PR 42822 does not build (failure)") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/26413 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/3853 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/16688 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/23277 "Built successfully") | [  ~~🛠 mac-safer-cpp~~](https://ews-build.webkit.org/#/builders/120/builds/28432 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/22936 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/26416 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/24677 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-03-21T21:44:19Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/74da465527986ed4d4052f718edc136e7b425c0f)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/96281 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/15895 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/5864 "Built successfully") | [❌ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/101348 "Hash 74da4655 for PR 42822 does not build (failure)") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/46800 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/98326 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/16191 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/24328 "Built successfully") | [❌ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/5/builds/101348 "Hash 74da4655 for PR 42822 does not build (failure)") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/30614 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/99284 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/12144 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/86973 "Passed tests") | [❌ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/5/builds/101348 "Hash 74da4655 for PR 42822 does not build (failure)") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/11898 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/4738 "Passed tests") | [❌ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/46127 "Hash 74da4655 for PR 42822 does not build (failure)") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/82014 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/4836 "Passed tests") | [❌ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/103376 "Hash 74da4655 for PR 42822 does not build (failure)") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/23348 "Built successfully") | [✅ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/16998 "Passed tests") | [❌ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/2/builds/103376 "Hash 74da4655 for PR 42822 does not build (failure)") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/23599 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/82996 "Passed tests") | [❌ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/2/builds/103376 "Hash 74da4655 for PR 42822 does not build (failure)") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/26438 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/3873 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/16722 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/23311 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/28466 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/22970 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/26450 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/24711 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-03-22T19:24:59Z
body: EWS run on current version of this PR (hash https://github.com/WebKit/WebKit/commit/09770fb56a699a8744d90a69df40ede317606b63)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/96377 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/15991 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/5987 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/101445 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/46896 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/98422 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/16287 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/24424 "Built successfully") | [✅ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/73458 "Passed tests") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/30689 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/99380 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/12232 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/87080 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/53795 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/11986 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/4849 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/46224 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/82096 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/4945 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/103472 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/23444 "Built successfully") | [✅ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/17072 "Passed tests") | [✅ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/82505 "Passed tests") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/23695 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/83105 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/81880 "Passed tests") | 
| [✅ 🛠 🧪 merge](https://ews-build.webkit.org/#/builders/19/builds/20546 "Built successfully and passed tests") | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/26521 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/3975 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/16845 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/23407 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/28562 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/23066 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/26546 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/24807 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-commit-queue
created_at: 2025-03-22T21:20:21Z
body: Committed 292545@main (bef74b7519fb): <https://commits.webkit.org/292545@main>

Reviewed commits have been landed. Closing PR #42822 and removing active labels.
author_association: COLLABORATOR