Title: [234600] trunk
Revision
234600
Author
wenson_hs...@apple.com
Date
2018-08-06 07:14:42 -0700 (Mon, 06 Aug 2018)

Log Message

[iOS] Caret disappears after resigning and becoming first responder if active focus state is retained
https://bugs.webkit.org/show_bug.cgi?id=188322
<rdar://problem/42455270>

Reviewed by Tim Horton.

Source/WebKit:

Prior to r230745, when a user selects a word in non-editable web content without a prior selection, we would
always try to activate the text interaction assistant, creating a selection view (a UITextSelectionView). After
the long press is recognized, this text selection view is configured for "highlight mode", which is a special
mode for presenting selection UI where the grabber handles at the start and end of the selection are suppressed.
UIKit then prepares to show the selection by asking WKContentView for the number of selection rects; if this
number is zero, the UITextSelectionView is removed from the superview, and state that keeps track of whether the
selection view is in "highlight mode" is reset.

In the case where there's no prior selection, our cached EditorState in the UI process will not be up to date
yet when the gesture is recognized. This means that when UIKit asks us for the number of selection rects, we'll
return 0, which causes any state tracking "highlight mode" for the selection to be reset, subsequently resulting
in selection handles showing up before the user has ended the initial loupe gesture.

r230745 addressed this bug by removing logic to activate the text selection when becoming first responder,
instead deferring until the next `-_selectionChanged` call with post-layout editor state data to activate the
selection. While this does ensure that selection handles don't erroneously appear, it also means that clients
that call -becomeFirstResponder to show selection UI and the keyboard in a web view while an element is already
focused will not have an active selection assistant (i.e. the selection view will still be hidden). One way this
happens is when Safari uses `-_retainActiveFocusedState` in combination with `-resignFirstResponder` and
`-becomeFirstResponder` to temporarily switch focus away from the web view when the URL bar is tapped.

To fix both the inactive selection after `-becomeFirstResponder` as well as the selection handles showing up
when performing a loupe gesture, we simply make the check in `-becomeFirstResponderForWebView` more nuanced.
Instead of always activating the selection or never activating the selection, only activate the selection if the
current editor state has information about a selection to avoid causing the selection view to be immediately
removed and "highlight mode" to be reset when selecting a word via loupe gesture for the first time.

Tests:  KeyboardInputTests.CaretSelectionRectAfterRestoringFirstResponder
        KeyboardInputTests.RangedSelectionRectAfterRestoringFirstResponder
        editing/selection/ios/selection-handles-after-touch-end.html

* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView becomeFirstResponderForWebView]):
(-[WKContentView canShowNonEmptySelectionView]):

Tools:

Adds plumbing in UIScriptController to grab the start and end selection handle rects for use in the new layout
test. Also adds new API tests to verify that when a web view resigns first responder, both caret and range
selection views are hidden, and when first responder status is restored, both caret and range selection views
are made visible again.

* DumpRenderTree/ios/UIScriptControllerIOS.mm:
(WTR::UIScriptController::selectionStartGrabberViewRect const):
(WTR::UIScriptController::selectionEndGrabberViewRect const):
* TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
* TestRunnerShared/UIScriptContext/UIScriptController.cpp:
(WTR::UIScriptController::selectionStartGrabberViewRect const):
(WTR::UIScriptController::selectionEndGrabberViewRect const):
* TestRunnerShared/UIScriptContext/UIScriptController.h:
* TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm:
(-[TestWKWebView waitForCaretViewFrameToBecome:]):
(-[TestWKWebView waitForSelectionViewRectsToBecome:]):
(webViewWithAutofocusedInput):

Pull out some common logic for creating a web view that allows programmatic focus to present the keyboard, and
immediately loading a web page with an autofocusing text field.

(TestWebKitAPI::TEST):
* TestWebKitAPI/cocoa/TestWKWebView.h:
* TestWebKitAPI/cocoa/TestWKWebView.mm:
(-[TestWKWebView caretViewRectInContentCoordinates]):
(-[TestWKWebView selectionViewRectsInContentCoordinates]):
* WebKitTestRunner/ios/UIScriptControllerIOS.mm:
(WTR::UIScriptController::selectionStartGrabberViewRect const):
(WTR::UIScriptController::selectionEndGrabberViewRect const):

LayoutTests:

Adds a new layout test to verify that (1) selection handles are not shown when selecting a word by long
pressing prior to ending the touch, and (2) selection handles are shown after ending the touch.

* editing/selection/ios/selection-handles-after-touch-end-expected.txt: Added.
* editing/selection/ios/selection-handles-after-touch-end.html: Added.
* platform/win/TestExpectations:

Skip iOS selection tests on Windows.

* resources/ui-helper.js:

Introduces new hooks in UIHelper to grab the frames of the start and end selection handle views.

(window.UIHelper.getSelectionStartGrabberViewRect.return.new.Promise.):
(window.UIHelper.getSelectionStartGrabberViewRect.return.new.Promise):
(window.UIHelper.getSelectionStartGrabberViewRect):
(window.UIHelper.getSelectionEndGrabberViewRect.return.new.Promise.):
(window.UIHelper.getSelectionEndGrabberViewRect.return.new.Promise):
(window.UIHelper.getSelectionEndGrabberViewRect):

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (234599 => 234600)


--- trunk/LayoutTests/ChangeLog	2018-08-06 13:59:04 UTC (rev 234599)
+++ trunk/LayoutTests/ChangeLog	2018-08-06 14:14:42 UTC (rev 234600)
@@ -1,3 +1,31 @@
+2018-08-06  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        [iOS] Caret disappears after resigning and becoming first responder if active focus state is retained
+        https://bugs.webkit.org/show_bug.cgi?id=188322
+        <rdar://problem/42455270>
+
+        Reviewed by Tim Horton.
+
+        Adds a new layout test to verify that (1) selection handles are not shown when selecting a word by long
+        pressing prior to ending the touch, and (2) selection handles are shown after ending the touch.
+
+        * editing/selection/ios/selection-handles-after-touch-end-expected.txt: Added.
+        * editing/selection/ios/selection-handles-after-touch-end.html: Added.
+        * platform/win/TestExpectations:
+
+        Skip iOS selection tests on Windows.
+
+        * resources/ui-helper.js:
+
+        Introduces new hooks in UIHelper to grab the frames of the start and end selection handle views.
+
+        (window.UIHelper.getSelectionStartGrabberViewRect.return.new.Promise.):
+        (window.UIHelper.getSelectionStartGrabberViewRect.return.new.Promise):
+        (window.UIHelper.getSelectionStartGrabberViewRect):
+        (window.UIHelper.getSelectionEndGrabberViewRect.return.new.Promise.):
+        (window.UIHelper.getSelectionEndGrabberViewRect.return.new.Promise):
+        (window.UIHelper.getSelectionEndGrabberViewRect):
+
 2018-08-06  Claudio Saavedra  <csaave...@igalia.com>
 
         [WPE] New webgl 2.0 failures.

Added: trunk/LayoutTests/editing/selection/ios/selection-handles-after-touch-end-expected.txt (0 => 234600)


--- trunk/LayoutTests/editing/selection/ios/selection-handles-after-touch-end-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/selection/ios/selection-handles-after-touch-end-expected.txt	2018-08-06 14:14:42 UTC (rev 234600)
@@ -0,0 +1,11 @@
+WK
+
+Selection rects before touchend:
+{ left = 0, top = 0, width = 250, height = 170 }
+Selection start handle before touchend: { left = 0, top = 0, width = 0, height = 0 }
+Selection end handle before touchend: { left = 0, top = 0, width = 0, height = 0 }
+
+Selection rects after touchend:
+{ left = 0, top = 0, width = 250, height = 170 }
+Selection start handle after touchend: { left = 0, top = 0, width = 2, height = 170 }
+Selection end handle after touchend: { left = 249, top = 0, width = 2, height = 170 }

Added: trunk/LayoutTests/editing/selection/ios/selection-handles-after-touch-end.html (0 => 234600)


--- trunk/LayoutTests/editing/selection/ios/selection-handles-after-touch-end.html	                        (rev 0)
+++ trunk/LayoutTests/editing/selection/ios/selection-handles-after-touch-end.html	2018-08-06 14:14:42 UTC (rev 234600)
@@ -0,0 +1,75 @@
+<html>
+<head>
+<script src=""
+<meta name=viewport content="width=device-width">
+<style>
+    body, html {
+        width: 100%;
+        height: 100%;
+        margin: 0;
+    }
+
+    #target {
+        font-size: 150px;
+    }
+</style>
+</head>
+
+<body>
+<p id="target">WK</p>
+<pre id="output"></pre>
+<script>
+if (window.testRunner) {
+    testRunner.dumpAsText();
+    testRunner.waitUntilDone();
+}
+
+function appendOutput(s)
+{
+    const paragraph = document.createElement("div");
+    paragraph.textContent = s;
+    output.appendChild(paragraph);
+}
+
+function toString(rect)
+{
+    return `{ left = ${Math.round(rect.left)}, top = ${Math.round(rect.top)}, width = ${Math.round(rect.width)}, height = ${Math.round(rect.height)} }`;
+}
+
+function checkDone() {
+    if (!window.doneCount)
+        doneCount = 0;
+    if (++doneCount == 3)
+        testRunner.notifyDone();
+}
+
+document.addEventListener("selectionchange", async () => {
+    if (!getSelection().rangeCount || getSelection().getRangeAt(0).collapsed)
+        return;
+
+    appendOutput("Selection rects before touchend:");
+    (await UIHelper.getUISelectionRects()).map(toString).map(appendOutput);
+    appendOutput(`Selection start handle before touchend: ${toString(await UIHelper.getSelectionStartGrabberViewRect())}`);
+    appendOutput(`Selection end handle before touchend: ${toString(await UIHelper.getSelectionEndGrabberViewRect())}`);
+    appendOutput("\n");
+    testRunner.runUIScript("uiController.liftUpAtPoint(100, 75, 1, () => uiController.uiScriptComplete(''));", checkDone);
+});
+
+document.addEventListener("touchend", async () => {
+    appendOutput("Selection rects after touchend:");
+    (await UIHelper.getUISelectionRects()).map(toString).map(appendOutput);
+    appendOutput(`Selection start handle after touchend: ${toString(await UIHelper.getSelectionStartGrabberViewRect())}`);
+    appendOutput(`Selection end handle after touchend: ${toString(await UIHelper.getSelectionEndGrabberViewRect())}`);
+    checkDone();
+});
+
+if (window.testRunner && testRunner.runUIScript)
+    testRunner.runUIScript("uiController.touchDownAtPoint(100, 75, 1, () => uiController.uiScriptComplete(''));", checkDone);
+else {
+    appendOutput(`This test verifies that selection handles are not present when selecting a word via long press,
+        before ending the long press. To manually test, long press the word "WK" until a selection highlight is shown,
+        and verify that there are no selection handles. Upon release, selection handles should appear.`);
+}
+</script>
+</body>
+</html>
\ No newline at end of file

Modified: trunk/LayoutTests/platform/win/TestExpectations (234599 => 234600)


--- trunk/LayoutTests/platform/win/TestExpectations	2018-08-06 13:59:04 UTC (rev 234599)
+++ trunk/LayoutTests/platform/win/TestExpectations	2018-08-06 14:14:42 UTC (rev 234600)
@@ -3434,8 +3434,7 @@
 editing/deleting/delete-emoji.html [ Skip ]
 editing/pasteboard/paste-image-as-blob-url.html [ Skip ]
 editing/selection/character-granularity-rect.html [ Skip ]
-editing/selection/ios/absolute-selection-after-scroll.html [ Skip ]
-editing/selection/ios/fixed-selection-after-scroll.html [ Skip ]
+editing/selection/ios [ Skip ]
 fast/canvas/canvas-createPattern-video-loading.html [ Skip ]
 fast/canvas/canvas-createPattern-video-modify.html [ Skip ]
 fast/events/before-input-prevent-insert-replacement.html [ Skip ]

Modified: trunk/LayoutTests/resources/ui-helper.js (234599 => 234600)


--- trunk/LayoutTests/resources/ui-helper.js	2018-08-06 13:59:04 UTC (rev 234599)
+++ trunk/LayoutTests/resources/ui-helper.js	2018-08-06 14:14:42 UTC (rev 234600)
@@ -153,6 +153,38 @@
         });
     }
 
+    static getSelectionStartGrabberViewRect()
+    {
+        if (!this.isWebKit2() || !this.isIOS())
+            return Promise.resolve();
+
+        return new Promise(resolve => {
+            testRunner.runUIScript(`(function() {
+                uiController.doAfterNextStablePresentationUpdate(function() {
+                    uiController.uiScriptComplete(JSON.stringify(uiController.selectionStartGrabberViewRect));
+                });
+            })()`, jsonString => {
+                resolve(JSON.parse(jsonString));
+            });
+        });
+    }
+
+    static getSelectionEndGrabberViewRect()
+    {
+        if (!this.isWebKit2() || !this.isIOS())
+            return Promise.resolve();
+
+        return new Promise(resolve => {
+            testRunner.runUIScript(`(function() {
+                uiController.doAfterNextStablePresentationUpdate(function() {
+                    uiController.uiScriptComplete(JSON.stringify(uiController.selectionEndGrabberViewRect));
+                });
+            })()`, jsonString => {
+                resolve(JSON.parse(jsonString));
+            });
+        });
+    }
+
     static replaceTextAtRange(text, location, length) {
         return new Promise(resolve => {
             testRunner.runUIScript(`(() => {

Modified: trunk/Source/WebKit/ChangeLog (234599 => 234600)


--- trunk/Source/WebKit/ChangeLog	2018-08-06 13:59:04 UTC (rev 234599)
+++ trunk/Source/WebKit/ChangeLog	2018-08-06 14:14:42 UTC (rev 234600)
@@ -1,3 +1,46 @@
+2018-08-06  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        [iOS] Caret disappears after resigning and becoming first responder if active focus state is retained
+        https://bugs.webkit.org/show_bug.cgi?id=188322
+        <rdar://problem/42455270>
+
+        Reviewed by Tim Horton.
+
+        Prior to r230745, when a user selects a word in non-editable web content without a prior selection, we would
+        always try to activate the text interaction assistant, creating a selection view (a UITextSelectionView). After
+        the long press is recognized, this text selection view is configured for "highlight mode", which is a special
+        mode for presenting selection UI where the grabber handles at the start and end of the selection are suppressed.
+        UIKit then prepares to show the selection by asking WKContentView for the number of selection rects; if this
+        number is zero, the UITextSelectionView is removed from the superview, and state that keeps track of whether the
+        selection view is in "highlight mode" is reset.
+
+        In the case where there's no prior selection, our cached EditorState in the UI process will not be up to date
+        yet when the gesture is recognized. This means that when UIKit asks us for the number of selection rects, we'll
+        return 0, which causes any state tracking "highlight mode" for the selection to be reset, subsequently resulting
+        in selection handles showing up before the user has ended the initial loupe gesture.
+
+        r230745 addressed this bug by removing logic to activate the text selection when becoming first responder,
+        instead deferring until the next `-_selectionChanged` call with post-layout editor state data to activate the
+        selection. While this does ensure that selection handles don't erroneously appear, it also means that clients
+        that call -becomeFirstResponder to show selection UI and the keyboard in a web view while an element is already
+        focused will not have an active selection assistant (i.e. the selection view will still be hidden). One way this
+        happens is when Safari uses `-_retainActiveFocusedState` in combination with `-resignFirstResponder` and
+        `-becomeFirstResponder` to temporarily switch focus away from the web view when the URL bar is tapped.
+
+        To fix both the inactive selection after `-becomeFirstResponder` as well as the selection handles showing up
+        when performing a loupe gesture, we simply make the check in `-becomeFirstResponderForWebView` more nuanced.
+        Instead of always activating the selection or never activating the selection, only activate the selection if the
+        current editor state has information about a selection to avoid causing the selection view to be immediately
+        removed and "highlight mode" to be reset when selecting a word via loupe gesture for the first time.
+
+        Tests:  KeyboardInputTests.CaretSelectionRectAfterRestoringFirstResponder
+                KeyboardInputTests.RangedSelectionRectAfterRestoringFirstResponder
+                editing/selection/ios/selection-handles-after-touch-end.html
+
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView becomeFirstResponderForWebView]):
+        (-[WKContentView canShowNonEmptySelectionView]):
+
 2018-08-06  Zan Dobersek  <zdober...@igalia.com>
 
         [Nicosia] Add Nicosia::Scene

Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm (234599 => 234600)


--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm	2018-08-06 13:59:04 UTC (rev 234599)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm	2018-08-06 14:14:42 UTC (rev 234600)
@@ -974,6 +974,9 @@
         didBecomeFirstResponder = [super becomeFirstResponder];
     }
 
+    if (didBecomeFirstResponder && [self canShowNonEmptySelectionView])
+        [_textSelectionAssistant activateSelection];
+
     return didBecomeFirstResponder;
 }
 
@@ -1676,6 +1679,15 @@
                      }];
 }
 
+- (BOOL)canShowNonEmptySelectionView
+{
+    if (self.suppressAssistantSelectionView)
+        return NO;
+
+    auto& state = _page->editorState();
+    return !state.isMissingPostLayoutData && !state.selectionIsNone;
+}
+
 - (BOOL)hasSelectablePositionAtPoint:(CGPoint)point
 {
     if (!_webView.configuration._textInteractionGesturesEnabled)

Modified: trunk/Tools/ChangeLog (234599 => 234600)


--- trunk/Tools/ChangeLog	2018-08-06 13:59:04 UTC (rev 234599)
+++ trunk/Tools/ChangeLog	2018-08-06 14:14:42 UTC (rev 234600)
@@ -1,5 +1,43 @@
 2018-08-06  Wenson Hsieh  <wenson_hs...@apple.com>
 
+        [iOS] Caret disappears after resigning and becoming first responder if active focus state is retained
+        https://bugs.webkit.org/show_bug.cgi?id=188322
+        <rdar://problem/42455270>
+
+        Reviewed by Tim Horton.
+
+        Adds plumbing in UIScriptController to grab the start and end selection handle rects for use in the new layout
+        test. Also adds new API tests to verify that when a web view resigns first responder, both caret and range
+        selection views are hidden, and when first responder status is restored, both caret and range selection views
+        are made visible again.
+
+        * DumpRenderTree/ios/UIScriptControllerIOS.mm:
+        (WTR::UIScriptController::selectionStartGrabberViewRect const):
+        (WTR::UIScriptController::selectionEndGrabberViewRect const):
+        * TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
+        * TestRunnerShared/UIScriptContext/UIScriptController.cpp:
+        (WTR::UIScriptController::selectionStartGrabberViewRect const):
+        (WTR::UIScriptController::selectionEndGrabberViewRect const):
+        * TestRunnerShared/UIScriptContext/UIScriptController.h:
+        * TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm:
+        (-[TestWKWebView waitForCaretViewFrameToBecome:]):
+        (-[TestWKWebView waitForSelectionViewRectsToBecome:]):
+        (webViewWithAutofocusedInput):
+
+        Pull out some common logic for creating a web view that allows programmatic focus to present the keyboard, and
+        immediately loading a web page with an autofocusing text field.
+
+        (TestWebKitAPI::TEST):
+        * TestWebKitAPI/cocoa/TestWKWebView.h:
+        * TestWebKitAPI/cocoa/TestWKWebView.mm:
+        (-[TestWKWebView caretViewRectInContentCoordinates]):
+        (-[TestWKWebView selectionViewRectsInContentCoordinates]):
+        * WebKitTestRunner/ios/UIScriptControllerIOS.mm:
+        (WTR::UIScriptController::selectionStartGrabberViewRect const):
+        (WTR::UIScriptController::selectionEndGrabberViewRect const):
+
+2018-08-06  Wenson Hsieh  <wenson_hs...@apple.com>
+
         [iOS] Layout tests that send HID events cause WebKitTestRunner to crash on recent SDKs
         https://bugs.webkit.org/show_bug.cgi?id=188334
         <rdar://problem/40630074>

Modified: trunk/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm (234599 => 234600)


--- trunk/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm	2018-08-06 13:59:04 UTC (rev 234599)
+++ trunk/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm	2018-08-06 14:14:42 UTC (rev 234600)
@@ -366,6 +366,16 @@
 {
 }
 
+JSObjectRef UIScriptController::selectionStartGrabberViewRect() const
+{
+    return nullptr;
 }
 
+JSObjectRef UIScriptController::selectionEndGrabberViewRect() const
+{
+    return nullptr;
+}
+
+}
+
 #endif // PLATFORM(IOS)

Modified: trunk/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl (234599 => 234600)


--- trunk/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl	2018-08-06 13:59:04 UTC (rev 234599)
+++ trunk/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl	2018-08-06 14:14:42 UTC (rev 234600)
@@ -234,6 +234,9 @@
 
     readonly attribute object selectionRangeViewRects; // An array of objects with 'left', 'top', 'width', and 'height' properties.
     readonly attribute object textSelectionCaretRect; // An object with 'left', 'top', 'width', 'height' properties.
+    readonly attribute object selectionStartGrabberViewRect;
+    readonly attribute object selectionEndGrabberViewRect;
+
     readonly attribute object inputViewBounds;
 
     void replaceTextAtRange(DOMString text, long location, long length);

Modified: trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp (234599 => 234600)


--- trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp	2018-08-06 13:59:04 UTC (rev 234599)
+++ trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp	2018-08-06 14:14:42 UTC (rev 234600)
@@ -384,6 +384,16 @@
     return nullptr;
 }
 
+JSObjectRef UIScriptController::selectionStartGrabberViewRect() const
+{
+    return nullptr;
+}
+
+JSObjectRef UIScriptController::selectionEndGrabberViewRect() const
+{
+    return nullptr;
+}
+
 JSObjectRef UIScriptController::inputViewBounds() const
 {
     return nullptr;

Modified: trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h (234599 => 234600)


--- trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h	2018-08-06 13:59:04 UTC (rev 234599)
+++ trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h	2018-08-06 14:14:42 UTC (rev 234600)
@@ -156,6 +156,8 @@
     
     JSObjectRef selectionRangeViewRects() const;
     JSObjectRef textSelectionCaretRect() const;
+    JSObjectRef selectionStartGrabberViewRect() const;
+    JSObjectRef selectionEndGrabberViewRect() const;
     JSObjectRef inputViewBounds() const;
 
     void replaceTextAtRange(JSStringRef, int location, int length);

Modified: trunk/Tools/TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm (234599 => 234600)


--- trunk/Tools/TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm	2018-08-06 13:59:04 UTC (rev 234599)
+++ trunk/Tools/TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm	2018-08-06 14:14:42 UTC (rev 234600)
@@ -34,10 +34,55 @@
 #import <WebKit/WKWebViewPrivate.h>
 #import <WebKitLegacy/WebEvent.h>
 
-namespace TestWebKitAPI {
+@implementation TestWKWebView (KeyboardInputTests)
 
-TEST(KeyboardInputTests, CanHandleKeyEventInCompletionHandler)
+- (void)waitForCaretViewFrameToBecome:(CGRect)frame
 {
+    BOOL hasEmittedWarning = NO;
+    NSTimeInterval secondsToWaitUntilWarning = 2;
+    NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
+    while ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]) {
+        CGRect currentFrame = self.caretViewRectInContentCoordinates;
+        if (CGRectEqualToRect(currentFrame, frame))
+            break;
+
+        if (hasEmittedWarning || startTime + secondsToWaitUntilWarning >= [NSDate timeIntervalSinceReferenceDate])
+            continue;
+
+        NSLog(@"Expected a caret rect of %@, but still observed %@", NSStringFromCGRect(frame), NSStringFromCGRect(currentFrame));
+        hasEmittedWarning = YES;
+    }
+}
+
+- (void)waitForSelectionViewRectsToBecome:(NSArray<NSValue *> *)selectionRects
+{
+    BOOL hasEmittedWarning = NO;
+    NSTimeInterval secondsToWaitUntilWarning = 2;
+    NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
+    while ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]) {
+        NSArray<NSValue *> *currentRects = self.selectionViewRectsInContentCoordinates;
+        BOOL selectionRectsMatch = YES;
+        if (currentRects.count == selectionRects.count) {
+            for (NSUInteger index = 0; index < selectionRects.count; ++index)
+                selectionRectsMatch |= [selectionRects[index] isEqualToValue:currentRects[index]];
+        } else
+            selectionRectsMatch = NO;
+
+        if (selectionRectsMatch)
+            break;
+
+        if (hasEmittedWarning || startTime + secondsToWaitUntilWarning >= [NSDate timeIntervalSinceReferenceDate])
+            continue;
+
+        NSLog(@"Expected a selection rects of %@, but still observed %@", selectionRects, currentRects);
+        hasEmittedWarning = YES;
+    }
+}
+
+@end
+
+static RetainPtr<TestWKWebView> webViewWithAutofocusedInput()
+{
     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
     auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
 
@@ -47,11 +92,20 @@
         return _WKFocusStartsInputSessionPolicyAllow;
     }];
     [webView _setInputDelegate:inputDelegate.get()];
-    [webView synchronouslyLoadHTMLString:@"<input autofocus>"];
+    [webView synchronouslyLoadHTMLString:@"<meta name='viewport' content='width=device-width, initial-scale=1'><input autofocus>"];
 
     TestWebKitAPI::Util::run(&doneWaiting);
     doneWaiting = false;
+    return webView;
+}
 
+namespace TestWebKitAPI {
+
+TEST(KeyboardInputTests, CanHandleKeyEventInCompletionHandler)
+{
+    auto webView = webViewWithAutofocusedInput();
+    bool doneWaiting = false;
+
     id <UITextInputPrivate> contentView = (id <UITextInputPrivate>)[webView firstResponder];
     auto firstWebEvent = adoptNS([[WebEvent alloc] initWithKeyEventType:WebEventKeyDown timeStamp:CFAbsoluteTimeGetCurrent() characters:@"a" charactersIgnoringModifiers:@"a" modifiers:0 isRepeating:NO withFlags:0 withInputManagerHint:nil keyCode:0 isTabKey:NO]);
     auto secondWebEvent = adoptNS([[WebEvent alloc] initWithKeyEventType:WebEventKeyUp timeStamp:CFAbsoluteTimeGetCurrent() characters:@"a" charactersIgnoringModifiers:@"a" modifiers:0 isRepeating:NO withFlags:0 withInputManagerHint:nil keyCode:0 isTabKey:NO]);
@@ -68,6 +122,41 @@
     EXPECT_WK_STREQ("a", [webView stringByEvaluatingJavaScript:@"document.querySelector('input').value"]);
 }
 
+TEST(KeyboardInputTests, CaretSelectionRectAfterRestoringFirstResponder)
+{
+    auto expectedCaretRect = CGRectMake(16, 13, 3, 15);
+    auto webView = webViewWithAutofocusedInput();
+    EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+    [webView waitForCaretViewFrameToBecome:expectedCaretRect];
+
+    dispatch_block_t restoreActiveFocusState = [webView _retainActiveFocusedState];
+    [webView resignFirstResponder];
+    restoreActiveFocusState();
+    [webView waitForCaretViewFrameToBecome:CGRectZero];
+
+    [webView becomeFirstResponder];
+    [webView waitForCaretViewFrameToBecome:expectedCaretRect];
+}
+
+TEST(KeyboardInputTests, RangedSelectionRectAfterRestoringFirstResponder)
+{
+    NSArray *expectedSelectionRects = @[ [NSValue valueWithCGRect:CGRectMake(16, 13, 24, 15)] ];
+
+    auto webView = webViewWithAutofocusedInput();
+    [[webView textInputContentView] insertText:@"hello"];
+    [webView selectAll:nil];
+    EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+    [webView waitForSelectionViewRectsToBecome:expectedSelectionRects];
+
+    dispatch_block_t restoreActiveFocusState = [webView _retainActiveFocusedState];
+    [webView resignFirstResponder];
+    restoreActiveFocusState();
+    [webView waitForSelectionViewRectsToBecome:@[ ]];
+
+    [webView becomeFirstResponder];
+    [webView waitForSelectionViewRectsToBecome:expectedSelectionRects];
+}
+
 } // namespace TestWebKitAPI
 
 #endif // WK_API_ENABLED && PLATFORM(IOS)

Modified: trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.h (234599 => 234600)


--- trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.h	2018-08-06 13:59:04 UTC (rev 234599)
+++ trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.h	2018-08-06 14:14:42 UTC (rev 234600)
@@ -63,6 +63,8 @@
 @interface TestWKWebView (IOSOnly)
 @property (nonatomic, readonly) UIView <UITextInput> *textInputContentView;
 @property (nonatomic, readonly) RetainPtr<NSArray> selectionRectsAfterPresentationUpdate;
+@property (nonatomic, readonly) CGRect caretViewRectInContentCoordinates;
+@property (nonatomic, readonly) NSArray<NSValue *> *selectionViewRectsInContentCoordinates;
 - (_WKActivatedElementInfo *)activatedElementAtPosition:(CGPoint)position;
 @end
 #endif

Modified: trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.mm (234599 => 234600)


--- trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.mm	2018-08-06 13:59:04 UTC (rev 234599)
+++ trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.mm	2018-08-06 14:14:42 UTC (rev 234600)
@@ -337,6 +337,22 @@
     return selectionRects;
 }
 
+- (CGRect)caretViewRectInContentCoordinates
+{
+    UIView *selectionView = [self.textInputContentView valueForKeyPath:@"interactionAssistant.selectionView"];
+    CGRect caretFrame = [[selectionView valueForKeyPath:@"caretView.frame"] CGRectValue];
+    return [selectionView convertRect:caretFrame toView:self.textInputContentView];
+}
+
+- (NSArray<NSValue *> *)selectionViewRectsInContentCoordinates
+{
+    NSMutableArray *selectionRects = [NSMutableArray array];
+    NSArray<UITextSelectionRect *> *rects = [self.textInputContentView valueForKeyPath:@"interactionAssistant.selectionView.rangeView.rects"];
+    for (UITextSelectionRect *rect in rects)
+        [selectionRects addObject:[NSValue valueWithCGRect:rect.rect]];
+    return selectionRects;
+}
+
 - (_WKActivatedElementInfo *)activatedElementAtPosition:(CGPoint)position
 {
     __block RetainPtr<_WKActivatedElementInfo> info;

Modified: trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm (234599 => 234600)


--- trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm	2018-08-06 13:59:04 UTC (rev 234599)
+++ trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm	2018-08-06 14:14:42 UTC (rev 234600)
@@ -579,6 +579,26 @@
     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:toNSDictionary(TestController::singleton().mainWebView()->platformView()._uiTextCaretRect) inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
 }
 
+JSObjectRef UIScriptController::selectionStartGrabberViewRect() const
+{
+    WKWebView *webView = TestController::singleton().mainWebView()->platformView();
+    UIView *contentView = [webView valueForKeyPath:@"_currentContentView"];
+    UIView *selectionRangeView = [contentView valueForKeyPath:@"interactionAssistant.selectionView.rangeView"];
+    auto frameInContentCoordinates = [selectionRangeView convertRect:[[selectionRangeView valueForKeyPath:@"startGrabber"] frame] toView:contentView];
+    auto jsContext = m_context->jsContext();
+    return JSValueToObject(jsContext, [JSValue valueWithObject:toNSDictionary(frameInContentCoordinates) inContext:[JSContext contextWithJSGlobalContextRef:jsContext]].JSValueRef, nullptr);
+}
+
+JSObjectRef UIScriptController::selectionEndGrabberViewRect() const
+{
+    WKWebView *webView = TestController::singleton().mainWebView()->platformView();
+    UIView *contentView = [webView valueForKeyPath:@"_currentContentView"];
+    UIView *selectionRangeView = [contentView valueForKeyPath:@"interactionAssistant.selectionView.rangeView"];
+    auto frameInContentCoordinates = [selectionRangeView convertRect:[[selectionRangeView valueForKeyPath:@"endGrabber"] frame] toView:contentView];
+    auto jsContext = m_context->jsContext();
+    return JSValueToObject(jsContext, [JSValue valueWithObject:toNSDictionary(frameInContentCoordinates) inContext:[JSContext contextWithJSGlobalContextRef:jsContext]].JSValueRef, nullptr);
+}
+
 JSObjectRef UIScriptController::inputViewBounds() const
 {
     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:toNSDictionary(TestController::singleton().mainWebView()->platformView()._inputViewBounds) inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to