- Revision
- 257487
- Author
- [email protected]
- Date
- 2020-02-26 11:19:20 -0800 (Wed, 26 Feb 2020)
Log Message
[iOS] Send focus update immediately on becoming or resigning first responder
https://bugs.webkit.org/show_bug.cgi?id=208082
<rdar://problem/59688413>
Rubber-stamped by Jer Noble.
Source/WebKit:
Send an activity state change immediately to the web process when the WKWebView becomes first
responder or resigns first responder.
Currently -becomeFirstResponderForWebView and -resignFirstResponderForWebView schedule an activity
state change message to be sent to the web process. This message will never be delivered within the
same event loop iteration. As a result, performing operations that requires the page to be focused
(e.g. selecting a position at a point) in the same event loop iteration as a call to -[WKWebView becomeFirstResponder])
will not work.
While I am in this area of the code simplify callers of activityStateDidChange() by changing its
parameter order such that it takes a dispatch mode before wantsSynchronousReply. The latter is
almost always defaulted to false to schedule an async message to the web process. Additionally,
changed wantsSynchronousReply from a boolean to an enumeration, ActivityStateChangeReplyMode, and
renamed the param to replyMode. The enumerators are self-documenting at the call site unlike the
boolean.
* UIProcess/Cocoa/WebViewImpl.mm:
(WebKit::WebViewImpl::prepareForMoveToWindow):
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::activityStateDidChange):
* UIProcess/WebPageProxy.h:
* UIProcess/ios/WKApplicationStateTrackingView.mm:
(-[WKApplicationStateTrackingView _applicationWillEnterForeground]):
Update code for new parameter ordering and pass a ActivityStateChangeReplyMode enumerator.
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView becomeFirstResponderForWebView]):
(-[WKContentView resignFirstResponderForWebView]):
Pass WebKit::WebPageProxy::ActivityStateChangeDispatchMode::Immediate to activityStateDidChange()
to dispatch the update immediately to the web process.
Tools:
Add a test to ensure that selecting a position at a point after the web view
becomes first responder is allowed. Such an operation is only allowed if the
web view is focused.
* TestWebKitAPI/Tests/ios/UIWKInteractionViewProtocol.mm:
(-[TestWKWebView selectPositionAtPoint:]):
(TEST):
* TestWebKitAPI/ios/UIKitSPI.h: Forward declare SPI.
Modified Paths
Diff
Modified: trunk/Source/WebKit/ChangeLog (257486 => 257487)
--- trunk/Source/WebKit/ChangeLog 2020-02-26 19:07:18 UTC (rev 257486)
+++ trunk/Source/WebKit/ChangeLog 2020-02-26 19:19:20 UTC (rev 257487)
@@ -1,3 +1,42 @@
+2020-02-26 Daniel Bates <[email protected]>
+
+ [iOS] Send focus update immediately on becoming or resigning first responder
+ https://bugs.webkit.org/show_bug.cgi?id=208082
+ <rdar://problem/59688413>
+
+ Rubber-stamped by Jer Noble.
+
+ Send an activity state change immediately to the web process when the WKWebView becomes first
+ responder or resigns first responder.
+
+ Currently -becomeFirstResponderForWebView and -resignFirstResponderForWebView schedule an activity
+ state change message to be sent to the web process. This message will never be delivered within the
+ same event loop iteration. As a result, performing operations that requires the page to be focused
+ (e.g. selecting a position at a point) in the same event loop iteration as a call to -[WKWebView becomeFirstResponder])
+ will not work.
+
+ While I am in this area of the code simplify callers of activityStateDidChange() by changing its
+ parameter order such that it takes a dispatch mode before wantsSynchronousReply. The latter is
+ almost always defaulted to false to schedule an async message to the web process. Additionally,
+ changed wantsSynchronousReply from a boolean to an enumeration, ActivityStateChangeReplyMode, and
+ renamed the param to replyMode. The enumerators are self-documenting at the call site unlike the
+ boolean.
+
+ * UIProcess/Cocoa/WebViewImpl.mm:
+ (WebKit::WebViewImpl::prepareForMoveToWindow):
+ * UIProcess/WebPageProxy.cpp:
+ (WebKit::WebPageProxy::activityStateDidChange):
+ * UIProcess/WebPageProxy.h:
+ * UIProcess/ios/WKApplicationStateTrackingView.mm:
+ (-[WKApplicationStateTrackingView _applicationWillEnterForeground]):
+ Update code for new parameter ordering and pass a ActivityStateChangeReplyMode enumerator.
+
+ * UIProcess/ios/WKContentViewInteraction.mm:
+ (-[WKContentView becomeFirstResponderForWebView]):
+ (-[WKContentView resignFirstResponderForWebView]):
+ Pass WebKit::WebPageProxy::ActivityStateChangeDispatchMode::Immediate to activityStateDidChange()
+ to dispatch the update immediately to the web process.
+
2020-02-26 Chris Dumez <[email protected]>
Unreviewed, rolling out r257471.
Modified: trunk/Source/WebKit/UIProcess/Cocoa/WebViewImpl.mm (257486 => 257487)
--- trunk/Source/WebKit/UIProcess/Cocoa/WebViewImpl.mm 2020-02-26 19:07:18 UTC (rev 257486)
+++ trunk/Source/WebKit/UIProcess/Cocoa/WebViewImpl.mm 2020-02-26 19:19:20 UTC (rev 257487)
@@ -2432,7 +2432,7 @@
});
dispatchSetTopContentInset();
- m_page->activityStateDidChange(WebCore::ActivityState::IsInWindow, false, WebPageProxy::ActivityStateChangeDispatchMode::Immediate);
+ m_page->activityStateDidChange(WebCore::ActivityState::IsInWindow, WebPageProxy::ActivityStateChangeDispatchMode::Immediate);
m_viewInWindowChangeWasDeferred = false;
}
Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.cpp (257486 => 257487)
--- trunk/Source/WebKit/UIProcess/WebPageProxy.cpp 2020-02-26 19:07:18 UTC (rev 257486)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.cpp 2020-02-26 19:19:20 UTC (rev 257487)
@@ -1887,12 +1887,12 @@
m_activityState.add(ActivityState::IsCapturingMedia);
}
-void WebPageProxy::activityStateDidChange(OptionSet<ActivityState::Flag> mayHaveChanged, bool wantsSynchronousReply, ActivityStateChangeDispatchMode dispatchMode)
+void WebPageProxy::activityStateDidChange(OptionSet<ActivityState::Flag> mayHaveChanged, ActivityStateChangeDispatchMode dispatchMode, ActivityStateChangeReplyMode replyMode)
{
LOG_WITH_STREAM(ActivityState, stream << "WebPageProxy " << identifier() << " activityStateDidChange - mayHaveChanged " << mayHaveChanged);
m_potentiallyChangedActivityStateFlags.add(mayHaveChanged);
- m_activityStateChangeWantsSynchronousReply = m_activityStateChangeWantsSynchronousReply || wantsSynchronousReply;
+ m_activityStateChangeWantsSynchronousReply = m_activityStateChangeWantsSynchronousReply || replyMode == ActivityStateChangeReplyMode::Synchronous;
if (m_suppressVisibilityUpdates && dispatchMode != ActivityStateChangeDispatchMode::Immediate)
return;
Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.h (257486 => 257487)
--- trunk/Source/WebKit/UIProcess/WebPageProxy.h 2020-02-26 19:07:18 UTC (rev 257486)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.h 2020-02-26 19:19:20 UTC (rev 257487)
@@ -646,8 +646,9 @@
void setDelegatesScrolling(bool delegatesScrolling) { m_delegatesScrolling = delegatesScrolling; }
bool delegatesScrolling() const { return m_delegatesScrolling; }
- enum class ActivityStateChangeDispatchMode { Deferrable, Immediate };
- void activityStateDidChange(OptionSet<WebCore::ActivityState::Flag> mayHaveChanged, bool wantsSynchronousReply = false, ActivityStateChangeDispatchMode = ActivityStateChangeDispatchMode::Deferrable);
+ enum class ActivityStateChangeDispatchMode : bool { Deferrable, Immediate };
+ enum class ActivityStateChangeReplyMode : bool { Asynchronous, Synchronous };
+ void activityStateDidChange(OptionSet<WebCore::ActivityState::Flag> mayHaveChanged, ActivityStateChangeDispatchMode = ActivityStateChangeDispatchMode::Deferrable, ActivityStateChangeReplyMode = ActivityStateChangeReplyMode::Asynchronous);
bool isInWindow() const { return m_activityState.contains(WebCore::ActivityState::IsInWindow); }
void waitForDidUpdateActivityState(ActivityStateChangeID);
void didUpdateActivityState() { m_waitingForDidUpdateActivityState = false; }
Modified: trunk/Source/WebKit/UIProcess/ios/WKApplicationStateTrackingView.mm (257486 => 257487)
--- trunk/Source/WebKit/UIProcess/ios/WKApplicationStateTrackingView.mm 2020-02-26 19:07:18 UTC (rev 257486)
+++ trunk/Source/WebKit/UIProcess/ios/WKApplicationStateTrackingView.mm 2020-02-26 19:19:20 UTC (rev 257487)
@@ -100,7 +100,7 @@
return;
page->applicationWillEnterForeground();
- page->activityStateDidChange(WebCore::ActivityState::allFlags() - WebCore::ActivityState::IsInWindow, true, WebKit::WebPageProxy::ActivityStateChangeDispatchMode::Immediate);
+ page->activityStateDidChange(WebCore::ActivityState::allFlags() - WebCore::ActivityState::IsInWindow, WebKit::WebPageProxy::ActivityStateChangeDispatchMode::Immediate, WebKit::WebPageProxy::ActivityStateChangeReplyMode::Synchronous);
}
- (BOOL)isBackground
Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm (257486 => 257487)
--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm 2020-02-26 19:07:18 UTC (rev 257486)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm 2020-02-26 19:19:20 UTC (rev 257487)
@@ -1343,7 +1343,7 @@
[strongSelf _resetInputViewDeferral];
});
- _page->activityStateDidChange(WebCore::ActivityState::IsFocused);
+ _page->activityStateDidChange(WebCore::ActivityState::IsFocused, WebKit::WebPageProxy::ActivityStateChangeDispatchMode::Immediate);
if ([self canShowNonEmptySelectionView])
[_textInteractionAssistant activateSelection];
@@ -1404,7 +1404,7 @@
if (superDidResign) {
[self _handleDOMPasteRequestWithResult:WebCore::DOMPasteAccessResponse::DeniedForGesture];
- _page->activityStateDidChange(WebCore::ActivityState::IsFocused);
+ _page->activityStateDidChange(WebCore::ActivityState::IsFocused, WebKit::WebPageProxy::ActivityStateChangeDispatchMode::Immediate);
if (_keyWebEventHandler) {
dispatch_async(dispatch_get_main_queue(), [weakHandler = WeakObjCPtr<id>(_keyWebEventHandler.get()), weakSelf = WeakObjCPtr<WKContentView>(self)] {
Modified: trunk/Tools/ChangeLog (257486 => 257487)
--- trunk/Tools/ChangeLog 2020-02-26 19:07:18 UTC (rev 257486)
+++ trunk/Tools/ChangeLog 2020-02-26 19:19:20 UTC (rev 257487)
@@ -1,3 +1,20 @@
+2020-02-26 Daniel Bates <[email protected]>
+
+ [iOS] Send focus update immediately on becoming or resigning first responder
+ https://bugs.webkit.org/show_bug.cgi?id=208082
+ <rdar://problem/59688413>
+
+ Rubber-stamped by Jer Noble.
+
+ Add a test to ensure that selecting a position at a point after the web view
+ becomes first responder is allowed. Such an operation is only allowed if the
+ web view is focused.
+
+ * TestWebKitAPI/Tests/ios/UIWKInteractionViewProtocol.mm:
+ (-[TestWKWebView selectPositionAtPoint:]):
+ (TEST):
+ * TestWebKitAPI/ios/UIKitSPI.h: Forward declare SPI.
+
2020-02-26 Chris Dumez <[email protected]>
Unreviewed, disable new DragAndDropTests.DragAndDropOnEmptyView API test on iOS.
Modified: trunk/Tools/TestWebKitAPI/Tests/ios/UIWKInteractionViewProtocol.mm (257486 => 257487)
--- trunk/Tools/TestWebKitAPI/Tests/ios/UIWKInteractionViewProtocol.mm 2020-02-26 19:07:18 UTC (rev 257486)
+++ trunk/Tools/TestWebKitAPI/Tests/ios/UIWKInteractionViewProtocol.mm 2020-02-26 19:19:20 UTC (rev 257487)
@@ -28,11 +28,15 @@
#if PLATFORM(IOS_FAMILY)
#import "PlatformUtilities.h"
+#import "TestInputDelegate.h"
#import "TestWKWebView.h"
#import "UIKitSPI.h"
+#import <WebKit/WKWebViewPrivate.h>
+#import <WebKit/WKWebViewPrivateForTesting.h>
#import <wtf/RetainPtr.h>
@interface TestWKWebView (UIWKInteractionViewTesting)
+- (void)selectPositionAtPoint:(CGPoint)point;
- (void)selectTextWithGranularity:(UITextGranularity)granularity atPoint:(CGPoint)point;
- (void)updateSelectionWithExtentPoint:(CGPoint)point;
- (void)updateSelectionWithExtentPoint:(CGPoint)point withBoundary:(UITextGranularity)granularity;
@@ -67,6 +71,15 @@
TestWebKitAPI::Util::run(&done);
}
+- (void)selectPositionAtPoint:(CGPoint)point
+{
+ __block bool done = false;
+ [self.textInputContentView selectPositionAtPoint:point completionHandler:^{
+ done = true;
+ }];
+ TestWebKitAPI::Util::run(&done);
+}
+
@end
TEST(UIWKInteractionViewProtocol, SelectTextWithCharacterGranularity)
@@ -92,4 +105,29 @@
EXPECT_WK_STREQ("Hello world", [webView stringByEvaluatingJavaScript:@"getSelection().toString()"]);
}
+TEST(UIWKInteractionViewProtocol, SelectPositionAtPointAfterBecomingFirstResponder)
+{
+ auto inputDelegate = adoptNS([TestInputDelegate new]);
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 400, 400)]);
+ [webView _setInputDelegate:inputDelegate.get()];
+ [inputDelegate setFocusStartsInputSessionPolicyHandler:[&] (WKWebView *, id <_WKFocusedElementInfo>) -> _WKFocusStartsInputSessionPolicy {
+ return _WKFocusStartsInputSessionPolicyAllow;
+ }];
+ // 1. Ensure that the WKWebView is not first responder.
+ [webView synchronouslyLoadHTMLString:@"<body style='margin: 0; padding: 0'><div contenteditable='true' style='height: 200px; width: 200px'></div></body>"];
+ [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.querySelector('div').focus()"];
+
+ // We explicitly dismiss the form accessory view to ensure that a DOM blur event is dispatched
+ // regardless of the test device used as -resignFirstResponder may not do this (e.g. it does
+ // not do this on iPad when there is a hardware keyboard attached).
+ [webView dismissFormAccessoryView];
+ [webView resignFirstResponder];
+ EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+
+ // 2. Make it first responder and perform selection.
+ [webView becomeFirstResponder];
+ [webView selectPositionAtPoint:CGPointMake(8, 8)];
+ EXPECT_WK_STREQ("DIV", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+}
+
#endif
Modified: trunk/Tools/TestWebKitAPI/ios/UIKitSPI.h (257486 => 257487)
--- trunk/Tools/TestWebKitAPI/ios/UIKitSPI.h 2020-02-26 19:07:18 UTC (rev 257486)
+++ trunk/Tools/TestWebKitAPI/ios/UIKitSPI.h 2020-02-26 19:19:20 UTC (rev 257487)
@@ -174,6 +174,7 @@
- (void)requestAutocorrectionRectsForString:(NSString *)input withCompletionHandler:(void (^)(UIWKAutocorrectionRects *rectsForInput))completionHandler;
- (void)requestAutocorrectionContextWithCompletionHandler:(void (^)(UIWKAutocorrectionContext *autocorrectionContext))completionHandler;
- (void)selectWordBackward;
+- (void)selectPositionAtPoint:(CGPoint)point completionHandler:(void (^)(void))completionHandler;
- (void)selectTextWithGranularity:(UITextGranularity)granularity atPoint:(CGPoint)point completionHandler:(void (^)(void))completionHandler;
- (void)updateSelectionWithExtentPoint:(CGPoint)point completionHandler:(void (^)(BOOL selectionEndIsMoving))completionHandler;
- (void)updateSelectionWithExtentPoint:(CGPoint)point withBoundary:(UITextGranularity)granularity completionHandler:(void (^)(BOOL selectionEndIsMoving))completionHandler;