Title: [288925] trunk
Revision
288925
Author
[email protected]
Date
2022-02-01 16:23:52 -0800 (Tue, 01 Feb 2022)

Log Message

Mitigate hangs underneath -requestAutocorrectionContextWithCompletionHandler: when focusing text fields
https://bugs.webkit.org/show_bug.cgi?id=235963
rdar://70152487

Reviewed by Tim Horton.

Source/WebKit:

After focusing an input field, UIKit attempts to invoke `-requestAutocorrectionContextWithCompletionHandler:` on
WKContentView up to 5 times, back to back. In WebKit, we handle this like sync IPC by waiting up to 1 second for
a response (this is done because UIKit would otherwise lock the main thread altogether after calling this
method, which results in a permanent deadlock in the UI process).

In the case where the web process is also sending a sync message to the UI process at the same time (for
instance, when spinning up the GPU or web authn process), this results in back-to-back 1 second deadlocks, for a
total UI process hang time of up to 5 seconds (as observed on an iPad with hardware keyboard attached).

This patch attempts to reduce hangs in `-[WKContentView requestAutocorrectionContextWithCompletionHandler:]` by
introducing a mechanism that throttles redundant `-requestAutocorrectionContextWithCompletionHandler:` requests
during the same runloop by immediately returning the last received autocorrection context instead of repeatedly
waiting on synchronous IPC message responses. Combined with another mechanism that preemptively sends an
autocorrection context when an input view is shown, this means that we will (mostly) no longer hang when
focusing an input field, or hang for at most 1 second (instead of 5) in the case where we happen to deadlock.

See comments below for more details.

Test: AutocorrectionTests.AutocorrectionContextBeforeAndAfterEditing

* UIProcess/ios/WKContentViewInteraction.h:

Add a new flag, `_autocorrectionContextNeedsUpdate`, that tracks whether or not it's safe to reply to
`-requestAutocorrectionContextWithCompletionHandler:` using the last-received WebAutocorrectionContext data that
was cached in the UI process.

This flag is unset after receiving a WebAutocorrectionContext, and set when any asynchronous text input methods
on WKContentView have been invoked, which may cause the autocorrection context to change. This is important in
order to preserve the existing behavior that a call to `-requestAutocorrectionContextWithCompletionHandler:`
immediately after calling a method like `-insertText:` or `-selectAll:` will not immediately return with a stale
autocorrection context.

The flag is also unset after the current runloop finishes, ensuring that this hack is limited to back-to-back
autocorrection contexts where editing actions are not dispatched in between each request. In the future, we may
be able to make this caching policy more aggressive, by removing this invalidation, which would allow us to keep
using the same autocorrection context data until we either learn about a programmatic selection change in the UI
process, or the WebKit client (in the UI process) has triggered editing or selection.

* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView setUpInteraction]):
(-[WKContentView cleanUpInteraction]):
(-[WKContentView replaceText:withText:]):
(-[WKContentView selectWordBackward]):
(-[WKContentView pasteForWebView:]):
(-[WKContentView _pasteAsQuotationForWebView:]):
(-[WKContentView selectForWebView:]):
(-[WKContentView selectAllForWebView:]):
(-[WKContentView changeSelectionWithGestureAt:withGesture:withState:withFlags:]):
(-[WKContentView changeSelectionWithTouchAt:withSelectionTouch:baseIsStart:withFlags:]):
(-[WKContentView changeSelectionWithTouchesFrom:to:withGesture:withState:]):
(-[WKContentView selectPositionAtPoint:completionHandler:]):
(-[WKContentView _selectPositionAtPoint:stayingWithinFocusedElement:completionHandler:]):
(-[WKContentView selectPositionAtBoundary:inDirection:fromPoint:completionHandler:]):
(-[WKContentView moveSelectionAtBoundary:inDirection:completionHandler:]):
(-[WKContentView selectTextWithGranularity:atPoint:completionHandler:]):
(-[WKContentView beginSelectionInDirection:completionHandler:]):
(-[WKContentView updateSelectionWithExtentPoint:completionHandler:]):
(-[WKContentView updateSelectionWithExtentPoint:withBoundary:completionHandler:]):
(-[WKContentView replaceDictatedText:withText:]):
(-[WKContentView requestAutocorrectionContextWithCompletionHandler:]):
(-[WKContentView _handleAutocorrectionContext:]):
(-[WKContentView beginSelectionChange]):
(-[WKContentView insertTextSuggestion:]):
(-[WKContentView _setMarkedText:highlights:selectedRange:]):
(-[WKContentView insertText:]):
(-[WKContentView insertText:alternatives:style:]):
(-[WKContentView _focusTextInputContext:placeCaretAt:completionHandler:]):
(-[WKContentView _willBeginTextInteractionInTextInputContext:]):
(-[WKContentView executeEditCommandWithCallback:]):
(-[WKContentView _elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:]):
(-[WKContentView _elementDidBlur]):
(-[WKContentView _selectionChanged]):
* UIProcess/ios/WebPageProxyIOS.mm:
(WebKit::WebPageProxy::requestAutocorrectionContext):
* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::requestDOMPasteAccess):
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/WebPage.messages.in:
* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::setIsShowingInputViewForFocusedElement):
(WebKit::WebPage::preemptivelySendAutocorrectionContext):

Pull out logic that preemptively computes and sends WebAutocorrectionContexts to the UI process (in anticipation
of synchronous IPC) into an appropriately-named helper method; additionally, call this new helper method when
the keyboard has been shown, following element focus in the UI process.

(WebKit::WebPage::handleAutocorrectionContextRequest):
(WebKit::WebPage::prepareToRunModalJavaScriptDialog):
(WebKit::WebPage::requestAutocorrectionContext): Deleted.

Drive-by: rename this to `handleAutocorrectionContextRequest` for clarity, since the purpose of this method is
to service a request for autocorrection context that was sent by the UI process.

Tools:

Add a new API test to exercise a scenario that currently isn't covered by existing tests: if the text input
client (i.e. UIKit) calls `-requestAutocorrectionContextWithCompletionHandler:`, then invokes an asynchronous
API that performs an editing action or changes the selection, and then requests another autocorrection context,
the second autocorrection context should reflect the changes that occurred as a result of the async editing
action.

* TestWebKitAPI/Tests/ios/AutocorrectionTestsIOS.mm:

Modified Paths

Diff

Modified: trunk/Source/WebKit/ChangeLog (288924 => 288925)


--- trunk/Source/WebKit/ChangeLog	2022-02-02 00:10:11 UTC (rev 288924)
+++ trunk/Source/WebKit/ChangeLog	2022-02-02 00:23:52 UTC (rev 288925)
@@ -1,3 +1,104 @@
+2022-02-01  Wenson Hsieh  <[email protected]>
+
+        Mitigate hangs underneath -requestAutocorrectionContextWithCompletionHandler: when focusing text fields
+        https://bugs.webkit.org/show_bug.cgi?id=235963
+        rdar://70152487
+
+        Reviewed by Tim Horton.
+
+        After focusing an input field, UIKit attempts to invoke `-requestAutocorrectionContextWithCompletionHandler:` on
+        WKContentView up to 5 times, back to back. In WebKit, we handle this like sync IPC by waiting up to 1 second for
+        a response (this is done because UIKit would otherwise lock the main thread altogether after calling this
+        method, which results in a permanent deadlock in the UI process).
+
+        In the case where the web process is also sending a sync message to the UI process at the same time (for
+        instance, when spinning up the GPU or web authn process), this results in back-to-back 1 second deadlocks, for a
+        total UI process hang time of up to 5 seconds (as observed on an iPad with hardware keyboard attached).
+
+        This patch attempts to reduce hangs in `-[WKContentView requestAutocorrectionContextWithCompletionHandler:]` by
+        introducing a mechanism that throttles redundant `-requestAutocorrectionContextWithCompletionHandler:` requests
+        during the same runloop by immediately returning the last received autocorrection context instead of repeatedly
+        waiting on synchronous IPC message responses. Combined with another mechanism that preemptively sends an
+        autocorrection context when an input view is shown, this means that we will (mostly) no longer hang when
+        focusing an input field, or hang for at most 1 second (instead of 5) in the case where we happen to deadlock.
+
+        See comments below for more details.
+
+        Test: AutocorrectionTests.AutocorrectionContextBeforeAndAfterEditing
+
+        * UIProcess/ios/WKContentViewInteraction.h:
+
+        Add a new flag, `_autocorrectionContextNeedsUpdate`, that tracks whether or not it's safe to reply to
+        `-requestAutocorrectionContextWithCompletionHandler:` using the last-received WebAutocorrectionContext data that
+        was cached in the UI process.
+
+        This flag is unset after receiving a WebAutocorrectionContext, and set when any asynchronous text input methods
+        on WKContentView have been invoked, which may cause the autocorrection context to change. This is important in
+        order to preserve the existing behavior that a call to `-requestAutocorrectionContextWithCompletionHandler:`
+        immediately after calling a method like `-insertText:` or `-selectAll:` will not immediately return with a stale
+        autocorrection context.
+
+        The flag is also unset after the current runloop finishes, ensuring that this hack is limited to back-to-back
+        autocorrection contexts where editing actions are not dispatched in between each request. In the future, we may
+        be able to make this caching policy more aggressive, by removing this invalidation, which would allow us to keep
+        using the same autocorrection context data until we either learn about a programmatic selection change in the UI
+        process, or the WebKit client (in the UI process) has triggered editing or selection.
+
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView setUpInteraction]):
+        (-[WKContentView cleanUpInteraction]):
+        (-[WKContentView replaceText:withText:]):
+        (-[WKContentView selectWordBackward]):
+        (-[WKContentView pasteForWebView:]):
+        (-[WKContentView _pasteAsQuotationForWebView:]):
+        (-[WKContentView selectForWebView:]):
+        (-[WKContentView selectAllForWebView:]):
+        (-[WKContentView changeSelectionWithGestureAt:withGesture:withState:withFlags:]):
+        (-[WKContentView changeSelectionWithTouchAt:withSelectionTouch:baseIsStart:withFlags:]):
+        (-[WKContentView changeSelectionWithTouchesFrom:to:withGesture:withState:]):
+        (-[WKContentView selectPositionAtPoint:completionHandler:]):
+        (-[WKContentView _selectPositionAtPoint:stayingWithinFocusedElement:completionHandler:]):
+        (-[WKContentView selectPositionAtBoundary:inDirection:fromPoint:completionHandler:]):
+        (-[WKContentView moveSelectionAtBoundary:inDirection:completionHandler:]):
+        (-[WKContentView selectTextWithGranularity:atPoint:completionHandler:]):
+        (-[WKContentView beginSelectionInDirection:completionHandler:]):
+        (-[WKContentView updateSelectionWithExtentPoint:completionHandler:]):
+        (-[WKContentView updateSelectionWithExtentPoint:withBoundary:completionHandler:]):
+        (-[WKContentView replaceDictatedText:withText:]):
+        (-[WKContentView requestAutocorrectionContextWithCompletionHandler:]):
+        (-[WKContentView _handleAutocorrectionContext:]):
+        (-[WKContentView beginSelectionChange]):
+        (-[WKContentView insertTextSuggestion:]):
+        (-[WKContentView _setMarkedText:highlights:selectedRange:]):
+        (-[WKContentView insertText:]):
+        (-[WKContentView insertText:alternatives:style:]):
+        (-[WKContentView _focusTextInputContext:placeCaretAt:completionHandler:]):
+        (-[WKContentView _willBeginTextInteractionInTextInputContext:]):
+        (-[WKContentView executeEditCommandWithCallback:]):
+        (-[WKContentView _elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:]):
+        (-[WKContentView _elementDidBlur]):
+        (-[WKContentView _selectionChanged]):
+        * UIProcess/ios/WebPageProxyIOS.mm:
+        (WebKit::WebPageProxy::requestAutocorrectionContext):
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::requestDOMPasteAccess):
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/WebPage.messages.in:
+        * WebProcess/WebPage/ios/WebPageIOS.mm:
+        (WebKit::WebPage::setIsShowingInputViewForFocusedElement):
+        (WebKit::WebPage::preemptivelySendAutocorrectionContext):
+
+        Pull out logic that preemptively computes and sends WebAutocorrectionContexts to the UI process (in anticipation
+        of synchronous IPC) into an appropriately-named helper method; additionally, call this new helper method when
+        the keyboard has been shown, following element focus in the UI process.
+
+        (WebKit::WebPage::handleAutocorrectionContextRequest):
+        (WebKit::WebPage::prepareToRunModalJavaScriptDialog):
+        (WebKit::WebPage::requestAutocorrectionContext): Deleted.
+
+        Drive-by: rename this to `handleAutocorrectionContextRequest` for clarity, since the purpose of this method is
+        to service a request for autocorrection context that was sent by the UI process.
+
 2022-02-01  Sihui Liu  <[email protected]>
 
         Write origin file when OriginStorageManager is destroyed

Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h (288924 => 288925)


--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h	2022-02-02 00:10:11 UTC (rev 288924)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h	2022-02-02 00:23:52 UTC (rev 288925)
@@ -437,6 +437,7 @@
     BOOL _textInteractionDidChangeFocusedElement;
     BOOL _treatAsContentEditableUntilNextEditorStateUpdate;
     bool _isWaitingOnPositionInformation;
+    BOOL _autocorrectionContextNeedsUpdate;
 
     WebCore::PointerID _commitPotentialTapPointerId;
 

Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm (288924 => 288925)


--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm	2022-02-02 00:10:11 UTC (rev 288924)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm	2022-02-02 00:23:52 UTC (rev 288925)
@@ -1077,6 +1077,8 @@
     _textCheckingController = makeUnique<WebKit::TextCheckingController>(*_page);
 #endif
 
+    _autocorrectionContextNeedsUpdate = YES;
+
     _page->process().updateTextCheckerState();
     _page->setScreenIsBeingCaptured([[[self window] screen] isCaptured]);
 
@@ -1112,6 +1114,7 @@
     _additionalContextForStrongPasswordAssistance = nil;
     _waitingForEditDragSnapshot = NO;
 
+    _autocorrectionContextNeedsUpdate = YES;
     _lastAutocorrectionContext = { };
 
     _candidateViewNeedsUpdate = NO;
@@ -3574,11 +3577,13 @@
 
 - (void)replaceText:(NSString *)text withText:(NSString *)word
 {
+    _autocorrectionContextNeedsUpdate = YES;
     _page->replaceSelectedText(text, word);
 }
 
 - (void)selectWordBackward
 {
+    _autocorrectionContextNeedsUpdate = YES;
     _page->selectWordBackward();
 }
 
@@ -4036,11 +4041,13 @@
     if (sender == UIMenuController.sharedMenuController && [self _handleDOMPasteRequestWithResult:WebCore::DOMPasteAccessResponse::GrantedForGesture])
         return;
 
+    _autocorrectionContextNeedsUpdate = YES;
     _page->executeEditCommand("paste"_s);
 }
 
 - (void)_pasteAsQuotationForWebView:(id)sender
 {
+    _autocorrectionContextNeedsUpdate = YES;
     _page->executeEditCommand("PasteAsQuotation"_s);
 }
 
@@ -4049,6 +4056,7 @@
     if (!_page->preferences().textInteractionEnabled())
         return;
 
+    _autocorrectionContextNeedsUpdate = YES;
     [_textInteractionAssistant selectWord];
     // We cannot use selectWord command, because we want to be able to select the word even when it is the last in the paragraph.
     _page->extendSelection(WebCore::TextGranularity::WordGranularity);
@@ -4059,6 +4067,7 @@
     if (!_page->preferences().textInteractionEnabled())
         return;
 
+    _autocorrectionContextNeedsUpdate = YES;
     [_textInteractionAssistant selectAll:sender];
     _page->selectAll();
 }
@@ -4428,6 +4437,7 @@
 
 - (void)changeSelectionWithGestureAt:(CGPoint)point withGesture:(UIWKGestureType)gestureType withState:(UIGestureRecognizerState)state withFlags:(UIWKSelectionFlags)flags
 {
+    _autocorrectionContextNeedsUpdate = YES;
     _usingGestureForSelection = YES;
     _page->selectWithGesture(WebCore::IntPoint(point), toGestureType(gestureType), toGestureRecognizerState(state), self._hasFocusedElement, [self, strongSelf = retainPtr(self), state, flags](const WebCore::IntPoint& point, WebKit::GestureType gestureType, WebKit::GestureRecognizerState gestureState, OptionSet<WebKit::SelectionFlags> innerFlags) {
         selectionChangedWithGesture(self, point, gestureType, gestureState, toSelectionFlags(flags) | innerFlags);
@@ -4438,6 +4448,7 @@
 
 - (void)changeSelectionWithTouchAt:(CGPoint)point withSelectionTouch:(UIWKSelectionTouch)touch baseIsStart:(BOOL)baseIsStart withFlags:(UIWKSelectionFlags)flags
 {
+    _autocorrectionContextNeedsUpdate = YES;
     _usingGestureForSelection = YES;
     _page->updateSelectionWithTouches(WebCore::IntPoint(point), toSelectionTouch(touch), baseIsStart, [self, strongSelf = retainPtr(self), flags](const WebCore::IntPoint& point, WebKit::SelectionTouch touch, OptionSet<WebKit::SelectionFlags> innerFlags) {
         selectionChangedWithTouch(self, point, touch, toSelectionFlags(flags) | innerFlags);
@@ -4448,6 +4459,7 @@
 
 - (void)changeSelectionWithTouchesFrom:(CGPoint)from to:(CGPoint)to withGesture:(UIWKGestureType)gestureType withState:(UIGestureRecognizerState)gestureState
 {
+    _autocorrectionContextNeedsUpdate = YES;
     _usingGestureForSelection = YES;
     _page->selectWithTwoTouches(WebCore::IntPoint(from), WebCore::IntPoint(to), toGestureType(gestureType), toGestureRecognizerState(gestureState), [self, strongSelf = retainPtr(self)](const WebCore::IntPoint& point, WebKit::GestureType gestureType, WebKit::GestureRecognizerState gestureState, OptionSet<WebKit::SelectionFlags> flags) {
         selectionChangedWithGesture(self, point, gestureType, gestureState, flags);
@@ -4545,11 +4557,13 @@
 
 - (void)selectPositionAtPoint:(CGPoint)point completionHandler:(void (^)(void))completionHandler
 {
+    _autocorrectionContextNeedsUpdate = YES;
     [self _selectPositionAtPoint:point stayingWithinFocusedElement:self._hasFocusedElement completionHandler:completionHandler];
 }
 
 - (void)_selectPositionAtPoint:(CGPoint)point stayingWithinFocusedElement:(BOOL)stayingWithinFocusedElement completionHandler:(void (^)(void))completionHandler
 {
+    _autocorrectionContextNeedsUpdate = YES;
     _usingGestureForSelection = YES;
 
     _page->selectPositionAtPoint(WebCore::IntPoint(point), stayingWithinFocusedElement, [view = retainPtr(self), completionHandler = makeBlockPtr(completionHandler)]() {
@@ -4560,6 +4574,7 @@
 
 - (void)selectPositionAtBoundary:(UITextGranularity)granularity inDirection:(UITextDirection)direction fromPoint:(CGPoint)point completionHandler:(void (^)(void))completionHandler
 {
+    _autocorrectionContextNeedsUpdate = YES;
     _usingGestureForSelection = YES;
     _page->selectPositionAtBoundaryWithDirection(WebCore::IntPoint(point), toWKTextGranularity(granularity), toWKSelectionDirection(direction), self._hasFocusedElement, [view = retainPtr(self), completionHandler = makeBlockPtr(completionHandler)]() {
         completionHandler();
@@ -4569,6 +4584,7 @@
 
 - (void)moveSelectionAtBoundary:(UITextGranularity)granularity inDirection:(UITextDirection)direction completionHandler:(void (^)(void))completionHandler
 {
+    _autocorrectionContextNeedsUpdate = YES;
     _usingGestureForSelection = YES;
     _page->moveSelectionAtBoundaryWithDirection(toWKTextGranularity(granularity), toWKSelectionDirection(direction), [view = retainPtr(self), completionHandler = makeBlockPtr(completionHandler)] {
         completionHandler();
@@ -4578,6 +4594,7 @@
 
 - (void)selectTextWithGranularity:(UITextGranularity)granularity atPoint:(CGPoint)point completionHandler:(void (^)(void))completionHandler
 {
+    _autocorrectionContextNeedsUpdate = YES;
     _usingGestureForSelection = YES;
     ++_suppressNonEditableSingleTapTextInteractionCount;
     _page->selectTextWithGranularityAtPoint(WebCore::IntPoint(point), toWKTextGranularity(granularity), self._hasFocusedElement, [view = retainPtr(self), selectionHandler = makeBlockPtr(completionHandler)] {
@@ -4589,6 +4606,7 @@
 
 - (void)beginSelectionInDirection:(UITextDirection)direction completionHandler:(void (^)(BOOL endIsMoving))completionHandler
 {
+    _autocorrectionContextNeedsUpdate = YES;
     _page->beginSelectionInDirection(toWKSelectionDirection(direction), [selectionHandler = makeBlockPtr(completionHandler)] (bool endIsMoving) {
         selectionHandler(endIsMoving);
     });
@@ -4596,6 +4614,7 @@
 
 - (void)updateSelectionWithExtentPoint:(CGPoint)point completionHandler:(void (^)(BOOL endIsMoving))completionHandler
 {
+    _autocorrectionContextNeedsUpdate = YES;
     auto respectSelectionAnchor = self.interactionAssistant._wk_hasFloatingCursor ? WebKit::RespectSelectionAnchor::Yes : WebKit::RespectSelectionAnchor::No;
     _page->updateSelectionWithExtentPoint(WebCore::IntPoint(point), self._hasFocusedElement, respectSelectionAnchor, [selectionHandler = makeBlockPtr(completionHandler)](bool endIsMoving) {
         selectionHandler(endIsMoving);
@@ -4604,6 +4623,7 @@
 
 - (void)updateSelectionWithExtentPoint:(CGPoint)point withBoundary:(UITextGranularity)granularity completionHandler:(void (^)(BOOL selectionEndIsMoving))completionHandler
 {
+    _autocorrectionContextNeedsUpdate = YES;
     ++_suppressNonEditableSingleTapTextInteractionCount;
     _page->updateSelectionWithExtentPointAndBoundary(WebCore::IntPoint(point), toWKTextGranularity(granularity), self._hasFocusedElement, [completionHandler = makeBlockPtr(completionHandler), protectedSelf = retainPtr(self)] (bool endIsMoving) {
         completionHandler(endIsMoving);
@@ -4657,6 +4677,7 @@
 
 - (void)replaceDictatedText:(NSString*)oldText withText:(NSString *)newText
 {
+    _autocorrectionContextNeedsUpdate = YES;
     _page->replaceDictatedText(oldText, newText);
 }
 
@@ -4729,7 +4750,7 @@
         if (_isUnsuppressingSoftwareKeyboardUsingLastAutocorrectionContext)
             return true;
 
-        return false;
+        return !_autocorrectionContextNeedsUpdate;
     })();
 
     if (respondWithLastKnownAutocorrectionContext) {
@@ -4748,15 +4769,24 @@
     _pendingAutocorrectionContextHandler = completionHandler;
     _page->requestAutocorrectionContext();
 
-    if (useSyncRequest) {
-        _page->process().connection()->waitForAndDispatchImmediately<Messages::WebPageProxy::HandleAutocorrectionContext>(_page->webPageID(), 1_s, IPC::WaitForOption::DispatchIncomingSyncMessagesWhileWaiting);
-        [self _cancelPendingAutocorrectionContextHandler];
-    }
+    if (!useSyncRequest)
+        return;
+
+    callOnMainRunLoop([weakSelf = WeakObjCPtr<WKContentView>(self)]() {
+        if (auto strongSelf = weakSelf.get())
+            strongSelf->_autocorrectionContextNeedsUpdate = YES;
+    });
+
+    if (!_page->process().connection()->waitForAndDispatchImmediately<Messages::WebPageProxy::HandleAutocorrectionContext>(_page->webPageID(), 1_s, IPC::WaitForOption::DispatchIncomingSyncMessagesWhileWaiting))
+        RELEASE_LOG(TextInput, "Timed out while waiting for autocorrection context.");
+
+    [self _cancelPendingAutocorrectionContextHandler];
 }
 
 - (void)_handleAutocorrectionContext:(const WebKit::WebAutocorrectionContext&)context
 {
     _lastAutocorrectionContext = context;
+    _autocorrectionContextNeedsUpdate = NO;
     [self unsuppressSoftwareKeyboardUsingLastAutocorrectionContextIfNeeded];
     [self _invokePendingAutocorrectionContextHandler:[WKAutocorrectionContext autocorrectionContextWithWebContext:context]];
 }
@@ -4993,6 +5023,7 @@
 
 - (void)beginSelectionChange
 {
+    _autocorrectionContextNeedsUpdate = YES;
     _selectionChangeNestingLevel++;
 
     [self.inputDelegate selectionWillChange:self];
@@ -5030,6 +5061,7 @@
 
 - (void)insertTextSuggestion:(UITextSuggestion *)textSuggestion
 {
+    _autocorrectionContextNeedsUpdate = YES;
     // FIXME: Replace NSClassFromString with actual class as soon as UIKit submitted the new class into the iOS SDK.
     if ([textSuggestion isKindOfClass:[UITextAutofillSuggestion class]]) {
         _page->autofillLoginCredentials([(UITextAutofillSuggestion *)textSuggestion username], [(UITextAutofillSuggestion *)textSuggestion password]);
@@ -5230,6 +5262,7 @@
 
 - (void)_setMarkedText:(NSString *)markedText highlights:(const Vector<WebCore::CompositionHighlight>&)highlights selectedRange:(NSRange)selectedRange
 {
+    _autocorrectionContextNeedsUpdate = YES;
     _candidateViewNeedsUpdate = !self.hasMarkedText;
     _markedText = markedText;
     _page->setCompositionAsync(markedText, { }, highlights, selectedRange, { });
@@ -5390,21 +5423,26 @@
         _lastInsertedCharacterToOverrideCharacterBeforeSelection = [aStringValue characterAtIndex:aStringValue.length - 1];
         _page->scheduleFullEditorStateUpdate();
     }
+
+    _autocorrectionContextNeedsUpdate = YES;
 }
 
 - (void)insertText:(NSString *)aStringValue alternatives:(NSArray<NSString *> *)alternatives style:(UITextAlternativeStyle)style
 {
-    if (!alternatives.count)
+    if (!alternatives.count) {
         [self insertText:aStringValue];
-    else {
-        BOOL isLowConfidence = style == UITextAlternativeStyleLowConfidence;
-        auto textAlternatives = adoptNS([[NSTextAlternatives alloc] initWithPrimaryString:aStringValue alternativeStrings:alternatives isLowConfidence:isLowConfidence]);
-        WebCore::TextAlternativeWithRange textAlternativeWithRange { textAlternatives.get(), NSMakeRange(0, aStringValue.length) };
+        return;
+    }
 
-        WebKit::InsertTextOptions options;
-        options.shouldSimulateKeyboardInput = [self _shouldSimulateKeyboardInputOnTextInsertion];
-        _page->insertDictatedTextAsync(aStringValue, { }, { textAlternativeWithRange }, WTFMove(options));
-    }
+    BOOL isLowConfidence = style == UITextAlternativeStyleLowConfidence;
+    auto textAlternatives = adoptNS([[NSTextAlternatives alloc] initWithPrimaryString:aStringValue alternativeStrings:alternatives isLowConfidence:isLowConfidence]);
+    WebCore::TextAlternativeWithRange textAlternativeWithRange { textAlternatives.get(), NSMakeRange(0, aStringValue.length) };
+
+    WebKit::InsertTextOptions options;
+    options.shouldSimulateKeyboardInput = [self _shouldSimulateKeyboardInputOnTextInsertion];
+    _page->insertDictatedTextAsync(aStringValue, { }, { textAlternativeWithRange }, WTFMove(options));
+
+    _autocorrectionContextNeedsUpdate = YES;
 }
 
 - (BOOL)hasText
@@ -5744,6 +5782,7 @@
         completionHandler(_focusedElementInformation.isReadOnly ? nil : self);
         return;
     }
+    _autocorrectionContextNeedsUpdate = YES;
     _usingGestureForSelection = YES;
     auto checkFocusedElement = [weakSelf = WeakObjCPtr<WKContentView> { self }, context = adoptNS([context copy]), completionHandler = makeBlockPtr(completionHandler)] (bool success) {
         auto strongSelf = weakSelf.get();
@@ -5794,6 +5833,7 @@
 
     _textInteractionDidChangeFocusedElement = NO;
     _page->setShouldRevealCurrentSelectionAfterInsertion(false);
+    _autocorrectionContextNeedsUpdate = YES;
     _usingGestureForSelection = YES;
 }
 
@@ -6034,6 +6074,7 @@
 
 - (void)executeEditCommandWithCallback:(NSString *)commandName
 {
+    _autocorrectionContextNeedsUpdate = YES;
     // FIXME: Editing commands are not considered by WebKit as user initiated even if they are the result
     // of keydown or keyup. We need to query the keyboard to determine if this was called from the keyboard
     // or not to know whether to tell WebKit to treat this command as user initiated or not.
@@ -6391,6 +6432,7 @@
 
     auto inputViewUpdateDeferrer = std::exchange(_inputViewUpdateDeferrer, nullptr);
 
+    _autocorrectionContextNeedsUpdate = YES;
     _didAccessoryTabInitiateFocus = _isChangingFocusUsingAccessoryTab;
 
     id <_WKInputDelegate> inputDelegate = [_webView _inputDelegate];
@@ -6582,6 +6624,7 @@
     _focusedElementInformation.isFocusingWithValidationMessage = false;
     _inputPeripheral = nil;
     _focusRequiresStrongPasswordAssistance = NO;
+    _autocorrectionContextNeedsUpdate = YES;
     _additionalContextForStrongPasswordAssistance = nil;
 
     [self resetShouldZoomToFocusRectAfterShowingKeyboard];
@@ -7197,6 +7240,7 @@
 
 - (void)_selectionChanged
 {
+    _autocorrectionContextNeedsUpdate = YES;
 #if ENABLE(APP_HIGHLIGHTS)
     [self setUpAppHighlightMenusIfNeeded];
 #endif

Modified: trunk/Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm (288924 => 288925)


--- trunk/Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm	2022-02-02 00:10:11 UTC (rev 288924)
+++ trunk/Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm	2022-02-02 00:23:52 UTC (rev 288925)
@@ -527,7 +527,7 @@
 
 void WebPageProxy::requestAutocorrectionContext()
 {
-    m_process->send(Messages::WebPage::RequestAutocorrectionContext(), m_webPageID);
+    m_process->send(Messages::WebPage::HandleAutocorrectionContextRequest(), m_webPageID);
 }
 
 void WebPageProxy::handleAutocorrectionContext(const WebAutocorrectionContext& context)

Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp (288924 => 288925)


--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp	2022-02-02 00:10:11 UTC (rev 288924)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp	2022-02-02 00:23:52 UTC (rev 288925)
@@ -7233,7 +7233,7 @@
     // requests on iOS are currently synchronous in the web process. This allows us to immediately fulfill pending
     // autocorrection context requests in the UI process on iOS before handling the DOM paste request. This workaround
     // should be removed once <rdar://problem/16207002> is resolved.
-    send(Messages::WebPageProxy::HandleAutocorrectionContext(autocorrectionContext()));
+    preemptivelySendAutocorrectionContext();
 #endif
     sendSyncWithDelayedReply(Messages::WebPageProxy::RequestDOMPasteAccess(pasteAccessCategory, rectForElementAtInteractionLocation(), originIdentifier), Messages::WebPageProxy::RequestDOMPasteAccess::Reply(response));
     return response;

Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.h (288924 => 288925)


--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.h	2022-02-02 00:10:11 UTC (rev 288924)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.h	2022-02-02 00:23:52 UTC (rev 288925)
@@ -802,7 +802,8 @@
     void requestAutocorrectionData(const String& textForAutocorrection, CompletionHandler<void(WebAutocorrectionData)>&& reply);
     void applyAutocorrection(const String& correction, const String& originalText, CompletionHandler<void(const String&)>&&);
     void syncApplyAutocorrection(const String& correction, const String& originalText, CompletionHandler<void(bool)>&&);
-    void requestAutocorrectionContext();
+    void handleAutocorrectionContextRequest();
+    void preemptivelySendAutocorrectionContext();
     void getPositionInformation(const InteractionInformationRequest&, CompletionHandler<void(InteractionInformationAtPosition&&)>&&);
     void requestPositionInformation(const InteractionInformationRequest&);
     void startInteractionWithElementContextOrPosition(std::optional<WebCore::ElementContext>&&, WebCore::IntPoint&&);

Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in (288924 => 288925)


--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in	2022-02-02 00:10:11 UTC (rev 288924)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in	2022-02-02 00:23:52 UTC (rev 288925)
@@ -86,7 +86,7 @@
     RequestAutocorrectionData(String textForAutocorrection) -> (struct WebKit::WebAutocorrectionData data) Async
     ApplyAutocorrection(String correction, String originalText) -> (String string) Async
     SyncApplyAutocorrection(String correction, String originalText) -> (bool autocorrectionApplied) Synchronous
-    RequestAutocorrectionContext()
+    HandleAutocorrectionContextRequest()
     RequestEvasionRectsAboveSelection() -> (Vector<WebCore::FloatRect> rects) Async
     GetPositionInformation(struct WebKit::InteractionInformationRequest request) -> (struct WebKit::InteractionInformationAtPosition information) Synchronous
     RequestPositionInformation(struct WebKit::InteractionInformationRequest request)

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


--- trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm	2022-02-02 00:10:11 UTC (rev 288924)
+++ trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm	2022-02-02 00:23:52 UTC (rev 288925)
@@ -1246,7 +1246,13 @@
 
 void WebPage::setIsShowingInputViewForFocusedElement(bool showingInputView)
 {
+    if (m_isShowingInputViewForFocusedElement == showingInputView)
+        return;
+
     m_isShowingInputViewForFocusedElement = showingInputView;
+
+    if (showingInputView)
+        preemptivelySendAutocorrectionContext();
 }
 
 void WebPage::setFocusedElementValue(const WebCore::ElementContext& context, const String& value)
@@ -2566,11 +2572,16 @@
     return correction;
 }
 
-void WebPage::requestAutocorrectionContext()
+void WebPage::preemptivelySendAutocorrectionContext()
 {
     send(Messages::WebPageProxy::HandleAutocorrectionContext(autocorrectionContext()));
 }
 
+void WebPage::handleAutocorrectionContextRequest()
+{
+    send(Messages::WebPageProxy::HandleAutocorrectionContext(autocorrectionContext()));
+}
+
 void WebPage::prepareToRunModalJavaScriptDialog()
 {
     if (!m_focusedElement)
@@ -2583,7 +2594,7 @@
     // 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()));
+    preemptivelySendAutocorrectionContext();
 }
 
 static HTMLAnchorElement* containingLinkAnchorElement(Element& element)

Modified: trunk/Tools/ChangeLog (288924 => 288925)


--- trunk/Tools/ChangeLog	2022-02-02 00:10:11 UTC (rev 288924)
+++ trunk/Tools/ChangeLog	2022-02-02 00:23:52 UTC (rev 288925)
@@ -1,3 +1,19 @@
+2022-02-01  Wenson Hsieh  <[email protected]>
+
+        Mitigate hangs underneath -requestAutocorrectionContextWithCompletionHandler: when focusing text fields
+        https://bugs.webkit.org/show_bug.cgi?id=235963
+        rdar://70152487
+
+        Reviewed by Tim Horton.
+
+        Add a new API test to exercise a scenario that currently isn't covered by existing tests: if the text input
+        client (i.e. UIKit) calls `-requestAutocorrectionContextWithCompletionHandler:`, then invokes an asynchronous
+        API that performs an editing action or changes the selection, and then requests another autocorrection context,
+        the second autocorrection context should reflect the changes that occurred as a result of the async editing
+        action.
+
+        * TestWebKitAPI/Tests/ios/AutocorrectionTestsIOS.mm:
+
 2022-02-01  Jonathan Bedard  <[email protected]>
 
         [EWS] Fix infinate retry on PR with red ToT 

Modified: trunk/Tools/TestWebKitAPI/Tests/ios/AutocorrectionTestsIOS.mm (288924 => 288925)


--- trunk/Tools/TestWebKitAPI/Tests/ios/AutocorrectionTestsIOS.mm	2022-02-02 00:10:11 UTC (rev 288924)
+++ trunk/Tools/TestWebKitAPI/Tests/ios/AutocorrectionTestsIOS.mm	2022-02-02 00:23:52 UTC (rev 288925)
@@ -141,4 +141,51 @@
     EXPECT_EQ(0U, [contextAfterTyping contextAfterSelection].length);
 }
 
+TEST(AutocorrectionTests, AutocorrectionContextBeforeAndAfterEditing)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] init]);
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) -> _WKFocusStartsInputSessionPolicy {
+        return _WKFocusStartsInputSessionPolicyAllow;
+    }];
+
+    [webView _setInputDelegate:inputDelegate.get()];
+    [webView synchronouslyLoadTestPageNamed:@"autofocused-text-input"];
+
+    __block RetainPtr<UIWKAutocorrectionContext> contextBeforeInsertingText;
+    __block RetainPtr<UIWKAutocorrectionContext> contextAfterInsertingText;
+    __block RetainPtr<UIWKAutocorrectionContext> contextAfterSelecting;
+
+    auto *contentView = [webView textInputContentView];
+    [contentView requestAutocorrectionContextWithCompletionHandler:^(UIWKAutocorrectionContext *context) {
+        contextBeforeInsertingText = context;
+    }];
+
+    [contentView insertText:@"hello"];
+    [contentView requestAutocorrectionContextWithCompletionHandler:^(UIWKAutocorrectionContext *context) {
+        contextAfterInsertingText = context;
+    }];
+
+    __block bool done = false;
+    [contentView selectAll:nil];
+    [contentView requestAutocorrectionContextWithCompletionHandler:^(UIWKAutocorrectionContext *context) {
+        contextAfterSelecting = context;
+        done = true;
+    }];
+
+    TestWebKitAPI::Util::run(&done);
+
+    EXPECT_EQ(0U, [contextBeforeInsertingText contextBeforeSelection].length);
+    EXPECT_EQ(0U, [contextBeforeInsertingText selectedText].length);
+    EXPECT_EQ(0U, [contextBeforeInsertingText contextAfterSelection].length);
+
+    EXPECT_WK_STREQ("hello", [contextAfterInsertingText contextBeforeSelection]);
+    EXPECT_EQ(0U, [contextAfterInsertingText selectedText].length);
+    EXPECT_EQ(0U, [contextAfterInsertingText contextAfterSelection].length);
+
+    EXPECT_EQ(0U, [contextAfterSelecting contextBeforeSelection].length);
+    EXPECT_WK_STREQ("hello", [contextAfterSelecting selectedText]);
+    EXPECT_EQ(0U, [contextAfterSelecting contextAfterSelection].length);
+}
+
 #endif // PLATFORM(IOS_FAMILY)
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to