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.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