Title: [277097] trunk
Revision
277097
Author
[email protected]
Date
2021-05-06 09:54:12 -0700 (Thu, 06 May 2021)

Log Message

[iOS] UI process hangs when showing a modal _javascript_ dialog while focusing an input field
https://bugs.webkit.org/show_bug.cgi?id=225409
rdar://76792407

Reviewed by Darin Adler, Chris Dumez and Tim Horton.

Source/WebKit:

Consider the scenario where an input field has a focus event handler that shows a modal _javascript_ dialog (e.g.
confirm, alert, prompt). We get the following sequence of events in this scenario:

(WEB)   The text field is focused, and sends WebPageProxy::ElementDidFocus. A modal dialog is then triggered by
        script inside the focus event handler, sending a message like WebPageProxy::RunJavaScriptAlert.

(UI)    The UI process receives WebPageProxy::ElementDidFocus, and calls `-reloadInputViews` while showing the
        keyboard. UIKit may then call back into `-requestAutocorrectionContextWithCompletionHandler:`, which
        triggers sync IPC back to the web process. While waiting to receive a response message, we receive and
        dispatch WebPageProxy::RunJavaScriptAlert.

At this point, the UI process is still waiting for a HandleAutocorrectionContext response IPC message, but the
web process is blocked on a response to the modal _javascript_ dialog; since both processes are blocked on each
other, we're now in deadlock.

To mitigate this, we can preemptively send autocorrection context data right before we're about to synchronously
block in the web process on a modal dialog. In the UI process, we use this autocorrection context object to
immediately stop syncwaiting for a HandleAutocorrectionContext response. See below for more details.

Test: fast/events/ios/show-modal-alert-during-focus.html

* Platform/IPC/Connection.cpp:
(IPC::Connection::SyncMessageState::dispatchMessages):
(IPC::Connection::waitForMessage):

Make a slight adjustment here so that `waitForMessage` bails if one of the messages we're dispatching underneath
`SyncMessageState::dispatchMessages` is the message we happen to be waiting for anyways. In this case, it's
necessary in order for the preemptive `WebPageProxy::HandleAutocorrectionContext` message sent by the web
process to unblock the UI process, which is waiting for a `WebPageProxy::HandleAutocorrectionContext` response.

* UIProcess/PageClient.h:
(WebKit::PageClient::runModalJavaScriptDialog):
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::runModalJavaScriptDialog):
(WebKit::WebPageProxy::runJavaScriptAlert):
(WebKit::WebPageProxy::runJavaScriptConfirm):
(WebKit::WebPageProxy::runJavaScriptPrompt):

Wrap calls to `runJavaScript(Alert|Confirm|Prompt)` underneath a new page client method that takes a block,
`runModalJavaScriptDialog`. See `-[WKContentView runModalJavaScriptDialog:]` below for more details.

* UIProcess/WebPageProxy.h:
* UIProcess/ios/PageClientImplIOS.h:
* UIProcess/ios/PageClientImplIOS.mm:
(WebKit::PageClientImpl::runModalJavaScriptDialog):
* UIProcess/ios/WKContentViewInteraction.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView requestAutocorrectionContextWithCompletionHandler:]):

Avoid blocking on the web process if it's waiting on a modal _javascript_ dialog anyways; just use the last
autocorrection context we received from the web process (which we know must've been sent right before showing
the modal dialog anyways).

(-[WKContentView runModalJavaScriptDialog:]):

In the case where we're in the process of presenting the keyboard (or UCB) on iOS after focusing an input field,
avoid immediately allowing the client to show any modal UI until we're done managing keyboard logic in WebKit.
Without this adjustment, we'll end up trying to present the modal _javascript_ dialog in the middle of UIKit's
call to `-requestAutocorrectionContextWithCompletionHandler:`, which then causes UIKit to throw an exception
underneath keyboard code.

(-[WKContentView _elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:]):
* WebProcess/WebCoreSupport/WebChromeClient.cpp:
(WebKit::WebChromeClient::runJavaScriptAlert):
(WebKit::WebChromeClient::runJavaScriptConfirm):
(WebKit::WebChromeClient::runJavaScriptPrompt):
* WebProcess/WebPage/WebPage.h:
(WebKit::WebPage::prepareToRunModalJavaScriptDialog):

Add logic to preemptively compute and send `WebAutocorrectionContext` data to the UI process on iOS, in the case
where we're about to show a modal _javascript_ dialog and there is an editable focused element. While the modal
dialog is shown, we'll use this information to immediately respond to UIKit's autocorrection context requests,
instead of trying to block on responses from the web process.

* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::requestAutocorrectionContext):
(WebKit::WebPage::prepareToRunModalJavaScriptDialog):

LayoutTests:

Add a layout test that exercises the deadlock. In the case where we fail, this test will time out.

* fast/events/ios/show-modal-alert-during-focus-expected.txt: Added.
* fast/events/ios/show-modal-alert-during-focus.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (277096 => 277097)


--- trunk/LayoutTests/ChangeLog	2021-05-06 16:35:37 UTC (rev 277096)
+++ trunk/LayoutTests/ChangeLog	2021-05-06 16:54:12 UTC (rev 277097)
@@ -1,3 +1,16 @@
+2021-05-06  Wenson Hsieh  <[email protected]>
+
+        [iOS] UI process hangs when showing a modal _javascript_ dialog while focusing an input field
+        https://bugs.webkit.org/show_bug.cgi?id=225409
+        rdar://76792407
+
+        Reviewed by Darin Adler, Chris Dumez and Tim Horton.
+
+        Add a layout test that exercises the deadlock. In the case where we fail, this test will time out.
+
+        * fast/events/ios/show-modal-alert-during-focus-expected.txt: Added.
+        * fast/events/ios/show-modal-alert-during-focus.html: Added.
+
 2021-05-06  Diego Pino Garcia  <[email protected]>
 
         [GTK][WPE] Unreviewed test gardening. Emit platform baselines after r277071.

Added: trunk/LayoutTests/fast/events/ios/show-modal-alert-during-focus-expected.txt (0 => 277097)


--- trunk/LayoutTests/fast/events/ios/show-modal-alert-during-focus-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/events/ios/show-modal-alert-during-focus-expected.txt	2021-05-06 16:54:12 UTC (rev 277097)
@@ -0,0 +1,11 @@
+ALERT: This is a modal alert.
+This test verifies that presenting a modal alert while focusing an editable input doesn't cause the application process to hang. To verify manually, tap the text field above, and check that the _javascript_ alert is presented with no significant delay (i.e. a 1-second hang).
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS doneShowingAlert became true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/fast/events/ios/show-modal-alert-during-focus.html (0 => 277097)


--- trunk/LayoutTests/fast/events/ios/show-modal-alert-during-focus.html	                        (rev 0)
+++ trunk/LayoutTests/fast/events/ios/show-modal-alert-during-focus.html	2021-05-06 16:54:12 UTC (rev 277097)
@@ -0,0 +1,45 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true ignoreSynchronousMessagingTimeouts=true ] -->
+<html>
+<head>
+<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
+<script src=""
+<script src=""
+<style>
+body, html {
+    width: 100%;
+    height: 100%;
+    margin: 0;
+}
+
+input {
+    font-size: 20px;
+}
+</style>
+</head>
+<body>
+<input placeholder="Tap to show an alert" type="text"></input>
+<script>
+if (window.testRunner)
+    testRunner.setShouldDismissJavaScriptAlertsAsynchronously(true);
+
+jsTestIsAsync = true;
+doneShowingAlert = false;
+
+addEventListener("load", async () => {
+    description("This test verifies that presenting a modal alert while focusing an editable input doesn't cause the application process to hang. To verify manually, tap the text field above, and check that the _javascript_ alert is presented with no significant delay (i.e. a 1-second hang).");
+
+    textField = document.querySelector("input");
+    textField.addEventListener("focus", () => {
+        alert("This is a modal alert.");
+        doneShowingAlert = true;
+    });
+
+    if (!window.testRunner)
+        return;
+
+    await UIHelper.activateElement(textField);
+    shouldBecomeEqual("doneShowingAlert", "true", finishJSTest);
+});
+</script>
+</body>
+</html>

Modified: trunk/Source/WebKit/ChangeLog (277096 => 277097)


--- trunk/Source/WebKit/ChangeLog	2021-05-06 16:35:37 UTC (rev 277096)
+++ trunk/Source/WebKit/ChangeLog	2021-05-06 16:54:12 UTC (rev 277097)
@@ -1,3 +1,89 @@
+2021-05-06  Wenson Hsieh  <[email protected]>
+
+        [iOS] UI process hangs when showing a modal _javascript_ dialog while focusing an input field
+        https://bugs.webkit.org/show_bug.cgi?id=225409
+        rdar://76792407
+
+        Reviewed by Darin Adler, Chris Dumez and Tim Horton.
+
+        Consider the scenario where an input field has a focus event handler that shows a modal _javascript_ dialog (e.g.
+        confirm, alert, prompt). We get the following sequence of events in this scenario:
+
+        (WEB)   The text field is focused, and sends WebPageProxy::ElementDidFocus. A modal dialog is then triggered by
+                script inside the focus event handler, sending a message like WebPageProxy::RunJavaScriptAlert.
+
+        (UI)    The UI process receives WebPageProxy::ElementDidFocus, and calls `-reloadInputViews` while showing the
+                keyboard. UIKit may then call back into `-requestAutocorrectionContextWithCompletionHandler:`, which
+                triggers sync IPC back to the web process. While waiting to receive a response message, we receive and
+                dispatch WebPageProxy::RunJavaScriptAlert.
+
+        At this point, the UI process is still waiting for a HandleAutocorrectionContext response IPC message, but the
+        web process is blocked on a response to the modal _javascript_ dialog; since both processes are blocked on each
+        other, we're now in deadlock.
+
+        To mitigate this, we can preemptively send autocorrection context data right before we're about to synchronously
+        block in the web process on a modal dialog. In the UI process, we use this autocorrection context object to
+        immediately stop syncwaiting for a HandleAutocorrectionContext response. See below for more details.
+
+        Test: fast/events/ios/show-modal-alert-during-focus.html
+
+        * Platform/IPC/Connection.cpp:
+        (IPC::Connection::SyncMessageState::dispatchMessages):
+        (IPC::Connection::waitForMessage):
+
+        Make a slight adjustment here so that `waitForMessage` bails if one of the messages we're dispatching underneath
+        `SyncMessageState::dispatchMessages` is the message we happen to be waiting for anyways. In this case, it's
+        necessary in order for the preemptive `WebPageProxy::HandleAutocorrectionContext` message sent by the web
+        process to unblock the UI process, which is waiting for a `WebPageProxy::HandleAutocorrectionContext` response.
+
+        * UIProcess/PageClient.h:
+        (WebKit::PageClient::runModalJavaScriptDialog):
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::runModalJavaScriptDialog):
+        (WebKit::WebPageProxy::runJavaScriptAlert):
+        (WebKit::WebPageProxy::runJavaScriptConfirm):
+        (WebKit::WebPageProxy::runJavaScriptPrompt):
+
+        Wrap calls to `runJavaScript(Alert|Confirm|Prompt)` underneath a new page client method that takes a block,
+        `runModalJavaScriptDialog`. See `-[WKContentView runModalJavaScriptDialog:]` below for more details.
+
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/ios/PageClientImplIOS.h:
+        * UIProcess/ios/PageClientImplIOS.mm:
+        (WebKit::PageClientImpl::runModalJavaScriptDialog):
+        * UIProcess/ios/WKContentViewInteraction.h:
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView requestAutocorrectionContextWithCompletionHandler:]):
+
+        Avoid blocking on the web process if it's waiting on a modal _javascript_ dialog anyways; just use the last
+        autocorrection context we received from the web process (which we know must've been sent right before showing
+        the modal dialog anyways).
+
+        (-[WKContentView runModalJavaScriptDialog:]):
+
+        In the case where we're in the process of presenting the keyboard (or UCB) on iOS after focusing an input field,
+        avoid immediately allowing the client to show any modal UI until we're done managing keyboard logic in WebKit.
+        Without this adjustment, we'll end up trying to present the modal _javascript_ dialog in the middle of UIKit's
+        call to `-requestAutocorrectionContextWithCompletionHandler:`, which then causes UIKit to throw an exception
+        underneath keyboard code.
+
+        (-[WKContentView _elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:]):
+        * WebProcess/WebCoreSupport/WebChromeClient.cpp:
+        (WebKit::WebChromeClient::runJavaScriptAlert):
+        (WebKit::WebChromeClient::runJavaScriptConfirm):
+        (WebKit::WebChromeClient::runJavaScriptPrompt):
+        * WebProcess/WebPage/WebPage.h:
+        (WebKit::WebPage::prepareToRunModalJavaScriptDialog):
+
+        Add logic to preemptively compute and send `WebAutocorrectionContext` data to the UI process on iOS, in the case
+        where we're about to show a modal _javascript_ dialog and there is an editable focused element. While the modal
+        dialog is shown, we'll use this information to immediately respond to UIKit's autocorrection context requests,
+        instead of trying to block on responses from the web process.
+
+        * WebProcess/WebPage/ios/WebPageIOS.mm:
+        (WebKit::WebPage::requestAutocorrectionContext):
+        (WebKit::WebPage::prepareToRunModalJavaScriptDialog):
+
 2021-05-06  Darin Adler  <[email protected]>
 
         Streamline codec parsing, replacing uses of HashMap with SortedArrayMap

Modified: trunk/Source/WebKit/Platform/IPC/Connection.cpp (277096 => 277097)


--- trunk/Source/WebKit/Platform/IPC/Connection.cpp	2021-05-06 16:35:37 UTC (rev 277096)
+++ trunk/Source/WebKit/Platform/IPC/Connection.cpp	2021-05-06 16:54:12 UTC (rev 277097)
@@ -92,7 +92,7 @@
     bool processIncomingMessage(Connection&, std::unique_ptr<Decoder>&);
 
     // Dispatch pending sync messages.
-    void dispatchMessages();
+    void dispatchMessages(Function<void(MessageName, uint64_t)>&& willDispatchMessage = { });
 
     // Add matching pending messages to the provided MessageReceiveQueue.
     void enqueueMatchingMessages(Connection&, MessageReceiveQueue&, ReceiverName, uint64_t destinationID);
@@ -192,7 +192,7 @@
     return true;
 }
 
-void Connection::SyncMessageState::dispatchMessages()
+void Connection::SyncMessageState::dispatchMessages(Function<void(MessageName, uint64_t)>&& willDispatchMessage)
 {
     ASSERT(RunLoop::isMain());
 
@@ -206,8 +206,12 @@
         }
     }
 
-    while (!m_messagesBeingDispatched.isEmpty())
-        m_messagesBeingDispatched.takeFirst().dispatch();
+    while (!m_messagesBeingDispatched.isEmpty()) {
+        auto messageToDispatch = m_messagesBeingDispatched.takeFirst();
+        if (willDispatchMessage)
+            willDispatchMessage(messageToDispatch.message->messageName(), messageToDispatch.message->destinationID());
+        messageToDispatch.dispatch();
+    }
 }
 
 void Connection::SyncMessageState::dispatchMessagesAndResetDidScheduleDispatchMessagesForConnection(Connection& connection)
@@ -567,10 +571,18 @@
     // Now wait for it to be set.
     while (true) {
         // Handle any messages that are blocked on a response from us.
-        SyncMessageState::singleton().dispatchMessages();
+        bool wasMessageToWaitForAlreadyDispatched = false;
+        SyncMessageState::singleton().dispatchMessages([&](auto nameOfMessageToDispatch, uint64_t destinationOfMessageToDispatch) {
+            wasMessageToWaitForAlreadyDispatched |= messageName == nameOfMessageToDispatch && destinationID == destinationOfMessageToDispatch;
+        });
 
         Locker lock { m_waitForMessageMutex };
 
+        if (wasMessageToWaitForAlreadyDispatched) {
+            m_waitingForMessage = nullptr;
+            break;
+        }
+
         if (m_waitingForMessage->decoder) {
             auto decoder = WTFMove(m_waitingForMessage->decoder);
             m_waitingForMessage = nullptr;

Modified: trunk/Source/WebKit/UIProcess/PageClient.h (277096 => 277097)


--- trunk/Source/WebKit/UIProcess/PageClient.h	2021-05-06 16:35:37 UTC (rev 277096)
+++ trunk/Source/WebKit/UIProcess/PageClient.h	2021-05-06 16:54:12 UTC (rev 277097)
@@ -335,6 +335,8 @@
     virtual void didCompleteSyntheticClick() = 0;
 #endif
 
+    virtual void runModalJavaScriptDialog(CompletionHandler<void()>&& callback) { callback(); }
+
 #if HAVE(VISIBILITY_PROPAGATION_VIEW)
     virtual void didCreateContextInWebProcessForVisibilityPropagation(LayerHostingContextID) { }
 #if ENABLE(GPU_PROCESS)

Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.cpp (277096 => 277097)


--- trunk/Source/WebKit/UIProcess/WebPageProxy.cpp	2021-05-06 16:35:37 UTC (rev 277096)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.cpp	2021-05-06 16:54:12 UTC (rev 277097)
@@ -192,6 +192,7 @@
 #include <wtf/SystemTracing.h>
 #include <wtf/URL.h>
 #include <wtf/URLParser.h>
+#include <wtf/WeakPtr.h>
 #include <wtf/text/StringView.h>
 #include <wtf/text/TextStream.h>
 
@@ -5691,9 +5692,24 @@
     m_uiClient->close(this);
 }
 
+void WebPageProxy::runModalJavaScriptDialog(RefPtr<WebFrameProxy>&& frame, FrameInfoData&& frameInfo, const String& message, CompletionHandler<void(WebPageProxy&, WebFrameProxy* frame, FrameInfoData&& frameInfo, const String& message, CompletionHandler<void()>&&)>&& runDialogCallback)
+{
+    pageClient().runModalJavaScriptDialog([weakThis = makeWeakPtr(*this), frameInfo = WTFMove(frameInfo), frame = WTFMove(frame), message, runDialogCallback = WTFMove(runDialogCallback)]() mutable {
+        auto protectedThis = makeRefPtr(weakThis.get());
+        if (!protectedThis)
+            return;
+
+        protectedThis->m_isRunningModalJavaScriptDialog = true;
+        runDialogCallback(*protectedThis, frame.get(), WTFMove(frameInfo), message, [weakThis = WTFMove(weakThis)]() mutable {
+            if (auto protectedThis = makeRefPtr(weakThis.get()))
+                protectedThis->m_isRunningModalJavaScriptDialog = false;
+        });
+    });
+}
+
 void WebPageProxy::runJavaScriptAlert(FrameIdentifier frameID, FrameInfoData&& frameInfo, const String& message, Messages::WebPageProxy::RunJavaScriptAlert::DelayedReply&& reply)
 {
-    WebFrameProxy* frame = m_process->webFrame(frameID);
+    auto frame = makeRefPtr(m_process->webFrame(frameID));
     MESSAGE_CHECK(m_process, frame);
 
     exitFullscreenImmediately();
@@ -5705,12 +5721,18 @@
         if (auto* automationSession = process().processPool().automationSession())
             automationSession->willShowJavaScriptDialog(*this);
     }
-    m_uiClient->runJavaScriptAlert(*this, message, frame, WTFMove(frameInfo), WTFMove(reply));
+
+    runModalJavaScriptDialog(WTFMove(frame), WTFMove(frameInfo), message, [reply = WTFMove(reply)](WebPageProxy& page, WebFrameProxy* frame, FrameInfoData&& frameInfo, const String& message, CompletionHandler<void()>&& completion) mutable {
+        page.m_uiClient->runJavaScriptAlert(page, message, frame, WTFMove(frameInfo), [reply = WTFMove(reply), completion = WTFMove(completion)]() mutable {
+            reply();
+            completion();
+        });
+    });
 }
 
 void WebPageProxy::runJavaScriptConfirm(FrameIdentifier frameID, FrameInfoData&& frameInfo, const String& message, Messages::WebPageProxy::RunJavaScriptConfirm::DelayedReply&& reply)
 {
-    WebFrameProxy* frame = m_process->webFrame(frameID);
+    auto frame = makeRefPtr(m_process->webFrame(frameID));
     MESSAGE_CHECK(m_process, frame);
 
     exitFullscreenImmediately();
@@ -5723,12 +5745,17 @@
             automationSession->willShowJavaScriptDialog(*this);
     }
 
-    m_uiClient->runJavaScriptConfirm(*this, message, frame, WTFMove(frameInfo), WTFMove(reply));
+    runModalJavaScriptDialog(WTFMove(frame), WTFMove(frameInfo), message, [reply = WTFMove(reply)](WebPageProxy& page, WebFrameProxy* frame, FrameInfoData&& frameInfo, const String& message, CompletionHandler<void()>&& completion) mutable {
+        page.m_uiClient->runJavaScriptConfirm(page, message, frame, WTFMove(frameInfo), [reply = WTFMove(reply), completion = WTFMove(completion)](bool result) mutable {
+            reply(result);
+            completion();
+        });
+    });
 }
 
 void WebPageProxy::runJavaScriptPrompt(FrameIdentifier frameID, FrameInfoData&& frameInfo, const String& message, const String& defaultValue, Messages::WebPageProxy::RunJavaScriptPrompt::DelayedReply&& reply)
 {
-    WebFrameProxy* frame = m_process->webFrame(frameID);
+    auto frame = makeRefPtr(m_process->webFrame(frameID));
     MESSAGE_CHECK(m_process, frame);
 
     exitFullscreenImmediately();
@@ -5741,7 +5768,12 @@
             automationSession->willShowJavaScriptDialog(*this);
     }
 
-    m_uiClient->runJavaScriptPrompt(*this, message, defaultValue, frame, WTFMove(frameInfo), WTFMove(reply));
+    runModalJavaScriptDialog(WTFMove(frame), WTFMove(frameInfo), message, [reply = WTFMove(reply), defaultValue](WebPageProxy& page, WebFrameProxy* frame, FrameInfoData&& frameInfo, const String& message, CompletionHandler<void()>&& completion) mutable {
+        page.m_uiClient->runJavaScriptPrompt(page, message, defaultValue, frame, WTFMove(frameInfo), [reply = WTFMove(reply), completion = WTFMove(completion)](auto& result) mutable {
+            reply(result);
+            completion();
+        });
+    });
 }
 
 void WebPageProxy::setStatusText(const String& text)

Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.h (277096 => 277097)


--- trunk/Source/WebKit/UIProcess/WebPageProxy.h	2021-05-06 16:35:37 UTC (rev 277096)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.h	2021-05-06 16:54:12 UTC (rev 277097)
@@ -1930,6 +1930,8 @@
     void setCocoaView(WKWebView *);
 #endif
 
+    bool isRunningModalJavaScriptDialog() const { return m_isRunningModalJavaScriptDialog; }
+
 private:
     WebPageProxy(PageClient&, WebProcessProxy&, Ref<API::PageConfiguration>&&);
     void platformInitialize();
@@ -2474,6 +2476,8 @@
 
     void didUpdateEditorState(const EditorState& oldEditorState, const EditorState& newEditorState);
 
+    void runModalJavaScriptDialog(RefPtr<WebFrameProxy>&&, FrameInfoData&&, const String& message, CompletionHandler<void(WebPageProxy&, WebFrameProxy*, FrameInfoData&&, const String&, CompletionHandler<void()>&&)>&&);
+
     const Identifier m_identifier;
     WebCore::PageIdentifier m_webPageID;
     WeakPtr<PageClient> m_pageClient;
@@ -3009,6 +3013,7 @@
     size_t m_suspendMediaPlaybackCounter { 0 };
 
     bool m_lastNavigationWasAppBound { false };
+    bool m_isRunningModalJavaScriptDialog { false };
 
     Optional<WebCore::PrivateClickMeasurement> m_privateClickMeasurement;
 

Modified: trunk/Source/WebKit/UIProcess/ios/PageClientImplIOS.h (277096 => 277097)


--- trunk/Source/WebKit/UIProcess/ios/PageClientImplIOS.h	2021-05-06 16:35:37 UTC (rev 277096)
+++ trunk/Source/WebKit/UIProcess/ios/PageClientImplIOS.h	2021-05-06 16:54:12 UTC (rev 277097)
@@ -244,6 +244,8 @@
     void didNotHandleTapAsMeaningfulClickAtPoint(const WebCore::IntPoint&) final;
     void didCompleteSyntheticClick() override;
 
+    void runModalJavaScriptDialog(CompletionHandler<void()>&& callback) final;
+
     void didChangeBackgroundColor() override;
     void videoControlsManagerDidChange() override;
 

Modified: trunk/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm (277096 => 277097)


--- trunk/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm	2021-05-06 16:35:37 UTC (rev 277096)
+++ trunk/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm	2021-05-06 16:54:12 UTC (rev 277097)
@@ -1000,6 +1000,11 @@
 }
 #endif
 
+void PageClientImpl::runModalJavaScriptDialog(CompletionHandler<void()>&& callback)
+{
+    [m_contentView runModalJavaScriptDialog:WTFMove(callback)];
+}
+
 } // namespace WebKit
 
 #endif // PLATFORM(IOS_FAMILY)

Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h (277096 => 277097)


--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h	2021-05-06 16:35:37 UTC (rev 277096)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h	2021-05-06 16:54:12 UTC (rev 277097)
@@ -62,6 +62,7 @@
 #import <wtf/BlockPtr.h>
 #import <wtf/CompletionHandler.h>
 #import <wtf/Forward.h>
+#import <wtf/Function.h>
 #import <wtf/OptionSet.h>
 #import <wtf/Vector.h>
 #import <wtf/WeakObjCPtr.h>
@@ -431,6 +432,7 @@
     NSInteger _suppressNonEditableSingleTapTextInteractionCount;
     CompletionHandler<void(WebCore::DOMPasteAccessResponse)> _domPasteRequestHandler;
     BlockPtr<void(UIWKAutocorrectionContext *)> _pendingAutocorrectionContextHandler;
+    CompletionHandler<void()> _pendingRunModalJavaScriptDialogCallback;
 
     RetainPtr<NSDictionary> _additionalContextForStrongPasswordAssistance;
 
@@ -639,6 +641,7 @@
 - (BOOL)ensurePositionInformationIsUpToDate:(WebKit::InteractionInformationRequest)request;
 
 - (void)doAfterEditorStateUpdateAfterFocusingElement:(dispatch_block_t)block;
+- (void)runModalJavaScriptDialog:(CompletionHandler<void()>&&)callback;
 
 #if ENABLE(DRAG_SUPPORT)
 - (void)_didChangeDragInteractionPolicy;

Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm (277096 => 277097)


--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm	2021-05-06 16:35:37 UTC (rev 277096)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm	2021-05-06 16:54:12 UTC (rev 277097)
@@ -134,6 +134,7 @@
 #import <wtf/BlockPtr.h>
 #import <wtf/Optional.h>
 #import <wtf/RetainPtr.h>
+#import <wtf/Scope.h>
 #import <wtf/SetForScope.h>
 #import <wtf/WeakObjCPtr.h>
 #import <wtf/cocoa/NSURLExtras.h>
@@ -4502,7 +4503,7 @@
         return;
     }
 
-    if (_domPasteRequestHandler) {
+    if (_page->isRunningModalJavaScriptDialog() || _domPasteRequestHandler) {
         completionHandler([WKAutocorrectionContext autocorrectionContextWithWebContext:_lastAutocorrectionContext]);
         return;
     }
@@ -4530,6 +4531,14 @@
     [self _invokePendingAutocorrectionContextHandler:[WKAutocorrectionContext autocorrectionContextWithWebContext:context]];
 }
 
+- (void)runModalJavaScriptDialog:(CompletionHandler<void()>&&)callback
+{
+    if (_isFocusingElementWithKeyboard)
+        _pendingRunModalJavaScriptDialogCallback = WTFMove(callback);
+    else
+        callback();
+}
+
 - (void)_didStartProvisionalLoadForMainFrame
 {
     // Reset the double tap gesture recognizer to prevent any double click that is in the process of being recognized.
@@ -6161,6 +6170,11 @@
     SetForScope<BOOL> isChangingFocusForScope { _isChangingFocus, self._hasFocusedElement };
     SetForScope<BOOL> isFocusingElementWithKeyboardForScope { _isFocusingElementWithKeyboard, [self _shouldShowKeyboardForElement:information] };
 
+    auto runModalJavaScriptDialogCallbackIfNeeded = makeScopeExit([&] {
+        if (auto callback = std::exchange(_pendingRunModalJavaScriptDialogCallback, { }))
+            callback();
+    });
+
     auto inputViewUpdateDeferrer = std::exchange(_inputViewUpdateDeferrer, nullptr);
 
     _didAccessoryTabInitiateFocus = _isChangingFocusUsingAccessoryTab;

Modified: trunk/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp (277096 => 277097)


--- trunk/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp	2021-05-06 16:35:37 UTC (rev 277096)
+++ trunk/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp	2021-05-06 16:54:12 UTC (rev 277097)
@@ -456,6 +456,7 @@
 
     // Notify the bundle client.
     m_page.injectedBundleUIClient().willRunJavaScriptAlert(&m_page, alertText, webFrame);
+    m_page.prepareToRunModalJavaScriptDialog();
 
     HangDetectionDisabler hangDetectionDisabler;
     IPC::UnboundedSynchronousIPCScope unboundedSynchronousIPCScope;
@@ -473,6 +474,7 @@
 
     // Notify the bundle client.
     m_page.injectedBundleUIClient().willRunJavaScriptConfirm(&m_page, message, webFrame);
+    m_page.prepareToRunModalJavaScriptDialog();
 
     HangDetectionDisabler hangDetectionDisabler;
     IPC::UnboundedSynchronousIPCScope unboundedSynchronousIPCScope;
@@ -494,6 +496,7 @@
 
     // Notify the bundle client.
     m_page.injectedBundleUIClient().willRunJavaScriptPrompt(&m_page, message, defaultValue, webFrame);
+    m_page.prepareToRunModalJavaScriptDialog();
 
     HangDetectionDisabler hangDetectionDisabler;
     IPC::UnboundedSynchronousIPCScope unboundedSynchronousIPCScope;

Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.h (277096 => 277097)


--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.h	2021-05-06 16:35:37 UTC (rev 277096)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.h	2021-05-06 16:54:12 UTC (rev 277097)
@@ -1449,6 +1449,7 @@
 #endif
 
     void didHandleOrPreventMouseDownOrMouseUpEvent();
+    void prepareToRunModalJavaScriptDialog();
 
 private:
     WebPage(WebCore::PageIdentifier, WebPageCreationParameters&&);
@@ -2341,6 +2342,7 @@
 inline void WebPage::platformWillPerformEditingCommand() { }
 inline bool WebPage::platformNeedsLayoutForEditorState(const WebCore::Frame&) const { return false; }
 inline void WebPage::didHandleOrPreventMouseDownOrMouseUpEvent() { }
+inline void WebPage::prepareToRunModalJavaScriptDialog() { }
 #endif
 
 } // namespace WebKit

Modified: trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm (277096 => 277097)


--- trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm	2021-05-06 16:35:37 UTC (rev 277096)
+++ trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm	2021-05-06 16:54:12 UTC (rev 277097)
@@ -2588,6 +2588,21 @@
     send(Messages::WebPageProxy::HandleAutocorrectionContext(autocorrectionContext()));
 }
 
+void WebPage::prepareToRunModalJavaScriptDialog()
+{
+    if (!m_focusedElement)
+        return;
+
+    if (!m_focusedElement->hasEditableStyle() && !is<HTMLTextFormControlElement>(*m_focusedElement))
+        return;
+
+    // When a modal dialog is presented while an editable element is focused, UIKit will attempt to request a
+    // WebAutocorrectionContext, which triggers synchronous IPC back to the web process, resulting in deadlock.
+    // To avoid this deadlock, we preemptively compute and send autocorrection context data to the UI process,
+    // such that the UI process can immediately respond to UIKit without synchronous IPC to the web process.
+    send(Messages::WebPageProxy::HandleAutocorrectionContext(autocorrectionContext()));
+}
+
 static HTMLAnchorElement* containingLinkAnchorElement(Element& element)
 {
     // FIXME: There is code in the drag controller that supports any link, even if it's not an HTMLAnchorElement. Why is this different?
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to