Title: [257487] trunk
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;
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to