Repository: WebKit/WebKit
Pull request #44757: [Site Isolation] Add support for text manipulation in cross-site frames
Author: login: szewai
Labels: name: New Bugs
description: Unclassified bugs are placed in this component until the correct component can be determined.
Created: 2025-04-30T19:02:56Z | Merged: 2025-05-02T22:15:00Z

#### ca8a33f183972bba6a96003dc5d6d29709c6490e
<pre>
[Site Isolation] Add support for text manipulation in cross-site frames
<a href="https://rdar.apple.com/150383885">rdar://150383885</a>
<a href="https://bugs.webkit.org/show_bug.cgi?id=292339">https://bugs.webkit.org/show_bug.cgi?id=292339</a>

Reviewed by Ryosuke Niwa.

To make text manipulation work with site isolation, UI process should send message to all frame processes and collect
replies from all of them. This patch also ensures web process inspects all local frames by replacing
traverseNextRendered() with traverseNext(), as traverseNextRendered() will stop early when encountering a remote frame.

API test: SiteIsolation.CompleteTextManipulation
          SiteIsolation.CompleteTextManipulationFailsInSomeFrame

* Source/WebCore/editing/TextManipulationController.cpp:
(WebCore::TextManipulationController::completeManipulation):
* Source/WebCore/editing/TextManipulationController.h:
* Source/WebCore/editing/TextManipulationControllerManipulationFailure.h:
* Source/WebKit/Scripts/webkit/messages.py:
(headers_for_type):
* Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in:
* Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _completeTextManipulation:completion:]):
(-[WKWebView _completeTextManipulationForItems:completion:]):
* Source/WebKit/UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::startTextManipulations):
(WebKit::WebPageProxy::completeTextManipulation):
* Source/WebKit/UIProcess/WebPageProxy.h:
* Source/WebKit/WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::startTextManipulations):
(WebKit::WebPage::completeTextManipulation):
* Source/WebKit/WebProcess/WebPage/WebPage.h:
* Source/WebKit/WebProcess/WebPage/WebPage.messages.in:
* Tools/TestWebKitAPI/Tests/WebKitCocoa/SiteIsolation.mm:
(-[SiteIsolationTextManipulationDelegate init]):
(-[SiteIsolationTextManipulationDelegate _webView:didFindTextManipulationItems:]):
(-[SiteIsolationTextManipulationDelegate items]):
(TestWebKitAPI::createToken):
(TestWebKitAPI::createItem):
(TestWebKitAPI::TEST(SiteIsolation, CompleteTextManipulation)):
(TestWebKitAPI::(SiteIsolation, CompleteTextManipulationFailsInSomeFrame)):
(TestWebKitAPI::TEST(SiteIsolation, DISABLED_Events)): Deleted.

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

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [❌ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/101824 "18 style errors") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/21492 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/11808 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/106982 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/52458 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/103864 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/21800 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/29999 "Built successfully") | [  ~~🧪 wpe-wk2~~](https://ews-build.webkit.org/#/builders/34/builds/77557 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/34554 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/104831 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/16844 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/91946 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/57868 "Passed tests") | 
| [✅ 🧪 webkitpy](https://ews-build.webkit.org/#/builders/6/builds/101298 "Passed tests") | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/16671 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/9965 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/51809 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/86525 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/10041 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/109339 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/28957 "Built successfully") | [✅ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/21331 "Passed tests") | [❌ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/86509 "Found 1 new test failure: imported/w3c/web-platform-tests/service-workers/service-worker/redirected-response.https.html (failure)") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/29318 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/88147 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/86081 "Passed tests") | 
| [✅ 🛠 🧪 merge](https://ews-build.webkit.org/#/builders/19/builds/21910 "Built successfully and passed tests") | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/30835 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/8556 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/23128 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/28885 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/34177 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/28696 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/32019 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/30255 "Built successfully") | | | 
<!--EWS-Status-Bubble-End-->


Diff:
diff --git a/Source/WebCore/editing/TextManipulationController.cpp b/Source/WebCore/editing/TextManipulationController.cpp
index 30a3c1340aa7..c255ce10ae57 100644
--- a/Source/WebCore/editing/TextManipulationController.cpp
+++ b/Source/WebCore/editing/TextManipulationController.cpp
@@ -688,9 +688,10 @@ void TextManipulationController::flushPendingItemsForCallback()
     m_pendingItemsForCallback.clear();
 }
 
-auto TextManipulationController::completeManipulation(const Vector<WebCore::TextManipulationItem>& completionItems) -> Vector<ManipulationFailure>
+TextManipulationController::ManipulationResult TextManipulationController::completeManipulation(const Vector<TextManipulationItem>& completionItems)
 {
     Vector<ManipulationFailure> failures;
+    Vector<uint64_t> succeededIndexes;
     NodeSet containersWithoutVisualOverflowBeforeReplacement;
     for (unsigned i = 0; i < completionItems.size(); ++i) {
         auto& itemToComplete = completionItems[i];
@@ -718,8 +719,12 @@ auto TextManipulationController::completeManipulation(const Vector<WebCore::Text
         std::exchange(itemData, itemDataIterator->value);
         m_items.remove(itemDataIterator);
 
-        if (auto failureOrNullopt = replace(itemData, itemToComplete.tokens, containersWithoutVisualOverflowBeforeReplacement))
+        if (auto failureOrNullopt = replace(itemData, itemToComplete.tokens, containersWithoutVisualOverflowBeforeReplacement)) {
             failures.append(ManipulationFailure { *frameID, itemID, i, *failureOrNullopt });
+            continue;
+        }
+
+        succeededIndexes.append(i);
     }
 
     if (!containersWithoutVisualOverflowBeforeReplacement.isEmpty()) {
@@ -743,7 +748,7 @@ auto TextManipulationController::completeManipulation(const Vector<WebCore::Text
         }
     }
 
-    return failures;
+    return { WTFMove(failures), WTFMove(succeededIndexes) };
 }
 
 struct TokenExchangeData {
diff --git a/Source/WebCore/editing/TextManipulationController.h b/Source/WebCore/editing/TextManipulationController.h
index f0b1db36a248..9683fcf2292d 100644
--- a/Source/WebCore/editing/TextManipulationController.h
+++ b/Source/WebCore/editing/TextManipulationController.h
@@ -62,7 +62,8 @@ class TextManipulationController final : public CanMakeWeakPtr<TextManipulationC
     void didAddOrCreateRendererForNode(Node&);
     void removeNode(Node&);
 
-    WEBCORE_EXPORT Vector<ManipulationFailure> completeManipulation(const Vector<TextManipulationItem>&);
+    using ManipulationResult = TextManipulationControllerManipulationResult;
+    WEBCORE_EXPORT ManipulationResult completeManipulation(const Vector<TextManipulationItem>&);
 
 private:
     void observeParagraphs(const Position& start, const Position& end);
diff --git a/Source/WebCore/editing/TextManipulationControllerManipulationFailure.h b/Source/WebCore/editing/TextManipulationControllerManipulationFailure.h
index 5c9ffb76a17a..a4ab1840199e 100644
--- a/Source/WebCore/editing/TextManipulationControllerManipulationFailure.h
+++ b/Source/WebCore/editing/TextManipulationControllerManipulationFailure.h
@@ -45,4 +45,9 @@ struct TextManipulationControllerManipulationFailure {
     Type type;
 };
 
+struct TextManipulationControllerManipulationResult {
+    Vector<TextManipulationControllerManipulationFailure> failures;
+    Vector<uint64_t> succeededIndexes;
+};
+
 } // namespace WebCore
diff --git a/Source/WebKit/Scripts/webkit/messages.py b/Source/WebKit/Scripts/webkit/messages.py
index f9b05e9952f3..f8b39b764ead 100644
--- a/Source/WebKit/Scripts/webkit/messages.py
+++ b/Source/WebKit/Scripts/webkit/messages.py
@@ -1210,6 +1210,7 @@ def headers_for_type(type, for_implementation_file=False):
         'WebCore::TextDrawingModeFlags': ['<WebCore/GraphicsTypes.h>'],
         'WebCore::TextExtraction::Item': ['<WebCore/TextExtractionTypes.h>'],
         'WebCore::TextIndicatorData': ['<WebCore/TextIndicator.h>'],
+        'WebCore::TextManipulationControllerManipulationResult': ['<WebCore/TextManipulationControllerManipulationFailure.h>'],
         'WebCore::TextManipulationTokenIdentifier': ['<WebCore/TextManipulationToken.h>'],
         'WebCore::ThirdPartyCookieBlockingMode': ['<WebCore/NetworkStorageSession.h>'],
         'WebCore::TrackID': ['<WebCore/TrackBase.h>'],
diff --git a/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in b/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in
index 7cacc2a7ff55..87abf35aa2fe 100644
--- a/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in
+++ b/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in
@@ -4633,6 +4633,12 @@ header: <WebCore/TextManipulationController.h>
     WebCore::TextManipulationControllerManipulationFailure::Type type;
 };
 
+header: <WebCore/TextManipulationControllerManipulationFailure.h>
+[CustomHeader] struct WebCore::TextManipulationControllerManipulationResult {
+    Vector<WebCore::TextManipulationControllerManipulationFailure> failures;
+    Vector<uint64_t> succeededIndexes;
+};
+
 struct WebCore::BackgroundFetchInformation {
     WebCore::ServiceWorkerRegistrationIdentifier registrationIdentifier;
     String identifier;
diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm b/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
index cd678959ec5b..4a0635e0c642 100644
--- a/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
+++ b/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
@@ -3743,8 +3743,8 @@ - (void)_completeTextManipulation:(_WKTextManipulationItem *)item completion:(vo
         tokens.append(WebCore::TextManipulationToken { coreTextManipulationTokenIdentifierFromString(wkToken.identifier), wkToken.content, std::nullopt });
 
     Vector<WebCore::TextManipulationItem> coreItems({ WebCore::TextManipulationItem { identifiers->frameID, false, false, identifiers->itemID, WTFMove(tokens) } });
-    _page->completeTextManipulation(coreItems, [capturedCompletionBlock = makeBlockPtr(completionHandler)] (bool allFailed, auto& failures) {
-        capturedCompletionBlock(!allFailed && failures.isEmpty());
+    _page->completeTextManipulation(coreItems, [capturedCompletionBlock = makeBlockPtr(completionHandler)] (auto failures) {
+        capturedCompletionBlock(failures.isEmpty());
     });
 }
 
@@ -3817,11 +3817,7 @@ - (void)_completeTextManipulationForItems:(NSArray<_WKTextManipulationItem *> *)
     }
 
     RetainPtr<NSArray<_WKTextManipulationItem *>> retainedItems = items;
-    _page->completeTextManipulation(coreItems, [capturedItems = retainedItems, capturedCompletionBlock = makeBlockPtr(completionHandler)](bool allFailed, auto& failures) {
-        if (allFailed) {
-            capturedCompletionBlock(makeFailureSetForAllTextManipulationItems(capturedItems.get()).get());
-            return;
-        }
+    _page->completeTextManipulation(coreItems, [capturedItems = retainedItems, capturedCompletionBlock = makeBlockPtr(completionHandler)](auto failures) {
         capturedCompletionBlock(wkTextManipulationErrors(capturedItems.get(), failures).get());
     });
 }
diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp
index c44635aa6504..b28a31932cae 100644
--- a/Source/WebKit/UIProcess/WebPageProxy.cpp
+++ b/Source/WebKit/UIProcess/WebPageProxy.cpp
@@ -14914,12 +14914,11 @@ void WebPageProxy::setMockWebAuthenticationConfiguration(MockWebAuthenticationCo
 
 void WebPageProxy::startTextManipulations(const Vector<TextManipulationController::ExclusionRule>& exclusionRules, bool includeSubframes, TextManipulationItemCallback&& callback, CompletionHandler<void()>&& completionHandler)
 {
-    if (!hasRunningProcess()) {
-        completionHandler();
-        return;
-    }
     m_textManipulationItemCallback = WTFMove(callback);
-    sendWithAsyncReply(Messages::WebPage::StartTextManipulations(exclusionRules, includeSubframes), WTFMove(completionHandler));
+    auto callbackAggregator = CallbackAggregator::create(WTFMove(completionHandler));
+    forEachWebContentProcess([&](auto& webProcess, auto pageID) {
+        webProcess.sendWithAsyncReply(Messages::WebPage::StartTextManipulations(exclusionRules, includeSubframes), [callbackAggregator] { }, pageID);
+    });
 }
 
 void WebPageProxy::didFindTextManipulationItems(const Vector<WebCore::TextManipulationItem>& items)
@@ -14929,13 +14928,70 @@ void WebPageProxy::didFindTextManipulationItems(const Vector<WebCore::TextManipu
     m_textManipulationItemCallback(items);
 }
 
-void WebPageProxy::completeTextManipulation(const Vector<TextManipulationItem>& items, Function<void(bool allFailed, const Vector<TextManipulationControllerManipulationFailure>&)>&& completionHandler)
+void WebPageProxy::completeTextManipulation(const Vector<TextManipulationItem>& items, CompletionHandler<void(Vector<TextManipulationControllerManipulationFailure>&&)>&& completionHandler)
 {
-    if (!hasRunningProcess()) {
-        completionHandler(true, { });
-        return;
-    }
-    sendWithAsyncReply(Messages::WebPage::CompleteTextManipulation(items), WTFMove(completionHandler));
+    class TextManipulationCallbackAggregator final : public ThreadSafeRefCounted<TextManipulationCallbackAggregator, WTF::DestructionThread::MainRunLoop> {
+    public:
+        struct ItemInfo {
+            Markable<FrameIdentifier> frameID;
+            Markable<TextManipulationItemIdentifier> identifier;
+        };
+
+        using Callback = CompletionHandler<void(Vector<TextManipulationControllerManipulationFailure>&&)>;
+
+        static Ref<TextManipulationCallbackAggregator> create(Vector<ItemInfo>&& items, Callback&& callback)
+        {
+            return adoptRef(*new TextManipulationCallbackAggregator(WTFMove(items), WTFMove(callback)));
+        }
+
+        ~TextManipulationCallbackAggregator()
+        {
+            ASSERT(RunLoop::isMain());
+            BitVector resultIndexes;
+            for (auto& failure : m_result.failures)
+                resultIndexes.set(failure.index);
+            for (auto& index : m_result.succeededIndexes)
+                resultIndexes.add(index);
+            for (unsigned index = 0; index < m_items.size(); ++index) {
+                if (resultIndexes.get(index))
+                    continue;
+
+                WebCore::TextManipulationControllerManipulationFailure failure { *m_items[index].frameID, m_items[index].identifier, index, WebCore::TextManipulationControllerManipulationFailure::Type::NotAvailable };
+                m_result.failures.append(WTFMove(failure));
+            }
+
+            m_callback(WTFMove(m_result.failures));
+        }
+
+        void addResult(TextManipulationControllerManipulationResult&& result)
+        {
+            m_result.failures.appendVector(WTFMove(result.failures));
+            m_result.succeededIndexes.appendVector(WTFMove(result.succeededIndexes));
+        }
+
+    private:
+        TextManipulationCallbackAggregator(Vector<ItemInfo>&& items, Callback&& callback)
+            : m_items(WTFMove(items))
+            , m_callback(WTFMove(callback))
+        {
+            ASSERT(RunLoop::isMain());
+        }
+
+        Vector<ItemInfo> m_items;
+        Callback m_callback;
+        TextManipulationControllerManipulationResult m_result;
+    };
+
+    Vector<TextManipulationCallbackAggregator::ItemInfo> itemInfos;
+    itemInfos.reserveCapacity(items.size());
+    for (auto& item : items)
+        itemInfos.append({ item.frameID, item.identifier });
+    auto callbackAggregator = TextManipulationCallbackAggregator::create(WTFMove(itemInfos), WTFMove(completionHandler));
+    forEachWebContentProcess([&](auto& webProcess, auto pageID) {
+        webProcess.sendWithAsyncReply(Messages::WebPage::CompleteTextManipulation(items), [callbackAggregator](auto&& result) {
+            callbackAggregator->addResult(WTFMove(result));
+        }, pageID);
+    });
 }
 
 void WebPageProxy::setCORSDisablingPatterns(Vector<String>&& patterns)
diff --git a/Source/WebKit/UIProcess/WebPageProxy.h b/Source/WebKit/UIProcess/WebPageProxy.h
index e86fba082b47..096a7520774f 100644
--- a/Source/WebKit/UIProcess/WebPageProxy.h
+++ b/Source/WebKit/UIProcess/WebPageProxy.h
@@ -315,6 +315,7 @@ struct TextCheckingResult;
 struct TextIndicatorData;
 struct TextManipulationControllerExclusionRule;
 struct TextManipulationControllerManipulationFailure;
+struct TextManipulationControllerManipulationResult;
 struct TextManipulationItem;
 struct TextRecognitionResult;
 struct TranslationContextMenuInfo;
@@ -2228,7 +2229,7 @@ class WebPageProxy final : public API::ObjectImpl<API::Object::Type::Page>, publ
     using TextManipulationItemCallback = Function<void(const Vector<WebCore::TextManipulationItem>&)>;
     void startTextManipulations(const Vector<WebCore::TextManipulationControllerExclusionRule>&, bool includeSubframes, TextManipulationItemCallback&&, WTF::CompletionHandler<void()>&&);
     void didFindTextManipulationItems(const Vector<WebCore::TextManipulationItem>&);
-    void completeTextManipulation(const Vector<WebCore::TextManipulationItem>&, Function<void(bool allFailed, const Vector<WebCore::TextManipulationControllerManipulationFailure>&)>&&);
+    void completeTextManipulation(const Vector<WebCore::TextManipulationItem>&, CompletionHandler<void(Vector<WebCore::TextManipulationControllerManipulationFailure>&&)>&&);
 
     const String& overriddenMediaType() const { return m_overriddenMediaType; }
     void setOverriddenMediaType(const String&);
diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.cpp b/Source/WebKit/WebProcess/WebPage/WebPage.cpp
index ea6310bd2e85..618548eef32e 100644
--- a/Source/WebKit/WebProcess/WebPage/WebPage.cpp
+++ b/Source/WebKit/WebProcess/WebPage/WebPage.cpp
@@ -8904,7 +8904,7 @@ void WebPage::startTextManipulations(Vector<WebCore::TextManipulationController:
     m_textManipulationExclusionRules = WTFMove(exclusionRules);
     m_textManipulationIncludesSubframes = includeSubframes;
     if (m_textManipulationIncludesSubframes) {
-        for (RefPtr<Frame> frame = m_mainFrame->coreLocalFrame(); frame; frame = frame->tree().traverseNextRendered())
+        for (RefPtr<Frame> frame = m_mainFrame->coreFrame(); frame; frame = frame->tree().traverseNext())
             startTextManipulationForFrame(*frame);
     } else if (RefPtr frame = m_mainFrame->coreLocalFrame())
         startTextManipulationForFrame(*frame);
@@ -8935,33 +8935,33 @@ void WebPage::startTextManipulationForFrame(WebCore::Frame& frame)
 }
 
 void WebPage::completeTextManipulation(const Vector<WebCore::TextManipulationItem>& items,
-    CompletionHandler<void(bool allFailed, const Vector<WebCore::TextManipulationController::ManipulationFailure>&)>&& completionHandler)
+    CompletionHandler<void(const WebCore::TextManipulationController::ManipulationResult&)>&& completionHandler)
 {
     if (!m_page) {
-        completionHandler(true, { });
+        completionHandler({ });
         return;
     }
 
     if (items.isEmpty()) {
-        completionHandler(true, { });
+        completionHandler({ });
         return;
     }
 
     auto currentFrameID = items[0].frameID;
 
-    auto completeManipulationForItems = [&](const Vector<WebCore::TextManipulationItem>& items) -> std::optional<Vector<TextManipulationControllerManipulationFailure>> {
+    auto completeManipulationForItems = [&](const Vector<WebCore::TextManipulationItem>& items) -> WebCore::TextManipulationController::ManipulationResult {
         ASSERT(!items.isEmpty());
         RefPtr frame = WebProcess::singleton().webFrame(currentFrameID);
         if (!frame)
-            return std::nullopt;
+            return { };
 
         RefPtr coreFrame = frame->coreLocalFrame();
         if (!coreFrame)
-            return std::nullopt;
+            return { };
 
-        auto* controller = coreFrame->document()->textManipulationControllerIfExists();
+        CheckedPtr controller = coreFrame->document()->textManipulationControllerIfExists();
         if (!controller)
-            return std::nullopt;
+            return { };
 
         return controller->completeManipulation(items);
     };
@@ -8969,32 +8969,19 @@ void WebPage::completeTextManipulation(const Vector<WebCore::TextManipulationIte
     bool containsItemsForMultipleFrames = WTF::anyOf(items, [&](auto& item) {
         return currentFrameID != item.frameID;
     });
-    if (!containsItemsForMultipleFrames) {
-        auto failures = completeManipulationForItems(items);
-        if (failures)
-            completionHandler(false, *failures);
-        else
-            completionHandler(true, { });
-        return;
-    }
+    if (!containsItemsForMultipleFrames)
+        return completionHandler(completeManipulationForItems(items));
 
-    Vector<WebCore::TextManipulationController::ManipulationFailure> failuresForAllItems;
+    WebCore::TextManipulationController::ManipulationResult resultForAllItems;
 
     auto completeManipulationForCurrentFrame = [&](uint64_t startIndexForCurrentFrame, Vector<WebCore::TextManipulationItem> itemsForCurrentFrame) {
-        auto failures = completeManipulationForItems(std::exchange(itemsForCurrentFrame, { }));
-        if (!failures) {
-            uint64_t index = startIndexForCurrentFrame;
-            for (auto& item : itemsForCurrentFrame) {
-                failuresForAllItems.append(TextManipulationControllerManipulationFailure {
-                    *item.frameID, item.identifier, index, TextManipulationControllerManipulationFailure::Type::NotAvailable });
-                ++index;
-            }
-            return;
-        }
-        for (auto& failure : *failures) {
-            failuresForAllItems.append(WTFMove(failure));
-            failuresForAllItems.last().index += startIndexForCurrentFrame;
-        }
+        auto result = completeManipulationForItems(std::exchange(itemsForCurrentFrame, { }));
+        for (auto& failure : result.failures)
+            failure.index += startIndexForCurrentFrame;
+        for (auto& index : result.succeededIndexes)
+            index += startIndexForCurrentFrame;
+        resultForAllItems.failures.appendVector(WTFMove(result.failures));
+        resultForAllItems.succeededIndexes.appendVector(WTFMove(result.succeededIndexes));
     };
 
     uint64_t indexForCurrentItem = 0;
@@ -9012,7 +8999,7 @@ void WebPage::completeTextManipulation(const Vector<WebCore::TextManipulationIte
     RELEASE_ASSERT(indexForCurrentItem >= itemCount);
     completeManipulationForCurrentFrame(indexForCurrentItem - itemCount, items.subspan(indexForCurrentItem - itemCount, itemCount));
 
-    completionHandler(false, WTFMove(failuresForAllItems));
+    completionHandler(resultForAllItems);
 }
 
 PAL::SessionID WebPage::sessionID() const
diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.h b/Source/WebKit/WebProcess/WebPage/WebPage.h
index bc84b6d68d2d..762b5ab7d943 100644
--- a/Source/WebKit/WebProcess/WebPage/WebPage.h
+++ b/Source/WebKit/WebProcess/WebPage/WebPage.h
@@ -1672,7 +1672,7 @@ class WebPage final : public API::ObjectImpl<API::Object::Type::BundlePage>, pub
     std::optional<WebCore::ElementContext> contextForElement(const WebCore::Element&) const;
 
     void startTextManipulations(Vector<WebCore::TextManipulationController::ExclusionRule>&&, bool includesSubframes, CompletionHandler<void()>&&);
-    void completeTextManipulation(const Vector<WebCore::TextManipulationItem>&, CompletionHandler<void(bool allFailed, const Vector<WebCore::TextManipulationController::ManipulationFailure>&)>&&);
+    void completeTextManipulation(const Vector<WebCore::TextManipulationItem>&, CompletionHandler<void(const WebCore::TextManipulationController::ManipulationResult&)>&&);
 
 #if ENABLE(APPLE_PAY)
     WebPaymentCoordinator* paymentCoordinator();
diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.messages.in b/Source/WebKit/WebProcess/WebPage/WebPage.messages.in
index aa0b7dd7005d..d086afad45fa 100644
--- a/Source/WebKit/WebProcess/WebPage/WebPage.messages.in
+++ b/Source/WebKit/WebProcess/WebPage/WebPage.messages.in
@@ -702,7 +702,7 @@ messages -> WebPage WantsAsyncDispatchMessage {
 #endif
 
     StartTextManipulations(Vector<WebCore::TextManipulationControllerExclusionRule> exclusionRules, bool includeSubframes) -> ()
-    CompleteTextManipulation(Vector<WebCore::TextManipulationItem> items) -> (bool allFailed, Vector<WebCore::TextManipulationControllerManipulationFailure> failures)
+    CompleteTextManipulation(Vector<WebCore::TextManipulationItem> items) -> (struct WebCore::TextManipulationControllerManipulationResult result)
 
     SetOverriddenMediaType(String mediaType)
     GetProcessDisplayName() -> (String displayName)
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/SiteIsolation.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/SiteIsolation.mm
index 7ecdaea65e84..bc926f9de6f5 100644
--- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/SiteIsolation.mm
+++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/SiteIsolation.mm
@@ -30,6 +30,7 @@
 #import "PlatformUtilities.h"
 #import "TestCocoa.h"
 #import "TestNavigationDelegate.h"
+#import "TestScriptMessageHandler.h"
 #import "TestUIDelegate.h"
 #import "TestURLSchemeHandler.h"
 #import "TestWKWebView.h"
@@ -50,10 +51,42 @@
 #import <WebKit/_WKFeature.h>
 #import <WebKit/_WKFrameTreeNode.h>
 #import <WebKit/_WKProcessPoolConfiguration.h>
+#import <WebKit/_WKTextManipulationConfiguration.h>
+#import <WebKit/_WKTextManipulationDelegate.h>
+#import <WebKit/_WKTextManipulationItem.h>
+#import <WebKit/_WKTextManipulationToken.h>
 #import <WebKit/_WKWebsiteDataStoreConfiguration.h>
 #import <wtf/BlockPtr.h>
 #import <wtf/text/MakeString.h>
 
+@interface SiteIsolationTextManipulationDelegate : NSObject <_WKTextManipulationDelegate>
+- (void)_webView:(WKWebView *)webView didFindTextManipulationItems:(NSArray<_WKTextManipulationItem *> *)items;
+@property (nonatomic, readonly, copy) NSArray<_WKTextManipulationItem *> *items;
+@end
+
+@implementation SiteIsolationTextManipulationDelegate {
+    RetainPtr<NSMutableArray> _items;
+}
+
+- (instancetype)init
+{
+    if (!(self = [super init]))
+        return nil;
+    _items = adoptNS([[NSMutableArray alloc] init]);
+    return self;
+}
+
+- (void)_webView:(WKWebView *)webView didFindTextManipulationItems:(NSArray<_WKTextManipulationItem *> *)items
+{
+    [_items addObjectsFromArray:items];
+}
+
+- (NSArray<_WKTextManipulationItem *> *)items
+{
+    return _items.get();
+}
+@end
+
 @interface NavigationDelegateAllowingAllTLS : NSObject<WKNavigationDelegate>
 - (void)waitForDidFinishNavigation;
 @end
@@ -4369,6 +4402,214 @@ HTTPServer server({
     Util::run(&done);
 }
 
+RetainPtr<_WKTextManipulationToken> createToken(NSString *identifier, NSString *content)
+{
+    RetainPtr<_WKTextManipulationToken> token = adoptNS([[_WKTextManipulationToken alloc] init]);
+    [token setIdentifier: identifier];
+    [token setContent: content];
+    return token;
+}
+
+static RetainPtr<_WKTextManipulationItem> createItem(NSString *itemIdentifier, const Vector<RetainPtr<_WKTextManipulationToken>>& tokens)
+{
+    RetainPtr<NSMutableArray> wkTokens = adoptNS([[NSMutableArray alloc] init]);
+    for (auto& token : tokens)
+        [wkTokens addObject:token.get()];
+
+    return adoptNS([[_WKTextManipulationItem alloc] initWithIdentifier:itemIdentifier tokens:wkTokens.get()]);
+}
+
+TEST(SiteIsolation, CompleteTextManipulation)
+{
+    static constexpr auto mainFrameBytes = R"TESTRESOURCE(
+    <div id='text'>mainframe content</div>
+    <script>
+        function getTextContent() {
+            window.webkit.messageHandlers.testHandler.postMessage(document.getElementById('text').innerHTML);
+        }
+        function getIframeTextContent() {
+            document.getElementById('iframe').contentWindow.postMessage('print', '*');
+        }
+        function postResult(event) {
+            window.webkit.messageHandlers.testHandler.postMessage(event.data);
+        }
+        addEventListener('message', postResult, false);
+    </script>
+    <iframe id='iframe' src='https://apple.com/apple'></iframe>
+    )TESTRESOURCE"_s;
+
+    static constexpr auto iframeBytes = R"TESTRESOURCE(
+    <div id='text'>iframe content</div>
+    <script>
+        addEventListener('message', () => {
+            let textElement = document.getElementById('text');
+            parent.postMessage(textElement.innerHTML, '*');
+        }, false);
+        parent.postMessage('loaded', '*');
+    </script>
+    )TESTRESOURCE"_s;
+
+    HTTPServer server({
+        { "/example"_s, { mainFrameBytes } },
+        { "/apple"_s, { iframeBytes } },
+    }, HTTPServer::Protocol::HttpsProxy);
+
+    bool didLoad = false;
+    bool didReceiveMainFrameContent = false;
+    bool didReceiveIframeContent = false;
+    auto webViewAndDelegates = makeWebViewAndDelegates(server);
+    [webViewAndDelegates.messageHandler addMessage:@"loaded" withHandler:[&]() {
+        didLoad = true;
+    }];
+    [webViewAndDelegates.messageHandler addMessage:@"MAINFRAME CONTENT" withHandler:[&]() {
+        didReceiveMainFrameContent = true;
+    }];
+    [webViewAndDelegates.messageHandler addMessage:@"IFRAME CONTENT" withHandler:[&]() {
+        didReceiveIframeContent = true;
+    }];
+    RetainPtr webView = webViewAndDelegates.webView;
+    RetainPtr textManipulationDelegate = adoptNS([[SiteIsolationTextManipulationDelegate alloc] init]);
+    [webView _setTextManipulationDelegate:textManipulationDelegate.get()];
+    RetainPtr manipulationConfiguration = adoptNS([[_WKTextManipulationConfiguration alloc] init]);
+    manipulationConfiguration.get().includeSubframes = YES;
+
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
+    Util::run(&didLoad);
+
+    [webView _startTextManipulationsWithConfiguration:manipulationConfiguration.get() completion:^{ }];
+    while ([textManipulationDelegate items].count < 2)
+        Util::spinRunLoop();
+
+    RetainPtr items = [textManipulationDelegate items];
+    auto sortFunction = ^(_WKTextManipulationItem *item1, _WKTextManipulationItem *item2) {
+        auto value1 = [NSNumber numberWithBool:item1.isSubframe];
+        auto value2 = [NSNumber numberWithBool:item2.isSubframe];
+        return [value1 compare:value2];
+    };
+    RetainPtr sortedItems = [items.get() sortedArrayUsingComparator:sortFunction];
+    EXPECT_EQ(items.get().count, 2UL);
+    auto firstItem = [sortedItems objectAtIndex:0];
+    auto secondItem = [sortedItems objectAtIndex:1];
+    EXPECT_EQ(firstItem.isSubframe, NO);
+    EXPECT_EQ(firstItem.isCrossSiteSubframe, NO);
+    EXPECT_EQ(firstItem.tokens.count, 1UL);
+    EXPECT_STREQ("mainframe content", firstItem.tokens[0].content.UTF8String);
+    EXPECT_EQ(secondItem.isSubframe, YES);
+    EXPECT_EQ(secondItem.isCrossSiteSubframe, YES);
+    EXPECT_EQ(secondItem.tokens.count, 1UL);
+    EXPECT_STREQ("iframe content", secondItem.tokens[0].content.UTF8String);
+
+    __block bool done = false;
+    [webView _completeTextManipulationForItems:@[
+        (_WKTextManipulationItem *)createItem(firstItem.identifier, { createToken(firstItem.tokens[0].identifier, @"MAINFRAME CONTENT") }),
+        (_WKTextManipulationItem *)createItem(secondItem.identifier, { createToken(secondItem.tokens[0].identifier, @"IFRAME CONTENT") })
+    ] completion:^(NSArray<NSError *> *errors) {
+        EXPECT_EQ(errors, nil);
+        done = true;
+    }];
+    Util::run(&done);
+
+    [webView evaluateJavaScript:@"getTextContent()" completionHandler:nil];
+    Util::run(&didReceiveMainFrameContent);
+
+    [webView evaluateJavaScript:@"getIframeTextContent()" completionHandler:nil];
+    Util::run(&didReceiveIframeContent);
+}
+
+TEST(SiteIsolation, CompleteTextManipulationFailsInSomeFrame)
+{
+    static constexpr auto mainFrameBytes = R"TESTRESOURCE(
+    <div>mainframe content</div>
+    <script>
+        function removeIframe() {
+            let element = document.getElementById('iframe');
+            element.parentNode.removeChild(element);
+        }
+        let messageCount = 0;
+        addEventListener('message', () => {
+            if (++messageCount == 2)
+                window.webkit.messageHandlers.testHandler.postMessage('loaded');
+        }, false);
+    </script>
+    <iframe id='iframe' src='https://apple.com/iframe'></iframe>
+    <iframe src='https://webkit.org/iframe'></iframe>
+    )TESTRESOURCE"_s;
+
+    static constexpr auto iframeBytes = R"TESTRESOURCE(
+    <div>iframe content</div>
+    <script>
+        parent.postMessage('loaded', '*');
+    </script>
+    )TESTRESOURCE"_s;
+
+    HTTPServer server({
+        { "/mainframe"_s, { mainFrameBytes } },
+        { "/iframe"_s, { iframeBytes } }
+    }, HTTPServer::Protocol::HttpsProxy);
+
+    bool receivedMessage = false;
+    auto webViewAndDelegates = makeWebViewAndDelegates(server);
+    [webViewAndDelegates.messageHandler addMessage:@"loaded" withHandler:[&]() {
+        receivedMessage = true;
+    }];
+    RetainPtr webView = webViewAndDelegates.webView;
+    RetainPtr textManipulationDelegate = adoptNS([[SiteIsolationTextManipulationDelegate alloc] init]);
+    [webView _setTextManipulationDelegate:textManipulationDelegate.get()];
+    RetainPtr manipulationConfiguration = adoptNS([[_WKTextManipulationConfiguration alloc] init]);
+    manipulationConfiguration.get().includeSubframes = YES;
+
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/mainframe"]]];
+    Util::run(&receivedMessage);
+
+    [webView _startTextManipulationsWithConfiguration:manipulationConfiguration.get() completion:^{ }];
+    while ([textManipulationDelegate items].count < 3)
+        Util::spinRunLoop();
+
+    RetainPtr items = [textManipulationDelegate items];
+    auto sortFunction = ^(_WKTextManipulationItem *item1, _WKTextManipulationItem *item2) {
+        auto value1 = [NSNumber numberWithBool:item1.isSubframe];
+        auto value2 = [NSNumber numberWithBool:item2.isSubframe];
+        return [value1 compare:value2];
+    };
+
+    RetainPtr sortedItems = [items.get() sortedArrayUsingComparator:sortFunction];
+    EXPECT_EQ(items.get().count, 3UL);
+    auto item1 = [sortedItems objectAtIndex:0];
+    auto item2 = [sortedItems objectAtIndex:1];
+    auto item3 = [sortedItems objectAtIndex:2];
+    EXPECT_EQ(item1.isSubframe, NO);
+    EXPECT_EQ(item1.isCrossSiteSubframe, NO);
+    EXPECT_EQ(item1.tokens.count, 1UL);
+    EXPECT_STREQ("mainframe content", item1.tokens[0].content.UTF8String);
+    EXPECT_EQ(item2.isSubframe, YES);
+    EXPECT_EQ(item2.isCrossSiteSubframe, YES);
+    EXPECT_EQ(item2.tokens.count, 1UL);
+    EXPECT_STREQ("iframe content", item2.tokens[0].content.UTF8String);
+    EXPECT_EQ(item3.isSubframe, YES);
+    EXPECT_EQ(item3.isCrossSiteSubframe, YES);
+    EXPECT_EQ(item3.tokens.count, 1UL);
+    EXPECT_STREQ("iframe content", item3.tokens[0].content.UTF8String);
+
+    __block bool done = false;
+    [webView evaluateJavaScript:@"removeIframe()" completionHandler:^(id, NSError *) {
+        done = true;
+    }];
+    Util::run(&done);
+
+    __block RetainPtr newItem1 = createItem(item1.identifier, { createToken(item1.tokens[0].identifier, @"MAINFRAME CONTENT") });
+    __block RetainPtr newItem2 = createItem(item2.identifier, { createToken(item2.tokens[0].identifier, @"IFRAME CONTENT") });
+    __block RetainPtr newItem3 = createItem(item3.identifier, { createToken(item3.tokens[0].identifier, @"IFRAME CONTENT") });
+    [webView _completeTextManipulationForItems:@[ newItem1.get(), newItem2.get(), newItem3.get()] completion:^(NSArray<NSError *> *errors) {
+        EXPECT_NOT_NULL(errors);
+        EXPECT_EQ(errors.count, 1UL);
+        EXPECT_EQ(errors.firstObject.domain, _WKTextManipulationItemErrorDomain);
+        EXPECT_EQ(errors.firstObject.code, _WKTextManipulationItemErrorContentChanged);
+        EXPECT_EQ(errors.firstObject.userInfo[_WKTextManipulationItemErrorItemKey], newItem2.get());
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+}
+
 // FIXME: Re-enable this once the extra resize events are gone.
 // https://bugs.webkit.org/show_bug.cgi?id=292311 might do it.
 TEST(SiteIsolation, DISABLED_Events)


Reviews:
user: login: rniwa
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-30T23:39:52Z
user: login: rniwa
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-30T23:45:44Z
user: login: szewai
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T04:55:18Z
user: login: rniwa
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T05:44:15Z
user: login: rniwa
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T05:47:19Z
user: login: rniwa
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T05:48:00Z
user: login: rniwa
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T06:03:33Z
user: login: rniwa
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T06:07:55Z
user: login: rniwa
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T06:09:49Z
user: login: rniwa
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T06:11:10Z
user: login: rniwa
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T06:12:32Z
user: login: szewai
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T17:08:45Z
user: login: szewai
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T17:29:39Z
user: login: rniwa
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T17:35:44Z
user: login: rniwa
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-02T17:37:00Z
user: login: rniwa
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T17:37:40Z

Review comments:
diff_hunk: @@ -14936,11 +14935,45 @@ void WebPageProxy::didFindTextManipulationItems(const Vector<WebCore::TextManipu
 
 void WebPageProxy::completeTextManipulation(const Vector<TextManipulationItem>& items, Function<void(bool allFailed, const Vector<TextManipulationControllerManipulationFailure>&)>&& completionHandler)
 {
-    if (!hasRunningProcess()) {
-        completionHandler(true, { });
-        return;
-    }
-    sendWithAsyncReply(Messages::WebPage::CompleteTextManipulation(items), WTFMove(completionHandler));
+    class TextManipulationCallbackAggregator final : public ThreadSafeRefCounted<TextManipulationCallbackAggregator, WTF::DestructionThread::MainRunLoop> {
+    public:
+        using Callback = CompletionHandler<void(bool, const Vector<TextManipulationControllerManipulationFailure>&)>;
+        static Ref<TextManipulationCallbackAggregator> create(Callback&& callback)
+        {
+            return adoptRef(*new TextManipulationCallbackAggregator(WTFMove(callback)));
+        }
+        ~TextManipulationCallbackAggregator()
path: Source/WebKit/UIProcess/WebPageProxy.cpp
user: login: rniwa
body: Maybe insert a blank line between each function definition?
created_at: 2025-04-30T23:39:52Z
author_association: MEMBER
diff_hunk: @@ -14936,11 +14935,45 @@ void WebPageProxy::didFindTextManipulationItems(const Vector<WebCore::TextManipu
 
 void WebPageProxy::completeTextManipulation(const Vector<TextManipulationItem>& items, Function<void(bool allFailed, const Vector<TextManipulationControllerManipulationFailure>&)>&& completionHandler)
 {
-    if (!hasRunningProcess()) {
-        completionHandler(true, { });
-        return;
-    }
-    sendWithAsyncReply(Messages::WebPage::CompleteTextManipulation(items), WTFMove(completionHandler));
+    class TextManipulationCallbackAggregator final : public ThreadSafeRefCounted<TextManipulationCallbackAggregator, WTF::DestructionThread::MainRunLoop> {
+    public:
+        using Callback = CompletionHandler<void(bool, const Vector<TextManipulationControllerManipulationFailure>&)>;
+        static Ref<TextManipulationCallbackAggregator> create(Callback&& callback)
+        {
+            return adoptRef(*new TextManipulationCallbackAggregator(WTFMove(callback)));
+        }
+        ~TextManipulationCallbackAggregator()
+        {
+            ASSERT(RunLoop::isMain());
+            m_callback(m_allFailed, m_failures);
+        }
+        void addResult(bool allFailed, const Vector<TextManipulationControllerManipulationFailure>& failures)
+        {
+            if (allFailed)
+                return;
+
+            if (m_allFailed)
+                m_allFailed = false;
+            m_failures.appendVector(failures);
path: Source/WebKit/UIProcess/WebPageProxy.cpp
user: login: rniwa
body: Hm... this doesn't seem right to me. If one of the process fails all of them, we'd get `allFailed` true but that doesn't necessarily mean `m_allFailed` is set to `true`. In that case, we need to generate errors for all the items which have thus far failed.
created_at: 2025-04-30T23:45:44Z
author_association: MEMBER
diff_hunk: @@ -14936,11 +14935,45 @@ void WebPageProxy::didFindTextManipulationItems(const Vector<WebCore::TextManipu
 
 void WebPageProxy::completeTextManipulation(const Vector<TextManipulationItem>& items, Function<void(bool allFailed, const Vector<TextManipulationControllerManipulationFailure>&)>&& completionHandler)
 {
-    if (!hasRunningProcess()) {
-        completionHandler(true, { });
-        return;
-    }
-    sendWithAsyncReply(Messages::WebPage::CompleteTextManipulation(items), WTFMove(completionHandler));
+    class TextManipulationCallbackAggregator final : public ThreadSafeRefCounted<TextManipulationCallbackAggregator, WTF::DestructionThread::MainRunLoop> {
+    public:
+        using Callback = CompletionHandler<void(bool, const Vector<TextManipulationControllerManipulationFailure>&)>;
+        static Ref<TextManipulationCallbackAggregator> create(Callback&& callback)
+        {
+            return adoptRef(*new TextManipulationCallbackAggregator(WTFMove(callback)));
+        }
+        ~TextManipulationCallbackAggregator()
+        {
+            ASSERT(RunLoop::isMain());
+            m_callback(m_allFailed, m_failures);
+        }
+        void addResult(bool allFailed, const Vector<TextManipulationControllerManipulationFailure>& failures)
+        {
+            if (allFailed)
+                return;
+
+            if (m_allFailed)
+                m_allFailed = false;
+            m_failures.appendVector(failures);
path: Source/WebKit/UIProcess/WebPageProxy.cpp
user: login: szewai
body: Fixed by making web process also report the item indexes that have been successfully replaced, so that UI process can compute the state of each item and generate errors for the ones that are not successfully replaced (and don't have an explicit error).
created_at: 2025-05-02T04:55:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -14934,13 +14933,70 @@ void WebPageProxy::didFindTextManipulationItems(const Vector<WebCore::TextManipu
     m_textManipulationItemCallback(items);
 }
 
-void WebPageProxy::completeTextManipulation(const Vector<TextManipulationItem>& items, Function<void(bool allFailed, const Vector<TextManipulationControllerManipulationFailure>&)>&& completionHandler)
+void WebPageProxy::completeTextManipulation(const Vector<TextManipulationItem>& items, CompletionHandler<void(Vector<TextManipulationControllerManipulationFailure>&&)>&& completionHandler)
 {
-    if (!hasRunningProcess()) {
-        completionHandler(true, { });
-        return;
-    }
-    sendWithAsyncReply(Messages::WebPage::CompleteTextManipulation(items), WTFMove(completionHandler));
+    class TextManipulationCallbackAggregator final : public ThreadSafeRefCounted<TextManipulationCallbackAggregator, WTF::DestructionThread::MainRunLoop> {
+    public:
+        struct ItemInfo {
+            Markable<FrameIdentifier> frameID;
+            Markable<TextManipulationItemIdentifier> identifier;
+        };
+
+        using Callback = CompletionHandler<void(Vector<TextManipulationControllerManipulationFailure>&&)>;
+
+        static Ref<TextManipulationCallbackAggregator> create(Vector<ItemInfo>&& items, Callback&& callback)
+        {
+            return adoptRef(*new TextManipulationCallbackAggregator(WTFMove(items), WTFMove(callback)));
+        }
+
+        ~TextManipulationCallbackAggregator()
+        {
+            ASSERT(RunLoop::isMain());
+            HashSet<unsigned> resultIndexes;
+            for (auto& failure : m_result.failures)
+                resultIndexes.add(failure.index + 1);
path: Source/WebKit/UIProcess/WebPageProxy.cpp
user: login: rniwa
body: This whole business about adding 1 to avoid 0 is very confusing.
I think we should use `UnsignedWithZeroKeyHashTraits` instead.
created_at: 2025-05-02T05:44:14Z
author_association: MEMBER
diff_hunk: @@ -717,8 +718,12 @@ auto TextManipulationController::completeManipulation(const Vector<WebCore::Text
         std::exchange(itemData, itemDataIterator->value);
         m_items.remove(itemDataIterator);
 
-        if (auto failureOrNullopt = replace(itemData, itemToComplete.tokens, containersWithoutVisualOverflowBeforeReplacement))
+        if (auto failureOrNullopt = replace(itemData, itemToComplete.tokens, containersWithoutVisualOverflowBeforeReplacement)) {
             failures.append(ManipulationFailure { *frameID, itemID, i, *failureOrNullopt });
+            continue;
+        }
+
+        succeededIndexes.append(i);
path: Source/WebCore/editing/TextManipulationController.cpp
user: login: rniwa
body: Instead of appending a full integer to store the index of success, why don't we create BitVector of length matching the length of completionItems? Then you can just check BitVector::get(index) to check whether the given index has succeeded or not.
created_at: 2025-05-02T05:47:18Z
author_association: MEMBER
diff_hunk: @@ -14934,13 +14933,70 @@ void WebPageProxy::didFindTextManipulationItems(const Vector<WebCore::TextManipu
     m_textManipulationItemCallback(items);
 }
 
-void WebPageProxy::completeTextManipulation(const Vector<TextManipulationItem>& items, Function<void(bool allFailed, const Vector<TextManipulationControllerManipulationFailure>&)>&& completionHandler)
+void WebPageProxy::completeTextManipulation(const Vector<TextManipulationItem>& items, CompletionHandler<void(Vector<TextManipulationControllerManipulationFailure>&&)>&& completionHandler)
 {
-    if (!hasRunningProcess()) {
-        completionHandler(true, { });
-        return;
-    }
-    sendWithAsyncReply(Messages::WebPage::CompleteTextManipulation(items), WTFMove(completionHandler));
+    class TextManipulationCallbackAggregator final : public ThreadSafeRefCounted<TextManipulationCallbackAggregator, WTF::DestructionThread::MainRunLoop> {
+    public:
+        struct ItemInfo {
+            Markable<FrameIdentifier> frameID;
+            Markable<TextManipulationItemIdentifier> identifier;
+        };
+
+        using Callback = CompletionHandler<void(Vector<TextManipulationControllerManipulationFailure>&&)>;
+
+        static Ref<TextManipulationCallbackAggregator> create(Vector<ItemInfo>&& items, Callback&& callback)
+        {
+            return adoptRef(*new TextManipulationCallbackAggregator(WTFMove(items), WTFMove(callback)));
+        }
+
+        ~TextManipulationCallbackAggregator()
+        {
+            ASSERT(RunLoop::isMain());
+            HashSet<unsigned> resultIndexes;
+            for (auto& failure : m_result.failures)
+                resultIndexes.add(failure.index + 1);
path: Source/WebKit/UIProcess/WebPageProxy.cpp
user: login: rniwa
body: If we used `BitVector` instead, we don't need to have this `HashSet` at all so that might be better altogether.
created_at: 2025-05-02T05:48:00Z
author_association: MEMBER
diff_hunk: @@ -4369,6 +4402,214 @@ HTTPServer server({
     Util::run(&done);
 }
 
+RetainPtr<_WKTextManipulationToken> createToken(NSString *identifier, NSString *content)
+{
+    RetainPtr<_WKTextManipulationToken> token = adoptNS([[_WKTextManipulationToken alloc] init]);
+    [token setIdentifier: identifier];
+    [token setContent: content];
+    return token;
+}
+
+static RetainPtr<_WKTextManipulationItem> createItem(NSString *itemIdentifier, const Vector<RetainPtr<_WKTextManipulationToken>>& tokens)
+{
+    RetainPtr<NSMutableArray> wkTokens = adoptNS([[NSMutableArray alloc] init]);
+    for (auto& token : tokens)
+        [wkTokens addObject:token.get()];
+
+    return adoptNS([[_WKTextManipulationItem alloc] initWithIdentifier:itemIdentifier tokens:wkTokens.get()]);
+}
+
+TEST(SiteIsolation, CompleteTextManipulation)
+{
+    static constexpr auto mainFrameBytes = R"TESTRESOURCE(
+    <div id='text'>mainframe content</div>
+    <script>
+        function getTextContent() {
+            window.webkit.messageHandlers.testHandler.postMessage(document.getElementById('text').innerHTML);
+        }
+        function getIframeTextContent() {
+            document.getElementById('iframe').contentWindow.postMessage('print', '*');
+        }
+        function postResult(event) {
+            window.webkit.messageHandlers.testHandler.postMessage(event.data);
+        }
+        addEventListener('message', postResult, false);
+    </script>
+    <iframe id='iframe' src='https://apple.com/apple'></iframe>
+    )TESTRESOURCE"_s;
+
+    static constexpr auto iframeBytes = R"TESTRESOURCE(
+    <div id='text'>iframe content</div>
+    <script>
+        addEventListener('message', () => {
+            let textElement = document.getElementById('text');
+            parent.postMessage(textElement.innerHTML, '*');
+        }, false);
+        parent.postMessage('loaded', '*');
+    </script>
+    )TESTRESOURCE"_s;
+
+    HTTPServer server({
+        { "/example"_s, { mainFrameBytes } },
+        { "/apple"_s, { iframeBytes } },
+    }, HTTPServer::Protocol::HttpsProxy);
+
+    bool receivedMessage1 = false;
path: Tools/TestWebKitAPI/Tests/WebKitCocoa/SiteIsolation.mm
user: login: rniwa
body: Can we give them more descriptive variable names like `didLoad`, `didReceiveMainFrameContent`, and `didReceiveIframeContent`?
created_at: 2025-05-02T06:03:33Z
author_association: MEMBER
diff_hunk: @@ -4369,6 +4402,214 @@ HTTPServer server({
     Util::run(&done);
 }
 
+RetainPtr<_WKTextManipulationToken> createToken(NSString *identifier, NSString *content)
+{
+    RetainPtr<_WKTextManipulationToken> token = adoptNS([[_WKTextManipulationToken alloc] init]);
+    [token setIdentifier: identifier];
+    [token setContent: content];
+    return token;
+}
+
+static RetainPtr<_WKTextManipulationItem> createItem(NSString *itemIdentifier, const Vector<RetainPtr<_WKTextManipulationToken>>& tokens)
+{
+    RetainPtr<NSMutableArray> wkTokens = adoptNS([[NSMutableArray alloc] init]);
+    for (auto& token : tokens)
+        [wkTokens addObject:token.get()];
+
+    return adoptNS([[_WKTextManipulationItem alloc] initWithIdentifier:itemIdentifier tokens:wkTokens.get()]);
+}
+
+TEST(SiteIsolation, CompleteTextManipulation)
+{
+    static constexpr auto mainFrameBytes = R"TESTRESOURCE(
+    <div id='text'>mainframe content</div>
+    <script>
+        function getTextContent() {
+            window.webkit.messageHandlers.testHandler.postMessage(document.getElementById('text').innerHTML);
+        }
+        function getIframeTextContent() {
+            document.getElementById('iframe').contentWindow.postMessage('print', '*');
+        }
+        function postResult(event) {
+            window.webkit.messageHandlers.testHandler.postMessage(event.data);
+        }
+        addEventListener('message', postResult, false);
+    </script>
+    <iframe id='iframe' src='https://apple.com/apple'></iframe>
+    )TESTRESOURCE"_s;
+
+    static constexpr auto iframeBytes = R"TESTRESOURCE(
+    <div id='text'>iframe content</div>
+    <script>
+        addEventListener('message', () => {
+            let textElement = document.getElementById('text');
+            parent.postMessage(textElement.innerHTML, '*');
+        }, false);
+        parent.postMessage('loaded', '*');
+    </script>
+    )TESTRESOURCE"_s;
+
+    HTTPServer server({
+        { "/example"_s, { mainFrameBytes } },
+        { "/apple"_s, { iframeBytes } },
+    }, HTTPServer::Protocol::HttpsProxy);
+
+    bool receivedMessage1 = false;
+    bool receivedMessage2 = false;
+    bool receivedMessage3 = false;
+    auto webViewAndDelegates = makeWebViewAndDelegates(server);
+    [webViewAndDelegates.messageHandler addMessage:@"loaded" withHandler:[&]() {
+        receivedMessage1 = true;
+    }];
+    [webViewAndDelegates.messageHandler addMessage:@"MAINFRAME CONTENT" withHandler:[&]() {
+        receivedMessage2 = true;
+    }];
+    [webViewAndDelegates.messageHandler addMessage:@"IFRAME CONTENT" withHandler:[&]() {
+        receivedMessage3 = true;
+    }];
+    RetainPtr webView = webViewAndDelegates.webView;
+    RetainPtr textManipulationDelegate = adoptNS([[SiteIsolationTextManipulationDelegate alloc] init]);
+    [webView _setTextManipulationDelegate:textManipulationDelegate.get()];
+    RetainPtr manipulationConfiguration = adoptNS([[_WKTextManipulationConfiguration alloc] init]);
+    manipulationConfiguration.get().includeSubframes = YES;
+
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
+    Util::run(&receivedMessage1);
+
+    [webView _startTextManipulationsWithConfiguration:manipulationConfiguration.get() completion:^{ }];
+    while ([textManipulationDelegate items].count < 2)
+        Util::spinRunLoop();
+
+    RetainPtr items = [textManipulationDelegate items];
+    auto sortFunction = ^(_WKTextManipulationItem *item1, _WKTextManipulationItem *item2) {
+        auto value1 = [NSNumber numberWithBool:item1.isSubframe];
+        auto value2 = [NSNumber numberWithBool:item2.isSubframe];
+        return [value1 compare:value2];
+    };
+    RetainPtr sortedItems = [items.get() sortedArrayUsingComparator:sortFunction];
path: Tools/TestWebKitAPI/Tests/WebKitCocoa/SiteIsolation.mm
user: login: rniwa
body: Why do we need to sort the results? Given we're waiting for both the main frame and the iframe to load, the order should be deterministic: prefix DFS.
created_at: 2025-05-02T06:07:55Z
author_association: MEMBER
diff_hunk: @@ -4369,6 +4402,214 @@ HTTPServer server({
     Util::run(&done);
 }
 
+RetainPtr<_WKTextManipulationToken> createToken(NSString *identifier, NSString *content)
+{
+    RetainPtr<_WKTextManipulationToken> token = adoptNS([[_WKTextManipulationToken alloc] init]);
+    [token setIdentifier: identifier];
+    [token setContent: content];
+    return token;
+}
+
+static RetainPtr<_WKTextManipulationItem> createItem(NSString *itemIdentifier, const Vector<RetainPtr<_WKTextManipulationToken>>& tokens)
+{
+    RetainPtr<NSMutableArray> wkTokens = adoptNS([[NSMutableArray alloc] init]);
+    for (auto& token : tokens)
+        [wkTokens addObject:token.get()];
+
+    return adoptNS([[_WKTextManipulationItem alloc] initWithIdentifier:itemIdentifier tokens:wkTokens.get()]);
+}
+
+TEST(SiteIsolation, CompleteTextManipulation)
+{
+    static constexpr auto mainFrameBytes = R"TESTRESOURCE(
+    <div id='text'>mainframe content</div>
+    <script>
+        function getTextContent() {
+            window.webkit.messageHandlers.testHandler.postMessage(document.getElementById('text').innerHTML);
+        }
+        function getIframeTextContent() {
+            document.getElementById('iframe').contentWindow.postMessage('print', '*');
+        }
+        function postResult(event) {
+            window.webkit.messageHandlers.testHandler.postMessage(event.data);
+        }
+        addEventListener('message', postResult, false);
+    </script>
+    <iframe id='iframe' src='https://apple.com/apple'></iframe>
+    )TESTRESOURCE"_s;
+
+    static constexpr auto iframeBytes = R"TESTRESOURCE(
+    <div id='text'>iframe content</div>
+    <script>
+        addEventListener('message', () => {
+            let textElement = document.getElementById('text');
+            parent.postMessage(textElement.innerHTML, '*');
+        }, false);
+        parent.postMessage('loaded', '*');
+    </script>
+    )TESTRESOURCE"_s;
+
+    HTTPServer server({
+        { "/example"_s, { mainFrameBytes } },
+        { "/apple"_s, { iframeBytes } },
+    }, HTTPServer::Protocol::HttpsProxy);
+
+    bool receivedMessage1 = false;
+    bool receivedMessage2 = false;
+    bool receivedMessage3 = false;
+    auto webViewAndDelegates = makeWebViewAndDelegates(server);
+    [webViewAndDelegates.messageHandler addMessage:@"loaded" withHandler:[&]() {
+        receivedMessage1 = true;
+    }];
+    [webViewAndDelegates.messageHandler addMessage:@"MAINFRAME CONTENT" withHandler:[&]() {
+        receivedMessage2 = true;
+    }];
+    [webViewAndDelegates.messageHandler addMessage:@"IFRAME CONTENT" withHandler:[&]() {
+        receivedMessage3 = true;
+    }];
+    RetainPtr webView = webViewAndDelegates.webView;
+    RetainPtr textManipulationDelegate = adoptNS([[SiteIsolationTextManipulationDelegate alloc] init]);
+    [webView _setTextManipulationDelegate:textManipulationDelegate.get()];
+    RetainPtr manipulationConfiguration = adoptNS([[_WKTextManipulationConfiguration alloc] init]);
+    manipulationConfiguration.get().includeSubframes = YES;
+
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
+    Util::run(&receivedMessage1);
+
+    [webView _startTextManipulationsWithConfiguration:manipulationConfiguration.get() completion:^{ }];
+    while ([textManipulationDelegate items].count < 2)
+        Util::spinRunLoop();
+
+    RetainPtr items = [textManipulationDelegate items];
+    auto sortFunction = ^(_WKTextManipulationItem *item1, _WKTextManipulationItem *item2) {
+        auto value1 = [NSNumber numberWithBool:item1.isSubframe];
+        auto value2 = [NSNumber numberWithBool:item2.isSubframe];
+        return [value1 compare:value2];
+    };
+    RetainPtr sortedItems = [items.get() sortedArrayUsingComparator:sortFunction];
+    EXPECT_EQ(items.get().count, 2UL);
+    auto firstItem = [sortedItems objectAtIndex:0];
+    auto secondItem = [sortedItems objectAtIndex:1];
+    EXPECT_EQ(firstItem.isSubframe, NO);
+    EXPECT_EQ(firstItem.isCrossSiteSubframe, NO);
+    EXPECT_EQ(firstItem.tokens.count, 1UL);
+    EXPECT_STREQ("mainframe content", firstItem.tokens[0].content.UTF8String);
+    EXPECT_EQ(secondItem.isSubframe, YES);
+    EXPECT_EQ(secondItem.isCrossSiteSubframe, YES);
+    EXPECT_EQ(secondItem.tokens.count, 1UL);
+    EXPECT_STREQ("iframe content", secondItem.tokens[0].content.UTF8String);
+
+    __block bool done = false;
+    [webView _completeTextManipulationForItems:@[
+        (_WKTextManipulationItem *)createItem(firstItem.identifier, { createToken(firstItem.tokens[0].identifier, @"MAINFRAME CONTENT") }),
+        (_WKTextManipulationItem *)createItem(secondItem.identifier, { createToken(secondItem.tokens[0].identifier, @"IFRAME CONTENT") })
+    ] completion:^(NSArray<NSError *> *errors) {
+        EXPECT_EQ(errors, nil);
+        done = true;
+    }];
+    Util::run(&done);
+
+    [webView evaluateJavaScript:@"getTextContent()" completionHandler:nil];
+    Util::run(&receivedMessage2);
+
+    [webView evaluateJavaScript:@"getIframeTextContent()" completionHandler:nil];
+    Util::run(&receivedMessage3);
+}
+
+TEST(SiteIsolation, CompleteTextManipulationFailsInSomeFrame)
+{
+    static constexpr auto mainFrameBytes = R"TESTRESOURCE(
+    <div>mainframe content</div>
+    <script>
+        function removeIframe() {
+            let element = document.getElementById('iframe');
+            element.parentNode.removeChild(element);
+        }
+        var messageCount = 0;
path: Tools/TestWebKitAPI/Tests/WebKitCocoa/SiteIsolation.mm
user: login: rniwa
body: Maybe use `let` for consistency?
created_at: 2025-05-02T06:09:49Z
author_association: MEMBER
diff_hunk: @@ -4369,6 +4402,214 @@ HTTPServer server({
     Util::run(&done);
 }
 
+RetainPtr<_WKTextManipulationToken> createToken(NSString *identifier, NSString *content)
+{
+    RetainPtr<_WKTextManipulationToken> token = adoptNS([[_WKTextManipulationToken alloc] init]);
+    [token setIdentifier: identifier];
+    [token setContent: content];
+    return token;
+}
+
+static RetainPtr<_WKTextManipulationItem> createItem(NSString *itemIdentifier, const Vector<RetainPtr<_WKTextManipulationToken>>& tokens)
+{
+    RetainPtr<NSMutableArray> wkTokens = adoptNS([[NSMutableArray alloc] init]);
+    for (auto& token : tokens)
+        [wkTokens addObject:token.get()];
+
+    return adoptNS([[_WKTextManipulationItem alloc] initWithIdentifier:itemIdentifier tokens:wkTokens.get()]);
+}
+
+TEST(SiteIsolation, CompleteTextManipulation)
+{
+    static constexpr auto mainFrameBytes = R"TESTRESOURCE(
+    <div id='text'>mainframe content</div>
+    <script>
+        function getTextContent() {
+            window.webkit.messageHandlers.testHandler.postMessage(document.getElementById('text').innerHTML);
+        }
+        function getIframeTextContent() {
+            document.getElementById('iframe').contentWindow.postMessage('print', '*');
+        }
+        function postResult(event) {
+            window.webkit.messageHandlers.testHandler.postMessage(event.data);
+        }
+        addEventListener('message', postResult, false);
+    </script>
+    <iframe id='iframe' src='https://apple.com/apple'></iframe>
+    )TESTRESOURCE"_s;
+
+    static constexpr auto iframeBytes = R"TESTRESOURCE(
+    <div id='text'>iframe content</div>
+    <script>
+        addEventListener('message', () => {
+            let textElement = document.getElementById('text');
+            parent.postMessage(textElement.innerHTML, '*');
+        }, false);
+        parent.postMessage('loaded', '*');
+    </script>
+    )TESTRESOURCE"_s;
+
+    HTTPServer server({
+        { "/example"_s, { mainFrameBytes } },
+        { "/apple"_s, { iframeBytes } },
+    }, HTTPServer::Protocol::HttpsProxy);
+
+    bool receivedMessage1 = false;
+    bool receivedMessage2 = false;
+    bool receivedMessage3 = false;
+    auto webViewAndDelegates = makeWebViewAndDelegates(server);
+    [webViewAndDelegates.messageHandler addMessage:@"loaded" withHandler:[&]() {
+        receivedMessage1 = true;
+    }];
+    [webViewAndDelegates.messageHandler addMessage:@"MAINFRAME CONTENT" withHandler:[&]() {
+        receivedMessage2 = true;
+    }];
+    [webViewAndDelegates.messageHandler addMessage:@"IFRAME CONTENT" withHandler:[&]() {
+        receivedMessage3 = true;
+    }];
+    RetainPtr webView = webViewAndDelegates.webView;
+    RetainPtr textManipulationDelegate = adoptNS([[SiteIsolationTextManipulationDelegate alloc] init]);
+    [webView _setTextManipulationDelegate:textManipulationDelegate.get()];
+    RetainPtr manipulationConfiguration = adoptNS([[_WKTextManipulationConfiguration alloc] init]);
+    manipulationConfiguration.get().includeSubframes = YES;
+
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
+    Util::run(&receivedMessage1);
+
+    [webView _startTextManipulationsWithConfiguration:manipulationConfiguration.get() completion:^{ }];
+    while ([textManipulationDelegate items].count < 2)
+        Util::spinRunLoop();
+
+    RetainPtr items = [textManipulationDelegate items];
+    auto sortFunction = ^(_WKTextManipulationItem *item1, _WKTextManipulationItem *item2) {
+        auto value1 = [NSNumber numberWithBool:item1.isSubframe];
+        auto value2 = [NSNumber numberWithBool:item2.isSubframe];
+        return [value1 compare:value2];
+    };
+    RetainPtr sortedItems = [items.get() sortedArrayUsingComparator:sortFunction];
+    EXPECT_EQ(items.get().count, 2UL);
+    auto firstItem = [sortedItems objectAtIndex:0];
+    auto secondItem = [sortedItems objectAtIndex:1];
+    EXPECT_EQ(firstItem.isSubframe, NO);
+    EXPECT_EQ(firstItem.isCrossSiteSubframe, NO);
+    EXPECT_EQ(firstItem.tokens.count, 1UL);
+    EXPECT_STREQ("mainframe content", firstItem.tokens[0].content.UTF8String);
+    EXPECT_EQ(secondItem.isSubframe, YES);
+    EXPECT_EQ(secondItem.isCrossSiteSubframe, YES);
+    EXPECT_EQ(secondItem.tokens.count, 1UL);
+    EXPECT_STREQ("iframe content", secondItem.tokens[0].content.UTF8String);
+
+    __block bool done = false;
+    [webView _completeTextManipulationForItems:@[
+        (_WKTextManipulationItem *)createItem(firstItem.identifier, { createToken(firstItem.tokens[0].identifier, @"MAINFRAME CONTENT") }),
+        (_WKTextManipulationItem *)createItem(secondItem.identifier, { createToken(secondItem.tokens[0].identifier, @"IFRAME CONTENT") })
+    ] completion:^(NSArray<NSError *> *errors) {
+        EXPECT_EQ(errors, nil);
+        done = true;
+    }];
+    Util::run(&done);
+
+    [webView evaluateJavaScript:@"getTextContent()" completionHandler:nil];
+    Util::run(&receivedMessage2);
+
+    [webView evaluateJavaScript:@"getIframeTextContent()" completionHandler:nil];
+    Util::run(&receivedMessage3);
+}
+
+TEST(SiteIsolation, CompleteTextManipulationFailsInSomeFrame)
+{
+    static constexpr auto mainFrameBytes = R"TESTRESOURCE(
+    <div>mainframe content</div>
+    <script>
+        function removeIframe() {
+            let element = document.getElementById('iframe');
+            element.parentNode.removeChild(element);
+        }
+        var messageCount = 0;
+        addEventListener('message', () => {
+            if (++messageCount == 2)
+                window.webkit.messageHandlers.testHandler.postMessage('loaded');
+        }, false);
+    </script>
+    <iframe id='iframe' src='https://apple.com/apple'></iframe>
+    <iframe src='https://apple.com/apple'></iframe>
+    )TESTRESOURCE"_s;
+
+    static constexpr auto iframeBytes = R"TESTRESOURCE(
+    <div>iframe content</div>
+    <script>
+        parent.postMessage('loaded', '*');
+    </script>
+    )TESTRESOURCE"_s;
+
+    HTTPServer server({
+        { "/example"_s, { mainFrameBytes } },
+        { "/apple"_s, { iframeBytes } },
+    }, HTTPServer::Protocol::HttpsProxy);
+
+    bool receivedMessage = false;
+    auto webViewAndDelegates = makeWebViewAndDelegates(server);
+    [webViewAndDelegates.messageHandler addMessage:@"loaded" withHandler:[&]() {
+        receivedMessage = true;
+    }];
+    RetainPtr webView = webViewAndDelegates.webView;
+    RetainPtr textManipulationDelegate = adoptNS([[SiteIsolationTextManipulationDelegate alloc] init]);
+    [webView _setTextManipulationDelegate:textManipulationDelegate.get()];
+    RetainPtr manipulationConfiguration = adoptNS([[_WKTextManipulationConfiguration alloc] init]);
+    manipulationConfiguration.get().includeSubframes = YES;
+
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
+    Util::run(&receivedMessage);
+
+    [webView _startTextManipulationsWithConfiguration:manipulationConfiguration.get() completion:^{ }];
+    while ([textManipulationDelegate items].count < 3)
+        Util::spinRunLoop();
+
+    RetainPtr items = [textManipulationDelegate items];
+    auto sortFunction = ^(_WKTextManipulationItem *item1, _WKTextManipulationItem *item2) {
+        auto value1 = [NSNumber numberWithBool:item1.isSubframe];
+        auto value2 = [NSNumber numberWithBool:item2.isSubframe];
+        return [value1 compare:value2];
+    };
+
+    RetainPtr sortedItems = [items.get() sortedArrayUsingComparator:sortFunction];
+    EXPECT_EQ(items.get().count, 3UL);
+    auto item1 = [sortedItems objectAtIndex:0];
+    auto item2 = [sortedItems objectAtIndex:1];
+    auto item3 = [sortedItems objectAtIndex:1];
path: Tools/TestWebKitAPI/Tests/WebKitCocoa/SiteIsolation.mm
user: login: rniwa
body: Isn't this supposed to be at objectAtIndex:2? We should probably use a distinct content for frame 2 and frame 3 to avoid this kind of error.
created_at: 2025-05-02T06:11:10Z
author_association: MEMBER
diff_hunk: @@ -4369,6 +4402,214 @@ HTTPServer server({
     Util::run(&done);
 }
 
+RetainPtr<_WKTextManipulationToken> createToken(NSString *identifier, NSString *content)
+{
+    RetainPtr<_WKTextManipulationToken> token = adoptNS([[_WKTextManipulationToken alloc] init]);
+    [token setIdentifier: identifier];
+    [token setContent: content];
+    return token;
+}
+
+static RetainPtr<_WKTextManipulationItem> createItem(NSString *itemIdentifier, const Vector<RetainPtr<_WKTextManipulationToken>>& tokens)
+{
+    RetainPtr<NSMutableArray> wkTokens = adoptNS([[NSMutableArray alloc] init]);
+    for (auto& token : tokens)
+        [wkTokens addObject:token.get()];
+
+    return adoptNS([[_WKTextManipulationItem alloc] initWithIdentifier:itemIdentifier tokens:wkTokens.get()]);
+}
+
+TEST(SiteIsolation, CompleteTextManipulation)
+{
+    static constexpr auto mainFrameBytes = R"TESTRESOURCE(
+    <div id='text'>mainframe content</div>
+    <script>
+        function getTextContent() {
+            window.webkit.messageHandlers.testHandler.postMessage(document.getElementById('text').innerHTML);
+        }
+        function getIframeTextContent() {
+            document.getElementById('iframe').contentWindow.postMessage('print', '*');
+        }
+        function postResult(event) {
+            window.webkit.messageHandlers.testHandler.postMessage(event.data);
+        }
+        addEventListener('message', postResult, false);
+    </script>
+    <iframe id='iframe' src='https://apple.com/apple'></iframe>
+    )TESTRESOURCE"_s;
+
+    static constexpr auto iframeBytes = R"TESTRESOURCE(
+    <div id='text'>iframe content</div>
+    <script>
+        addEventListener('message', () => {
+            let textElement = document.getElementById('text');
+            parent.postMessage(textElement.innerHTML, '*');
+        }, false);
+        parent.postMessage('loaded', '*');
+    </script>
+    )TESTRESOURCE"_s;
+
+    HTTPServer server({
+        { "/example"_s, { mainFrameBytes } },
+        { "/apple"_s, { iframeBytes } },
+    }, HTTPServer::Protocol::HttpsProxy);
+
+    bool receivedMessage1 = false;
+    bool receivedMessage2 = false;
+    bool receivedMessage3 = false;
+    auto webViewAndDelegates = makeWebViewAndDelegates(server);
+    [webViewAndDelegates.messageHandler addMessage:@"loaded" withHandler:[&]() {
+        receivedMessage1 = true;
+    }];
+    [webViewAndDelegates.messageHandler addMessage:@"MAINFRAME CONTENT" withHandler:[&]() {
+        receivedMessage2 = true;
+    }];
+    [webViewAndDelegates.messageHandler addMessage:@"IFRAME CONTENT" withHandler:[&]() {
+        receivedMessage3 = true;
+    }];
+    RetainPtr webView = webViewAndDelegates.webView;
+    RetainPtr textManipulationDelegate = adoptNS([[SiteIsolationTextManipulationDelegate alloc] init]);
+    [webView _setTextManipulationDelegate:textManipulationDelegate.get()];
+    RetainPtr manipulationConfiguration = adoptNS([[_WKTextManipulationConfiguration alloc] init]);
+    manipulationConfiguration.get().includeSubframes = YES;
+
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
+    Util::run(&receivedMessage1);
+
+    [webView _startTextManipulationsWithConfiguration:manipulationConfiguration.get() completion:^{ }];
+    while ([textManipulationDelegate items].count < 2)
+        Util::spinRunLoop();
+
+    RetainPtr items = [textManipulationDelegate items];
+    auto sortFunction = ^(_WKTextManipulationItem *item1, _WKTextManipulationItem *item2) {
+        auto value1 = [NSNumber numberWithBool:item1.isSubframe];
+        auto value2 = [NSNumber numberWithBool:item2.isSubframe];
+        return [value1 compare:value2];
+    };
+    RetainPtr sortedItems = [items.get() sortedArrayUsingComparator:sortFunction];
+    EXPECT_EQ(items.get().count, 2UL);
+    auto firstItem = [sortedItems objectAtIndex:0];
+    auto secondItem = [sortedItems objectAtIndex:1];
+    EXPECT_EQ(firstItem.isSubframe, NO);
+    EXPECT_EQ(firstItem.isCrossSiteSubframe, NO);
+    EXPECT_EQ(firstItem.tokens.count, 1UL);
+    EXPECT_STREQ("mainframe content", firstItem.tokens[0].content.UTF8String);
+    EXPECT_EQ(secondItem.isSubframe, YES);
+    EXPECT_EQ(secondItem.isCrossSiteSubframe, YES);
+    EXPECT_EQ(secondItem.tokens.count, 1UL);
+    EXPECT_STREQ("iframe content", secondItem.tokens[0].content.UTF8String);
+
+    __block bool done = false;
+    [webView _completeTextManipulationForItems:@[
+        (_WKTextManipulationItem *)createItem(firstItem.identifier, { createToken(firstItem.tokens[0].identifier, @"MAINFRAME CONTENT") }),
+        (_WKTextManipulationItem *)createItem(secondItem.identifier, { createToken(secondItem.tokens[0].identifier, @"IFRAME CONTENT") })
+    ] completion:^(NSArray<NSError *> *errors) {
+        EXPECT_EQ(errors, nil);
+        done = true;
+    }];
+    Util::run(&done);
+
+    [webView evaluateJavaScript:@"getTextContent()" completionHandler:nil];
+    Util::run(&receivedMessage2);
+
+    [webView evaluateJavaScript:@"getIframeTextContent()" completionHandler:nil];
+    Util::run(&receivedMessage3);
+}
+
+TEST(SiteIsolation, CompleteTextManipulationFailsInSomeFrame)
+{
+    static constexpr auto mainFrameBytes = R"TESTRESOURCE(
+    <div>mainframe content</div>
+    <script>
+        function removeIframe() {
+            let element = document.getElementById('iframe');
+            element.parentNode.removeChild(element);
+        }
+        var messageCount = 0;
+        addEventListener('message', () => {
+            if (++messageCount == 2)
+                window.webkit.messageHandlers.testHandler.postMessage('loaded');
+        }, false);
+    </script>
+    <iframe id='iframe' src='https://apple.com/apple'></iframe>
+    <iframe src='https://apple.com/apple'></iframe>
path: Tools/TestWebKitAPI/Tests/WebKitCocoa/SiteIsolation.mm
user: login: rniwa
body: Doesn't this make two iframes share the process since they're both accessing https://apple.com?
We ideally have another test case or modify this one to have two distinct cross-site addresses.
created_at: 2025-05-02T06:12:32Z
author_association: MEMBER
diff_hunk: @@ -4369,6 +4402,214 @@ HTTPServer server({
     Util::run(&done);
 }
 
+RetainPtr<_WKTextManipulationToken> createToken(NSString *identifier, NSString *content)
+{
+    RetainPtr<_WKTextManipulationToken> token = adoptNS([[_WKTextManipulationToken alloc] init]);
+    [token setIdentifier: identifier];
+    [token setContent: content];
+    return token;
+}
+
+static RetainPtr<_WKTextManipulationItem> createItem(NSString *itemIdentifier, const Vector<RetainPtr<_WKTextManipulationToken>>& tokens)
+{
+    RetainPtr<NSMutableArray> wkTokens = adoptNS([[NSMutableArray alloc] init]);
+    for (auto& token : tokens)
+        [wkTokens addObject:token.get()];
+
+    return adoptNS([[_WKTextManipulationItem alloc] initWithIdentifier:itemIdentifier tokens:wkTokens.get()]);
+}
+
+TEST(SiteIsolation, CompleteTextManipulation)
+{
+    static constexpr auto mainFrameBytes = R"TESTRESOURCE(
+    <div id='text'>mainframe content</div>
+    <script>
+        function getTextContent() {
+            window.webkit.messageHandlers.testHandler.postMessage(document.getElementById('text').innerHTML);
+        }
+        function getIframeTextContent() {
+            document.getElementById('iframe').contentWindow.postMessage('print', '*');
+        }
+        function postResult(event) {
+            window.webkit.messageHandlers.testHandler.postMessage(event.data);
+        }
+        addEventListener('message', postResult, false);
+    </script>
+    <iframe id='iframe' src='https://apple.com/apple'></iframe>
+    )TESTRESOURCE"_s;
+
+    static constexpr auto iframeBytes = R"TESTRESOURCE(
+    <div id='text'>iframe content</div>
+    <script>
+        addEventListener('message', () => {
+            let textElement = document.getElementById('text');
+            parent.postMessage(textElement.innerHTML, '*');
+        }, false);
+        parent.postMessage('loaded', '*');
+    </script>
+    )TESTRESOURCE"_s;
+
+    HTTPServer server({
+        { "/example"_s, { mainFrameBytes } },
+        { "/apple"_s, { iframeBytes } },
+    }, HTTPServer::Protocol::HttpsProxy);
+
+    bool receivedMessage1 = false;
+    bool receivedMessage2 = false;
+    bool receivedMessage3 = false;
+    auto webViewAndDelegates = makeWebViewAndDelegates(server);
+    [webViewAndDelegates.messageHandler addMessage:@"loaded" withHandler:[&]() {
+        receivedMessage1 = true;
+    }];
+    [webViewAndDelegates.messageHandler addMessage:@"MAINFRAME CONTENT" withHandler:[&]() {
+        receivedMessage2 = true;
+    }];
+    [webViewAndDelegates.messageHandler addMessage:@"IFRAME CONTENT" withHandler:[&]() {
+        receivedMessage3 = true;
+    }];
+    RetainPtr webView = webViewAndDelegates.webView;
+    RetainPtr textManipulationDelegate = adoptNS([[SiteIsolationTextManipulationDelegate alloc] init]);
+    [webView _setTextManipulationDelegate:textManipulationDelegate.get()];
+    RetainPtr manipulationConfiguration = adoptNS([[_WKTextManipulationConfiguration alloc] init]);
+    manipulationConfiguration.get().includeSubframes = YES;
+
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
+    Util::run(&receivedMessage1);
+
+    [webView _startTextManipulationsWithConfiguration:manipulationConfiguration.get() completion:^{ }];
+    while ([textManipulationDelegate items].count < 2)
+        Util::spinRunLoop();
+
+    RetainPtr items = [textManipulationDelegate items];
+    auto sortFunction = ^(_WKTextManipulationItem *item1, _WKTextManipulationItem *item2) {
+        auto value1 = [NSNumber numberWithBool:item1.isSubframe];
+        auto value2 = [NSNumber numberWithBool:item2.isSubframe];
+        return [value1 compare:value2];
+    };
+    RetainPtr sortedItems = [items.get() sortedArrayUsingComparator:sortFunction];
path: Tools/TestWebKitAPI/Tests/WebKitCocoa/SiteIsolation.mm
user: login: szewai
body: The items are received from different web processes, so there is no guarantee they will arrive UI process in order.
(i.e. UI process sends "start" to WP1, WP2, WP3 in order and the replies may not come in WP1, WP2, WP3 order); I've seen the iframe process replies faster when running test locally.
created_at: 2025-05-02T17:08:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -717,8 +718,12 @@ auto TextManipulationController::completeManipulation(const Vector<WebCore::Text
         std::exchange(itemData, itemDataIterator->value);
         m_items.remove(itemDataIterator);
 
-        if (auto failureOrNullopt = replace(itemData, itemToComplete.tokens, containersWithoutVisualOverflowBeforeReplacement))
+        if (auto failureOrNullopt = replace(itemData, itemToComplete.tokens, containersWithoutVisualOverflowBeforeReplacement)) {
             failures.append(ManipulationFailure { *frameID, itemID, i, *failureOrNullopt });
+            continue;
+        }
+
+        succeededIndexes.append(i);
path: Source/WebCore/editing/TextManipulationController.cpp
user: login: szewai
body: `BitVector` is not supported in IPC message yet. I can use `BitVector` here but will need to convert it to `Vector` for sending reply to UI process.
created_at: 2025-05-02T17:29:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4369,6 +4402,214 @@ HTTPServer server({
     Util::run(&done);
 }
 
+RetainPtr<_WKTextManipulationToken> createToken(NSString *identifier, NSString *content)
+{
+    RetainPtr<_WKTextManipulationToken> token = adoptNS([[_WKTextManipulationToken alloc] init]);
+    [token setIdentifier: identifier];
+    [token setContent: content];
+    return token;
+}
+
+static RetainPtr<_WKTextManipulationItem> createItem(NSString *itemIdentifier, const Vector<RetainPtr<_WKTextManipulationToken>>& tokens)
+{
+    RetainPtr<NSMutableArray> wkTokens = adoptNS([[NSMutableArray alloc] init]);
+    for (auto& token : tokens)
+        [wkTokens addObject:token.get()];
+
+    return adoptNS([[_WKTextManipulationItem alloc] initWithIdentifier:itemIdentifier tokens:wkTokens.get()]);
+}
+
+TEST(SiteIsolation, CompleteTextManipulation)
+{
+    static constexpr auto mainFrameBytes = R"TESTRESOURCE(
+    <div id='text'>mainframe content</div>
+    <script>
+        function getTextContent() {
+            window.webkit.messageHandlers.testHandler.postMessage(document.getElementById('text').innerHTML);
+        }
+        function getIframeTextContent() {
+            document.getElementById('iframe').contentWindow.postMessage('print', '*');
+        }
+        function postResult(event) {
+            window.webkit.messageHandlers.testHandler.postMessage(event.data);
+        }
+        addEventListener('message', postResult, false);
+    </script>
+    <iframe id='iframe' src='https://apple.com/apple'></iframe>
+    )TESTRESOURCE"_s;
+
+    static constexpr auto iframeBytes = R"TESTRESOURCE(
+    <div id='text'>iframe content</div>
+    <script>
+        addEventListener('message', () => {
+            let textElement = document.getElementById('text');
+            parent.postMessage(textElement.innerHTML, '*');
+        }, false);
+        parent.postMessage('loaded', '*');
+    </script>
+    )TESTRESOURCE"_s;
+
+    HTTPServer server({
+        { "/example"_s, { mainFrameBytes } },
+        { "/apple"_s, { iframeBytes } },
+    }, HTTPServer::Protocol::HttpsProxy);
+
+    bool receivedMessage1 = false;
+    bool receivedMessage2 = false;
+    bool receivedMessage3 = false;
+    auto webViewAndDelegates = makeWebViewAndDelegates(server);
+    [webViewAndDelegates.messageHandler addMessage:@"loaded" withHandler:[&]() {
+        receivedMessage1 = true;
+    }];
+    [webViewAndDelegates.messageHandler addMessage:@"MAINFRAME CONTENT" withHandler:[&]() {
+        receivedMessage2 = true;
+    }];
+    [webViewAndDelegates.messageHandler addMessage:@"IFRAME CONTENT" withHandler:[&]() {
+        receivedMessage3 = true;
+    }];
+    RetainPtr webView = webViewAndDelegates.webView;
+    RetainPtr textManipulationDelegate = adoptNS([[SiteIsolationTextManipulationDelegate alloc] init]);
+    [webView _setTextManipulationDelegate:textManipulationDelegate.get()];
+    RetainPtr manipulationConfiguration = adoptNS([[_WKTextManipulationConfiguration alloc] init]);
+    manipulationConfiguration.get().includeSubframes = YES;
+
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
+    Util::run(&receivedMessage1);
+
+    [webView _startTextManipulationsWithConfiguration:manipulationConfiguration.get() completion:^{ }];
+    while ([textManipulationDelegate items].count < 2)
+        Util::spinRunLoop();
+
+    RetainPtr items = [textManipulationDelegate items];
+    auto sortFunction = ^(_WKTextManipulationItem *item1, _WKTextManipulationItem *item2) {
+        auto value1 = [NSNumber numberWithBool:item1.isSubframe];
+        auto value2 = [NSNumber numberWithBool:item2.isSubframe];
+        return [value1 compare:value2];
+    };
+    RetainPtr sortedItems = [items.get() sortedArrayUsingComparator:sortFunction];
path: Tools/TestWebKitAPI/Tests/WebKitCocoa/SiteIsolation.mm
user: login: rniwa
body: Hm... maybe we should check with the translation team to make sure that's okay. Right now, iframe content comes in the order they appear. Changing the order of it could affect translation results.
created_at: 2025-05-02T17:35:44Z
author_association: MEMBER
diff_hunk: @@ -717,8 +718,12 @@ auto TextManipulationController::completeManipulation(const Vector<WebCore::Text
         std::exchange(itemData, itemDataIterator->value);
         m_items.remove(itemDataIterator);
 
-        if (auto failureOrNullopt = replace(itemData, itemToComplete.tokens, containersWithoutVisualOverflowBeforeReplacement))
+        if (auto failureOrNullopt = replace(itemData, itemToComplete.tokens, containersWithoutVisualOverflowBeforeReplacement)) {
             failures.append(ManipulationFailure { *frameID, itemID, i, *failureOrNullopt });
+            continue;
+        }
+
+        succeededIndexes.append(i);
path: Source/WebCore/editing/TextManipulationController.cpp
user: login: rniwa
body: Oh I see. That's unfortunate.
created_at: 2025-05-02T17:37:40Z
author_association: MEMBER

PR comments:
user: login: webkit-early-warning-system
created_at: 2025-04-30T19:03:03Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/da13e7e3524ebd25e7ba5a73e4b57e2eb3a964af)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [❌ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/101548 "Failed to checkout and rebase branch from PR 44757") | [❌ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/21216 "Failed to checkout and rebase branch from PR 44757") | [❌ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/11525 "Failed to checkout and rebase branch from PR 44757") | [  ~~🛠 wpe~~](https://ews-build.webkit.org/#/builders/5/builds/106705 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🛠 win~~](https://ews-build.webkit.org/#/builders/59/builds/52181 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") 
| [❌ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/103588 "Failed to checkout and rebase branch from PR 44757") | [❌ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/21524 "Failed to checkout and rebase branch from PR 44757") | [❌ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/29710 "Failed to checkout and rebase branch from PR 44757") | [  ~~🧪 wpe-wk2~~](https://ews-build.webkit.org/#/builders/5/builds/106705 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🧪 win-tests~~](https://ews-build.webkit.org/#/builders/59/builds/52181 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") 
| [❌ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/104555 "Failed to checkout and rebase branch from PR 44757") | [❌ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/130/builds/21524 "Failed to checkout and rebase branch from PR 44757") | [❌ 🧪 api-mac](https://ews-build.webkit.org/#/builders/138/builds/11525 "Failed to checkout and rebase branch from PR 44757") | [  ~~🧪 api-wpe~~](https://ews-build.webkit.org/#/builders/5/builds/106705 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | 
| | [❌ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/130/builds/21524 "Failed to checkout and rebase branch from PR 44757") | [❌ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/138/builds/11525 "Failed to checkout and rebase branch from PR 44757") | [  ~~🛠 wpe-cairo~~](https://ews-build.webkit.org/#/builders/65/builds/51528 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | 
| | [❌ 🧪 api-ios](https://ews-build.webkit.org/#/builders/130/builds/21524 "Failed to checkout and rebase branch from PR 44757") | [❌ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/138/builds/11525 "Failed to checkout and rebase branch from PR 44757") | [❌ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/109057 "Failed to checkout and rebase branch from PR 44757") | 
| | [❌ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/28681 "Failed to checkout and rebase branch from PR 44757") | [❌ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/123/builds/29710 "Failed to checkout and rebase branch from PR 44757") | [❌ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/2/builds/109057 "Failed to checkout and rebase branch from PR 44757") | 
| | [❌ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/29042 "Failed to checkout and rebase branch from PR 44757") | [❌ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/138/builds/11525 "Failed to checkout and rebase branch from PR 44757") | [❌ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/2/builds/109057 "Failed to checkout and rebase branch from PR 44757") | 
| | [⏳ 🧪 vision-wk2 ](https://ews-build.webkit.org/#/builders/visionOS-2-Simulator-WK2-Tests-EWS "Waiting in queue, processing has not started yet") | [❌ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/138/builds/11525 "Failed to checkout and rebase branch from PR 44757") | [  ~~🛠 playstation~~](https://ews-build.webkit.org/#/builders/134/builds/22813 "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/28609 "Failed to checkout and rebase branch from PR 44757") | [❌ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/33898 "Failed to checkout and rebase branch from PR 44757") | | 
| | [❌ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/28420 "Failed to checkout and rebase branch from PR 44757") | | | 
| | [  ~~🛠 watch~~](https://ews-build.webkit.org/#/builders/129/builds/31743 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | | | 
| | [❌ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/29979 "Failed to checkout and rebase branch from PR 44757") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-04-30T19:12:57Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/ec7949e8f4a60a8fec7e2d1001dafd0b4812d3f5)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [❌ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/101550 "5 style errors") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/21218 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/11527 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/106707 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/52183 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/103590 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/21526 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/29712 "Built successfully") | [✅ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/77337 "Passed tests") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/34364 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/104557 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/16618 "Passed tests") | [❌ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/91703 "Found 3 new API test failures: TestWebKitAPI.TextManipulation.CompleteTextManipulationShouldReplaceTextContentWithMultipleTokens, TestWebKitAPI.TextManipulation.CompleteTextManipulationFailWhenContentIsChangedInIframe, TestWebKitAPI.TextManipulation.CompleteTextManipulationFailWhenItemIdentifierIsDuplicated (failure)") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/57675 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/16444 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/9720 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/51530 "Built successfully") | 
| | [  ~~🧪 api-ios~~](https://ews-build.webkit.org/#/builders/13/builds/86312 "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/9797 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/109059 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/28683 "Built successfully") | [✅ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/21106 "Passed tests") | [✅ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/86312 "Passed tests") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/29044 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/87904 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/85874 "Passed tests") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/30615 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/8322 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/22815 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/28611 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/33900 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/28422 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/31745 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/29981 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-04-30T22:34:06Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/d42e1c3402c6662e5ce6ea7800ce2dcf5e2d5428)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [❌ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/101572 "5 style errors") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/21240 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/11551 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/106730 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/52205 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/103612 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/21548 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/29735 "Built successfully") | [✅ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/77356 "Passed tests") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/34381 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/104579 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/16639 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/91728 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/57693 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/16464 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/9745 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/51552 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/86332 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/9822 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/109081 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/28705 "Built successfully") | [❌ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/21126 "Found 1 new test failure: imported/w3c/web-platform-tests/css/css-view-transitions/transition-in-hidden-page.html (failure)") | [✅ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/86330 "Passed tests") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/29066 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/87929 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/85892 "Passed tests") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/30635 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/8346 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/22839 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/28633 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/33922 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/28444 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/31767 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/30003 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-05-02T00:33:49Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/20d8bf8b8b95609ba2597b93b82ad6aa920e9105)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [❌ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/101726 "18 style errors") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/21394 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/11710 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/106884 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/52360 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/103766 "Passed tests") | [![loading](https://user-images.githubusercontent.com/3098702/171232313-daa606f1-8fd6-4b0f-a20b-2cb93c43d19b.png) 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/21702 "Build is in progress. Recent messages:OS: Sequoia (15.2), Xcode: 16.2; Skipping applying patch since patch_id isn't provided; Checked out pull request; Compiled WebKit (warnings)") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/29895 "Built successfully") | [✅ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/77457 "Passed tests") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/34492 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/104733 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/16771 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/91866 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/57795 "Passed tests") | 
| [✅ 🧪 webkitpy](https://ews-build.webkit.org/#/builders/6/builds/101198 "Passed tests") | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/16601 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/9884 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/51716 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/86459 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/9961 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/109238 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/28859 "Built successfully") | [✅ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/21257 "Passed tests") | [  ~~🧪 gtk-wk2~~](https://ews-build.webkit.org/#/builders/1/builds/86440 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/29220 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/88067 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/86005 "Passed tests") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/30757 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/8482 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/23022 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/28787 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/34077 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/28598 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/31921 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/30157 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-05-02T04:49:27Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/d919a2322ba0bb92e245fe50101b2f4de1e8a8b8)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [❌ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/101758 "18 style errors") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/21426 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/11742 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/106916 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/52392 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/103798 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/21734 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/29929 "Built successfully") | [✅ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/77474 "Passed tests") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/34500 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/104765 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/16791 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/91892 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/57812 "Passed tests") | 
| [✅ 🧪 webkitpy](https://ews-build.webkit.org/#/builders/6/builds/101230 "Passed tests") | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/16616 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/9910 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/51743 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/86473 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/9987 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/109272 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/28891 "Built successfully") | [✅ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/21276 "Passed tests") | [✅ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/86457 "Passed tests") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/29252 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/88093 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/86027 "Passed tests") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/30783 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/8503 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/23054 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/28819 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/34109 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/28630 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/31953 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/30189 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-05-02T17:25:11Z
body: EWS run on current version of this PR (hash https://github.com/WebKit/WebKit/commit/101152da7de89e220592f0be0441abf1e26afae1)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [❌ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/101824 "18 style errors") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/21492 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/11808 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/106982 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/52458 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/103864 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/21800 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/29999 "Built successfully") | [  ~~🧪 wpe-wk2~~](https://ews-build.webkit.org/#/builders/34/builds/77557 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/34554 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/104831 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/16844 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/91946 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/57868 "Passed tests") | 
| [✅ 🧪 webkitpy](https://ews-build.webkit.org/#/builders/6/builds/101298 "Passed tests") | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/16671 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/9965 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/51809 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/86525 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/10041 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/109339 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/28957 "Built successfully") | [✅ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/21331 "Passed tests") | [❌ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/86509 "Found 1 new test failure: imported/w3c/web-platform-tests/service-workers/service-worker/redirected-response.https.html (failure)") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/29318 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/88147 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/86081 "Passed tests") | 
| [✅ 🛠 🧪 merge](https://ews-build.webkit.org/#/builders/19/builds/21910 "Built successfully and passed tests") | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/30835 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/8556 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/23128 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/28885 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/34177 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/28696 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/32019 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/30255 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-commit-queue
created_at: 2025-05-02T22:14:59Z
body: Committed 294454@main (ca8a33f18397): <https://commits.webkit.org/294454@main>

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