Title: [249112] trunk
Revision
249112
Author
wenson_hs...@apple.com
Date
2019-08-26 12:37:29 -0700 (Mon, 26 Aug 2019)

Log Message

REGRESSION (iOS 13): Tests that simulate multiple back-to-back single taps fail or time out
https://bugs.webkit.org/show_bug.cgi?id=201129
<rdar://problem/51857277>

Reviewed by Tim Horton.

Source/WebKit:

Adds a new SPI hook in WebKit to let clients know when a synthetic tap gesture that has ended has been reset.
See Tools/ChangeLog and LayoutTests/ChangeLog for more details.

* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _doAfterResettingSingleTapGesture:]):
* UIProcess/API/Cocoa/WKWebViewPrivate.h:
* UIProcess/ios/WKContentViewInteraction.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView _singleTapDidReset:]):
(-[WKContentView _doAfterResettingSingleTapGesture:]):

Tools:

The tests in editing/pasteboard/ios were timing out on iOS 13 before this change. This is because they simulate
back-to-back single taps; while this is recognized as two single taps on iOS 12 and prior, only the first single
tap is recognized on iOS 13 (and the second is simply dropped on the floor). This occurs because the synthetic
single tap gesture is reset slightly later on iOS 13 compared to iOS 12, so when the second tap is dispatched,
the gesture recognizer is still in "ended" state after the first tap on iOS 13, which means the gesture isn't
capable of recognizing further touches yet.

In UIKit, a gesture recognizer is only reset once its UIGestureEnvironment's containing dependency subgraph no
longer contains gestures that are active. In iOS 12, the synthetic click gesture is a part of a dependency
subgraph that contains only itself and the normal (blocking) double tap gesture which requires the click to fail
before it can be recognized; immediately after simulating the tap, both these gestures are inactive, which
allows both of them to be reset.

However, in iOS 13, the synthetic click gesture is part of a gesture dependency graph that contains the double
tap for double click gesture, as well as the non-blocking double tap gesture, both of which are still active
immediately after sending the first tap. This change in dependencies is caused by the introduction of
UIUndoGestureInteraction's single and double three-finger tap gestures, which (in -[UIUndoGestureInteraction
gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:]) explicitly add all other taps as failure
requirements. This effectively links the synthetic single tap gesture to most of the other gestures in
WKContentView's dependency graph by way of these tap gestures for the undo interaction.

All this means that there is now a short (~50 ms) delay after the synthetic single tap gestures is recognized,
before it can be recognized again. To account for this new delay in our test infrastructure, simply wait for
single tap gestures that have ended to reset before attempting to send subsequent single taps. We do this by
introducing WebKit testing SPI to invoke a completion handler after resetting the synthetic click gesture (only
if necessary - i.e., if the gesture is in ended state when we are about to begin simulating the tap). This
allows calls to `UIScriptController::singleTapAtPoint` to be reliably recognized as single taps without
requiring arbitrary 120 ms "human speed" delays.

This fixes a number of flaky or failing layout tests, including the tests in editing/pasteboard/ios.

* TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
* TestRunnerShared/UIScriptContext/UIScriptController.h:
(WTR::UIScriptController::doubleTapAtPoint):

Add a `delay` parameter to `doubleTapAtPoint`. A number of layout tests were actually simulating double click
gestures by simulating two back-to-back single taps; this is done for the purposes of being able to add a "human
speed" delay prior to the second single tap gesture. After the change to wait for the single tap gesture to
reset before attempting to simulate the next tap, this strategy no longer works, since the second gesture is
recognized only as a single tap instead of a double tap.

Instead, we add a delay parameter to `UIScriptController::doubleTapAtPoint`, which the "human speed" double tap
gestures use instead to wait after simulating the first tap.

* WebKitTestRunner/ios/HIDEventGenerator.h:
* WebKitTestRunner/ios/HIDEventGenerator.mm:
(-[HIDEventGenerator _waitFor:]):
(-[HIDEventGenerator sendTaps:location:withNumberOfTouches:delay:completionBlock:]):

Plumb the tap gesture delay through to this helper method.

(-[HIDEventGenerator tap:completionBlock:]):
(-[HIDEventGenerator doubleTap:delay:completionBlock:]):
(-[HIDEventGenerator twoFingerTap:completionBlock:]):
(-[HIDEventGenerator sendTaps:location:withNumberOfTouches:completionBlock:]): Deleted.
(-[HIDEventGenerator doubleTap:completionBlock:]): Deleted.
* WebKitTestRunner/ios/UIScriptControllerIOS.h:
* WebKitTestRunner/ios/UIScriptControllerIOS.mm:
(WTR::UIScriptControllerIOS::waitForSingleTapToReset const):

Add a new helper to wait for the content view's single tap gesture to reset if needed; call this before
attempting to simulate single taps (either using a stylus, or with a regular touch).

(WTR::UIScriptControllerIOS::singleTapAtPointWithModifiers):
(WTR::UIScriptControllerIOS::doubleTapAtPoint):
(WTR::UIScriptControllerIOS::stylusTapAtPointWithModifiers):

LayoutTests:

Adjusts a few layout tests after changes to UIScriptController::doubleTapAtPoint and
UIScriptController::singleTapAtPoint.

* editing/selection/ios/change-selection-by-tapping.html:

Tweak this test to tap the page 12 times instead of 20 (which seems to cause occasional timeouts locally, when
running all layout tests with a dozen active simulators).

* fast/events/ios/double-tap-zoom.html:
* fast/events/ios/viewport-device-width-allows-double-tap-zoom-out.html:
* fast/events/ios/viewport-shrink-to-fit-allows-double-tap.html:

Augment a few call sites of `doubleTapAtPoint` with a 0 delay. Ideally, these should just use ui-helper.js, but
we can refactor these tests as a part of folding basic-gestures.js into ui-helper.js.

* http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt:
* http/tests/security/anchor-download-block-crossorigin-expected.txt:

Rebaseline these layout tests, due to change in line numbers.

* platform/ipad/TestExpectations:

Unskip these tests on iPad, now that they should pass.

* pointerevents/utils.js:
(const.ui.new.UIController.prototype.doubleTapToZoom):
* resources/basic-gestures.js:
(return.new.Promise.):
(return.new.Promise):

Adjust some more call sites of `doubleTapAtPoint`. Ideally, these should use just `ui-helper.js` too.

* resources/ui-helper.js:
(window.UIHelper.doubleTapAt.return.new.Promise):
(window.UIHelper.doubleTapAt):
(window.UIHelper.humanSpeedDoubleTapAt):
(window.UIHelper.humanSpeedZoomByDoubleTappingAt):

Add a delay parameter to `doubleTapAt` to specify a delay after each simulated tap. By default, this is 0, but
the `humanSpeed*` helpers add a delay of 120 milliseconds. Additionally, these helpers were previously calling
`singleTapAtPoint` twice, with a timeout in between to add a delay. Instead, call `doubleTapAtPoint` with a
nonzero delay; otherwise, we'll end up waiting in `singleTapAtPoint` for the gesture subgraph containing both
the double tap gestures and the synthetic single tap gesture to reset, which causes these two single taps to no
longer be recognized as a double tap gesture.

(window.UIHelper.zoomByDoubleTappingAt):

Modified Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (249111 => 249112)


--- trunk/LayoutTests/ChangeLog	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/LayoutTests/ChangeLog	2019-08-26 19:37:29 UTC (rev 249112)
@@ -1,3 +1,58 @@
+2019-08-26  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        REGRESSION (iOS 13): Tests that simulate multiple back-to-back single taps fail or time out
+        https://bugs.webkit.org/show_bug.cgi?id=201129
+        <rdar://problem/51857277>
+
+        Reviewed by Tim Horton.
+
+        Adjusts a few layout tests after changes to UIScriptController::doubleTapAtPoint and
+        UIScriptController::singleTapAtPoint.
+
+        * editing/selection/ios/change-selection-by-tapping.html:
+
+        Tweak this test to tap the page 12 times instead of 20 (which seems to cause occasional timeouts locally, when
+        running all layout tests with a dozen active simulators).
+
+        * fast/events/ios/double-tap-zoom.html:
+        * fast/events/ios/viewport-device-width-allows-double-tap-zoom-out.html:
+        * fast/events/ios/viewport-shrink-to-fit-allows-double-tap.html:
+
+        Augment a few call sites of `doubleTapAtPoint` with a 0 delay. Ideally, these should just use ui-helper.js, but
+        we can refactor these tests as a part of folding basic-gestures.js into ui-helper.js.
+
+        * http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt:
+        * http/tests/security/anchor-download-block-crossorigin-expected.txt:
+
+        Rebaseline these layout tests, due to change in line numbers.
+
+        * platform/ipad/TestExpectations:
+
+        Unskip these tests on iPad, now that they should pass.
+
+        * pointerevents/utils.js:
+        (const.ui.new.UIController.prototype.doubleTapToZoom):
+        * resources/basic-gestures.js:
+        (return.new.Promise.):
+        (return.new.Promise):
+
+        Adjust some more call sites of `doubleTapAtPoint`. Ideally, these should use just `ui-helper.js` too.
+
+        * resources/ui-helper.js:
+        (window.UIHelper.doubleTapAt.return.new.Promise):
+        (window.UIHelper.doubleTapAt):
+        (window.UIHelper.humanSpeedDoubleTapAt):
+        (window.UIHelper.humanSpeedZoomByDoubleTappingAt):
+
+        Add a delay parameter to `doubleTapAt` to specify a delay after each simulated tap. By default, this is 0, but
+        the `humanSpeed*` helpers add a delay of 120 milliseconds. Additionally, these helpers were previously calling
+        `singleTapAtPoint` twice, with a timeout in between to add a delay. Instead, call `doubleTapAtPoint` with a
+        nonzero delay; otherwise, we'll end up waiting in `singleTapAtPoint` for the gesture subgraph containing both
+        the double tap gestures and the synthetic single tap gesture to reset, which causes these two single taps to no
+        longer be recognized as a double tap gesture.
+
+        (window.UIHelper.zoomByDoubleTappingAt):
+
 2019-08-26  Jiewen Tan  <jiewen_...@apple.com>
 
         [WebAuthn] Support HID authenticators on iOS

Modified: trunk/LayoutTests/editing/selection/ios/change-selection-by-tapping.html (249111 => 249112)


--- trunk/LayoutTests/editing/selection/ios/change-selection-by-tapping.html	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/LayoutTests/editing/selection/ios/change-selection-by-tapping.html	2019-08-26 19:37:29 UTC (rev 249112)
@@ -40,7 +40,7 @@
     description("Verifies that rapidly tapping to change selection doesn't hang due to IPC deadlock. To verify manually, focus the editable text and tap repeatedly in different parts of the editable area to change selection; check that this does not result in sporadic 1-second IPC hangs.");
 
     await UIHelper.activateElementAndWaitForInputSession(document.getElementById("editor"));
-    for (let i = 0; i < 5; ++i) {
+    for (let i = 0; i < 3; ++i) {
         for (const [x, y] of [[40, 40], [220, 40], [40, 240], [220, 240]])
             await tapAndWaitForSelectionChange(x, y);
     }

Modified: trunk/LayoutTests/fast/events/ios/double-tap-zoom.html (249111 => 249112)


--- trunk/LayoutTests/fast/events/ios/double-tap-zoom.html	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/LayoutTests/fast/events/ios/double-tap-zoom.html	2019-08-26 19:37:29 UTC (rev 249112)
@@ -9,7 +9,7 @@
                 uiController.uiScriptComplete('Zoomed to scale ' + uiController.zoomScale);
             };
 
-            uiController.doubleTapAtPoint(50, 50, function() {});
+            uiController.doubleTapAtPoint(50, 50, 0, function() {});
         })();
     </script>
     <script>

Modified: trunk/LayoutTests/fast/events/ios/viewport-device-width-allows-double-tap-zoom-out.html (249111 => 249112)


--- trunk/LayoutTests/fast/events/ios/viewport-device-width-allows-double-tap-zoom-out.html	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/LayoutTests/fast/events/ios/viewport-device-width-allows-double-tap-zoom-out.html	2019-08-26 19:37:29 UTC (rev 249112)
@@ -8,7 +8,7 @@
             uiController.didEndZoomingCallback = function() {
                 uiController.uiScriptComplete(uiController.zoomScale);
             };
-            uiController.doubleTapAtPoint(15, 15, function() {});
+            uiController.doubleTapAtPoint(15, 15, 0, function() {});
         })();
     </script>
     <script>

Modified: trunk/LayoutTests/fast/events/ios/viewport-shrink-to-fit-allows-double-tap.html (249111 => 249112)


--- trunk/LayoutTests/fast/events/ios/viewport-shrink-to-fit-allows-double-tap.html	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/LayoutTests/fast/events/ios/viewport-shrink-to-fit-allows-double-tap.html	2019-08-26 19:37:29 UTC (rev 249112)
@@ -16,7 +16,7 @@
                     uiController.didEndZoomingCallback = function() {
                         uiController.uiScriptComplete(uiController.zoomScale);
                     };
-                    uiController.doubleTapAtPoint(15, 60, function() {});
+                    uiController.doubleTapAtPoint(15, 60, 0, function() {});
                 })();`;
         }
 

Modified: trunk/LayoutTests/http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt (249111 => 249112)


--- trunk/LayoutTests/http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/LayoutTests/http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt	2019-08-26 19:37:29 UTC (rev 249112)
@@ -1,14 +1,14 @@
-CONSOLE MESSAGE: line 192: adcampaignid must have a non-negative value less than or equal to 63 for Ad Click Attribution.
-CONSOLE MESSAGE: line 192: adcampaignid must have a non-negative value less than or equal to 63 for Ad Click Attribution.
-CONSOLE MESSAGE: line 192: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
-CONSOLE MESSAGE: line 192: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
-CONSOLE MESSAGE: line 192: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
-CONSOLE MESSAGE: line 192: addestination could not be converted to a valid HTTP-family URL.
-CONSOLE MESSAGE: line 192: addestination could not be converted to a valid HTTP-family URL.
-CONSOLE MESSAGE: line 192: addestination could not be converted to a valid HTTP-family URL.
-CONSOLE MESSAGE: line 192: Both adcampaignid and addestination need to be set for Ad Click Attribution to work.
-CONSOLE MESSAGE: line 192: Both adcampaignid and addestination need to be set for Ad Click Attribution to work.
-CONSOLE MESSAGE: line 192: addestination can not be the same site as the current website.
+CONSOLE MESSAGE: line 182: adcampaignid must have a non-negative value less than or equal to 63 for Ad Click Attribution.
+CONSOLE MESSAGE: line 182: adcampaignid must have a non-negative value less than or equal to 63 for Ad Click Attribution.
+CONSOLE MESSAGE: line 182: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
+CONSOLE MESSAGE: line 182: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
+CONSOLE MESSAGE: line 182: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
+CONSOLE MESSAGE: line 182: addestination could not be converted to a valid HTTP-family URL.
+CONSOLE MESSAGE: line 182: addestination could not be converted to a valid HTTP-family URL.
+CONSOLE MESSAGE: line 182: addestination could not be converted to a valid HTTP-family URL.
+CONSOLE MESSAGE: line 182: Both adcampaignid and addestination need to be set for Ad Click Attribution to work.
+CONSOLE MESSAGE: line 182: Both adcampaignid and addestination need to be set for Ad Click Attribution to work.
+CONSOLE MESSAGE: line 182: addestination can not be the same site as the current website.
 Test for validity of ad click attribution attributes on anchor tags.
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".

Modified: trunk/LayoutTests/http/tests/security/anchor-download-block-crossorigin-expected.txt (249111 => 249112)


--- trunk/LayoutTests/http/tests/security/anchor-download-block-crossorigin-expected.txt	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/LayoutTests/http/tests/security/anchor-download-block-crossorigin-expected.txt	2019-08-26 19:37:29 UTC (rev 249112)
@@ -1,4 +1,4 @@
-CONSOLE MESSAGE: line 165: The download attribute on anchor was ignored because its href URL has a different security origin.
+CONSOLE MESSAGE: line 155: The download attribute on anchor was ignored because its href URL has a different security origin.
 Tests that the download attribute is ignored if the link is cross origin.
 
 It should navigate the subframe instead of downloading the file.

Modified: trunk/LayoutTests/platform/ipad/TestExpectations (249111 => 249112)


--- trunk/LayoutTests/platform/ipad/TestExpectations	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/LayoutTests/platform/ipad/TestExpectations	2019-08-26 19:37:29 UTC (rev 249112)
@@ -55,12 +55,6 @@
 http/tests/paymentrequest/rejects_if_not_active.https.html [ Skip ]
 http/tests/paymentrequest/updateWith-method-pmi-handling.https.html [ Skip ]
 
-# <rdar://problem/51857277> iOS 13 iPad: editing/pasteboard/ios/dom-paste-* layout tests timing out
-editing/pasteboard/ios/dom-paste-confirmation.html [ Skip ]
-editing/pasteboard/ios/dom-paste-consecutive-confirmations.html [ Skip ]
-editing/pasteboard/ios/dom-paste-rejection.html [ Skip ]
-editing/pasteboard/ios/dom-paste-requires-user-gesture.html [ Skip ]
-
 # <rdar://problem/51862629> REGRESSION (r244239) [ iPad Sim ] Layout Test fast/canvas/canvas-too-large-to-draw.html is failing
 fast/canvas/canvas-too-large-to-draw.html [ Failure ]
 

Modified: trunk/LayoutTests/pointerevents/utils.js (249111 => 249112)


--- trunk/LayoutTests/pointerevents/utils.js	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/LayoutTests/pointerevents/utils.js	2019-08-26 19:37:29 UTC (rev 249112)
@@ -126,7 +126,7 @@
     doubleTapToZoom(options)
     {
         const durationInSeconds = 0.35;
-        return new Promise(resolve => this._run(`uiController.doubleTapAtPoint(${options.x}, ${options.y})`).then(() =>
+        return new Promise(resolve => this._run(`uiController.doubleTapAtPoint(${options.x}, ${options.y}, 0)`).then(() =>
             setTimeout(resolve, durationInSeconds * 1000)
         ));
         return this._run();

Modified: trunk/LayoutTests/resources/basic-gestures.js (249111 => 249112)


--- trunk/LayoutTests/resources/basic-gestures.js	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/LayoutTests/resources/basic-gestures.js	2019-08-26 19:37:29 UTC (rev 249112)
@@ -32,7 +32,7 @@
     return new Promise(resolve => {
         testRunner.runUIScript(`
             (function() {
-                uiController.doubleTapAtPoint(${x}, ${y}, function() {
+                uiController.doubleTapAtPoint(${x}, ${y}, 0, function() {
                     uiController.uiScriptComplete();
                 });
             })();`, resolve);

Modified: trunk/LayoutTests/resources/ui-helper.js (249111 => 249112)


--- trunk/LayoutTests/resources/ui-helper.js	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/LayoutTests/resources/ui-helper.js	2019-08-26 19:37:29 UTC (rev 249112)
@@ -50,7 +50,7 @@
         });
     }
 
-    static doubleTapAt(x, y)
+    static doubleTapAt(x, y, delay = 0)
     {
         console.assert(this.isIOSFamily());
 
@@ -68,7 +68,7 @@
 
         return new Promise((resolve) => {
             testRunner.runUIScript(`
-                uiController.doubleTapAtPoint(${x}, ${y}, function() {
+                uiController.doubleTapAtPoint(${x}, ${y}, ${delay}, function() {
                     uiController.uiScriptComplete();
                 });`, resolve);
         });
@@ -91,12 +91,7 @@
             return Promise.resolve();
         }
 
-        return new Promise(async (resolve) => {
-            await UIHelper.tapAt(x, y);
-            await new Promise(resolveAfterDelay => setTimeout(resolveAfterDelay, 120));
-            await UIHelper.tapAt(x, y);
-            resolve();
-        });
+        return UIHelper.doubleTapAt(x, y, 0.12);
     }
 
     static humanSpeedZoomByDoubleTappingAt(x, y)
@@ -117,17 +112,12 @@
         }
 
         return new Promise(async (resolve) => {
-            await UIHelper.tapAt(x, y);
-            await new Promise(resolveAfterDelay => setTimeout(resolveAfterDelay, 120));
-            await new Promise((resolveAfterZoom) => {
-                testRunner.runUIScript(`
-                    uiController.didEndZoomingCallback = () => {
-                        uiController.didEndZoomingCallback = null;
-                        uiController.uiScriptComplete(uiController.zoomScale);
-                    };
-                    uiController.singleTapAtPoint(${x}, ${y}, () => {});`, resolveAfterZoom);
-            });
-            resolve();
+            testRunner.runUIScript(`
+                uiController.didEndZoomingCallback = () => {
+                    uiController.didEndZoomingCallback = null;
+                    uiController.uiScriptComplete(uiController.zoomScale);
+                };
+                uiController.doubleTapAtPoint(${x}, ${y}, 0.12, () => { });`, resolve);
         });
     }
 
@@ -153,7 +143,7 @@
                     uiController.didEndZoomingCallback = null;
                     uiController.uiScriptComplete(uiController.zoomScale);
                 };
-                uiController.doubleTapAtPoint(${x}, ${y}, () => {});`, resolve);
+                uiController.doubleTapAtPoint(${x}, ${y}, 0, () => { });`, resolve);
         });
     }
 

Modified: trunk/Source/WebKit/ChangeLog (249111 => 249112)


--- trunk/Source/WebKit/ChangeLog	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/Source/WebKit/ChangeLog	2019-08-26 19:37:29 UTC (rev 249112)
@@ -1,3 +1,22 @@
+2019-08-26  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        REGRESSION (iOS 13): Tests that simulate multiple back-to-back single taps fail or time out
+        https://bugs.webkit.org/show_bug.cgi?id=201129
+        <rdar://problem/51857277>
+
+        Reviewed by Tim Horton.
+
+        Adds a new SPI hook in WebKit to let clients know when a synthetic tap gesture that has ended has been reset.
+        See Tools/ChangeLog and LayoutTests/ChangeLog for more details.
+
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView _doAfterResettingSingleTapGesture:]):
+        * UIProcess/API/Cocoa/WKWebViewPrivate.h:
+        * UIProcess/ios/WKContentViewInteraction.h:
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView _singleTapDidReset:]):
+        (-[WKContentView _doAfterResettingSingleTapGesture:]):
+
 2019-08-26  Brent Fulgham  <bfulg...@apple.com>
 
         [FTW] Go back to ID2D1Bitmap as our NativeImage type

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm (249111 => 249112)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm	2019-08-26 19:37:29 UTC (rev 249112)
@@ -7002,6 +7002,11 @@
     };
 }
 
+- (void)_doAfterResettingSingleTapGesture:(dispatch_block_t)action
+{
+    [_contentView _doAfterResettingSingleTapGesture:action];
+}
+
 - (void)_doAfterReceivingEditDragSnapshotForTesting:(dispatch_block_t)action
 {
     [_contentView _doAfterReceivingEditDragSnapshotForTesting:action];

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h (249111 => 249112)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h	2019-08-26 19:37:29 UTC (rev 249112)
@@ -481,6 +481,7 @@
 - (void)_didShowForcePressPreview WK_API_AVAILABLE(ios(10.3));
 - (void)_didDismissForcePressPreview WK_API_AVAILABLE(ios(10.3));
 - (void)_doAfterNextStablePresentationUpdate:(dispatch_block_t)updateBlock WK_API_AVAILABLE(ios(10.3));
+- (void)_doAfterResettingSingleTapGesture:(dispatch_block_t)action WK_API_AVAILABLE(ios(WK_IOS_TBA));
 
 @property (nonatomic, readonly) NSArray<NSValue *> *_uiTextSelectionRects WK_API_AVAILABLE(ios(10.3));
 @property (nonatomic, readonly) CGRect _uiTextCaretRect WK_API_AVAILABLE(ios(10.3));

Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h (249111 => 249112)


--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h	2019-08-26 19:37:29 UTC (rev 249112)
@@ -385,6 +385,8 @@
 #if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
     std::unique_ptr<WebKit::TextCheckingController> _textCheckingController;
 #endif
+
+    Vector<BlockPtr<void()>> _actionsToPerformAfterResettingSingleTapGestureRecognizer;
 }
 
 @end
@@ -555,6 +557,7 @@
 - (void)selectFormAccessoryPickerRow:(NSInteger)rowIndex;
 - (void)setTimePickerValueToHour:(NSInteger)hour minute:(NSInteger)minute;
 - (NSDictionary *)_contentsOfUserInterfaceItem:(NSString *)userInterfaceItem;
+- (void)_doAfterResettingSingleTapGesture:(dispatch_block_t)action;
 - (void)_doAfterReceivingEditDragSnapshotForTesting:(dispatch_block_t)action;
 
 @property (nonatomic, readonly) NSString *textContentTypeForTesting;

Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm (249111 => 249112)


--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm	2019-08-26 19:37:29 UTC (rev 249112)
@@ -2450,6 +2450,9 @@
             _page->touchWithIdentifierWasRemoved(pointerId);
     }
 #endif
+    auto actionsToPerform = std::exchange(_actionsToPerformAfterResettingSingleTapGestureRecognizer, { });
+    for (auto action : actionsToPerform)
+        action();
 }
 
 - (void)_doubleTapDidFail:(UITapGestureRecognizer *)gestureRecognizer
@@ -7541,6 +7544,15 @@
 
 @implementation WKContentView (WKTesting)
 
+- (void)_doAfterResettingSingleTapGesture:(dispatch_block_t)action
+{
+    if ([_singleTapGestureRecognizer state] != UIGestureRecognizerStateEnded) {
+        action();
+        return;
+    }
+    _actionsToPerformAfterResettingSingleTapGestureRecognizer.append(makeBlockPtr(action));
+}
+
 - (void)_doAfterReceivingEditDragSnapshotForTesting:(dispatch_block_t)action
 {
 #if ENABLE(DRAG_SUPPORT)

Modified: trunk/Tools/ChangeLog (249111 => 249112)


--- trunk/Tools/ChangeLog	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/Tools/ChangeLog	2019-08-26 19:37:29 UTC (rev 249112)
@@ -1,3 +1,78 @@
+2019-08-26  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        REGRESSION (iOS 13): Tests that simulate multiple back-to-back single taps fail or time out
+        https://bugs.webkit.org/show_bug.cgi?id=201129
+        <rdar://problem/51857277>
+
+        Reviewed by Tim Horton.
+
+        The tests in editing/pasteboard/ios were timing out on iOS 13 before this change. This is because they simulate
+        back-to-back single taps; while this is recognized as two single taps on iOS 12 and prior, only the first single
+        tap is recognized on iOS 13 (and the second is simply dropped on the floor). This occurs because the synthetic
+        single tap gesture is reset slightly later on iOS 13 compared to iOS 12, so when the second tap is dispatched,
+        the gesture recognizer is still in "ended" state after the first tap on iOS 13, which means the gesture isn't
+        capable of recognizing further touches yet.
+
+        In UIKit, a gesture recognizer is only reset once its UIGestureEnvironment's containing dependency subgraph no
+        longer contains gestures that are active. In iOS 12, the synthetic click gesture is a part of a dependency
+        subgraph that contains only itself and the normal (blocking) double tap gesture which requires the click to fail
+        before it can be recognized; immediately after simulating the tap, both these gestures are inactive, which
+        allows both of them to be reset.
+
+        However, in iOS 13, the synthetic click gesture is part of a gesture dependency graph that contains the double
+        tap for double click gesture, as well as the non-blocking double tap gesture, both of which are still active
+        immediately after sending the first tap. This change in dependencies is caused by the introduction of
+        UIUndoGestureInteraction's single and double three-finger tap gestures, which (in -[UIUndoGestureInteraction
+        gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:]) explicitly add all other taps as failure
+        requirements. This effectively links the synthetic single tap gesture to most of the other gestures in
+        WKContentView's dependency graph by way of these tap gestures for the undo interaction.
+
+        All this means that there is now a short (~50 ms) delay after the synthetic single tap gestures is recognized,
+        before it can be recognized again. To account for this new delay in our test infrastructure, simply wait for
+        single tap gestures that have ended to reset before attempting to send subsequent single taps. We do this by
+        introducing WebKit testing SPI to invoke a completion handler after resetting the synthetic click gesture (only
+        if necessary - i.e., if the gesture is in ended state when we are about to begin simulating the tap). This
+        allows calls to `UIScriptController::singleTapAtPoint` to be reliably recognized as single taps without
+        requiring arbitrary 120 ms "human speed" delays.
+
+        This fixes a number of flaky or failing layout tests, including the tests in editing/pasteboard/ios.
+
+        * TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
+        * TestRunnerShared/UIScriptContext/UIScriptController.h:
+        (WTR::UIScriptController::doubleTapAtPoint):
+
+        Add a `delay` parameter to `doubleTapAtPoint`. A number of layout tests were actually simulating double click
+        gestures by simulating two back-to-back single taps; this is done for the purposes of being able to add a "human
+        speed" delay prior to the second single tap gesture. After the change to wait for the single tap gesture to
+        reset before attempting to simulate the next tap, this strategy no longer works, since the second gesture is
+        recognized only as a single tap instead of a double tap.
+
+        Instead, we add a delay parameter to `UIScriptController::doubleTapAtPoint`, which the "human speed" double tap
+        gestures use instead to wait after simulating the first tap.
+
+        * WebKitTestRunner/ios/HIDEventGenerator.h:
+        * WebKitTestRunner/ios/HIDEventGenerator.mm:
+        (-[HIDEventGenerator _waitFor:]):
+        (-[HIDEventGenerator sendTaps:location:withNumberOfTouches:delay:completionBlock:]):
+
+        Plumb the tap gesture delay through to this helper method.
+
+        (-[HIDEventGenerator tap:completionBlock:]):
+        (-[HIDEventGenerator doubleTap:delay:completionBlock:]):
+        (-[HIDEventGenerator twoFingerTap:completionBlock:]):
+        (-[HIDEventGenerator sendTaps:location:withNumberOfTouches:completionBlock:]): Deleted.
+        (-[HIDEventGenerator doubleTap:completionBlock:]): Deleted.
+        * WebKitTestRunner/ios/UIScriptControllerIOS.h:
+        * WebKitTestRunner/ios/UIScriptControllerIOS.mm:
+        (WTR::UIScriptControllerIOS::waitForSingleTapToReset const):
+
+        Add a new helper to wait for the content view's single tap gesture to reset if needed; call this before
+        attempting to simulate single taps (either using a stylus, or with a regular touch).
+
+        (WTR::UIScriptControllerIOS::singleTapAtPointWithModifiers):
+        (WTR::UIScriptControllerIOS::doubleTapAtPoint):
+        (WTR::UIScriptControllerIOS::stylusTapAtPointWithModifiers):
+
 2019-08-26  Jonathan Bedard  <jbed...@apple.com>
 
         results.webkit.org: Allow clicking on the tooltip arrow

Modified: trunk/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl (249111 => 249112)


--- trunk/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl	2019-08-26 19:37:29 UTC (rev 249112)
@@ -57,7 +57,7 @@
     void liftUpAtPoint(long x, long y, long touchCount, object callback);
     void singleTapAtPoint(long x, long y, object callback);
     void singleTapAtPointWithModifiers(long x, long y, object modifierArray, object callback);
-    void doubleTapAtPoint(long x, long y, object callback);
+    void doubleTapAtPoint(long x, long y, float delay, object callback);
     void dragFromPointToPoint(long startX, long startY, long endX, long endY, double durationSeconds, object callback);
 
     void longPressAtPoint(long x, long y, object callback);

Modified: trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h (249111 => 249112)


--- trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h	2019-08-26 19:37:29 UTC (rev 249112)
@@ -135,7 +135,7 @@
     virtual void liftUpAtPoint(long x, long y, long touchCount, JSValueRef callback) { notImplemented(); }
     virtual void singleTapAtPoint(long x, long y, JSValueRef callback) { notImplemented(); }
     virtual void singleTapAtPointWithModifiers(long x, long y, JSValueRef modifierArray, JSValueRef callback) { notImplemented(); }
-    virtual void doubleTapAtPoint(long x, long y, JSValueRef callback) { notImplemented(); }
+    virtual void doubleTapAtPoint(long x, long y, float delay, JSValueRef callback) { notImplemented(); }
     virtual void dragFromPointToPoint(long startX, long startY, long endX, long endY, double durationSeconds, JSValueRef callback) { notImplemented(); }
     virtual void longPressAtPoint(long x, long y, JSValueRef callback) { notImplemented(); }
 

Modified: trunk/Tools/WebKitTestRunner/ios/HIDEventGenerator.h (249111 => 249112)


--- trunk/Tools/WebKitTestRunner/ios/HIDEventGenerator.h	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/Tools/WebKitTestRunner/ios/HIDEventGenerator.h	2019-08-26 19:37:29 UTC (rev 249112)
@@ -87,7 +87,7 @@
 
 // Taps
 - (void)tap:(CGPoint)location completionBlock:(void (^)(void))completionBlock;
-- (void)doubleTap:(CGPoint)location completionBlock:(void (^)(void))completionBlock;
+- (void)doubleTap:(CGPoint)location delay:(NSTimeInterval)delay completionBlock:(void (^)(void))completionBlock;
 - (void)twoFingerTap:(CGPoint)location completionBlock:(void (^)(void))completionBlock;
 
 // Long Press

Modified: trunk/Tools/WebKitTestRunner/ios/HIDEventGenerator.mm (249111 => 249112)


--- trunk/Tools/WebKitTestRunner/ios/HIDEventGenerator.mm	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/Tools/WebKitTestRunner/ios/HIDEventGenerator.mm	2019-08-26 19:37:29 UTC (rev 249112)
@@ -712,8 +712,22 @@
     [self sendMarkerHIDEventWithCompletionBlock:completionBlock];
 }
 
-- (void)sendTaps:(int)tapCount location:(CGPoint)location withNumberOfTouches:(int)touchCount completionBlock:(void (^)(void))completionBlock
+- (void)_waitFor:(NSTimeInterval)delay
 {
+    if (delay <= 0)
+        return;
+
+    bool doneWaitingForDelay = false;
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), [&doneWaitingForDelay] {
+        doneWaitingForDelay = true;
+    });
+
+    while (!doneWaitingForDelay)
+        [NSRunLoop.currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:NSDate.distantFuture];
+}
+
+- (void)sendTaps:(int)tapCount location:(CGPoint)location withNumberOfTouches:(int)touchCount delay:(NSTimeInterval)delay completionBlock:(void (^)(void))completionBlock
+{
     struct timespec doubleDelay = { 0, static_cast<long>(multiTapInterval * nanosecondsPerSecond) };
     struct timespec pressDelay = { 0, static_cast<long>(fingerLiftDelay * nanosecondsPerSecond) };
 
@@ -723,6 +737,8 @@
         [self liftUp:location touchCount:touchCount];
         if (i + 1 != tapCount) 
             nanosleep(&doubleDelay, 0);
+
+        [self _waitFor:delay];
     }
     
     [self sendMarkerHIDEventWithCompletionBlock:completionBlock];
@@ -730,17 +746,17 @@
 
 - (void)tap:(CGPoint)location completionBlock:(void (^)(void))completionBlock
 {
-    [self sendTaps:1 location:location withNumberOfTouches:1 completionBlock:completionBlock];
+    [self sendTaps:1 location:location withNumberOfTouches:1 delay:0 completionBlock:completionBlock];
 }
 
-- (void)doubleTap:(CGPoint)location completionBlock:(void (^)(void))completionBlock
+- (void)doubleTap:(CGPoint)location delay:(NSTimeInterval)delay completionBlock:(void (^)(void))completionBlock
 {
-    [self sendTaps:2 location:location withNumberOfTouches:1 completionBlock:completionBlock];
+    [self sendTaps:2 location:location withNumberOfTouches:1 delay:delay completionBlock:completionBlock];
 }
 
 - (void)twoFingerTap:(CGPoint)location completionBlock:(void (^)(void))completionBlock
 {
-    [self sendTaps:1 location:location withNumberOfTouches:2 completionBlock:completionBlock];
+    [self sendTaps:1 location:location withNumberOfTouches:2 delay:0 completionBlock:completionBlock];
 }
 
 - (void)longPress:(CGPoint)location completionBlock:(void (^)(void))completionBlock

Modified: trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.h (249111 => 249112)


--- trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.h	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.h	2019-08-26 19:37:29 UTC (rev 249112)
@@ -52,7 +52,7 @@
     void liftUpAtPoint(long x, long y, long touchCount, JSValueRef) override;
     void singleTapAtPoint(long x, long y, JSValueRef) override;
     void singleTapAtPointWithModifiers(long x, long y, JSValueRef modifierArray, JSValueRef) override;
-    void doubleTapAtPoint(long x, long y, JSValueRef) override;
+    void doubleTapAtPoint(long x, long y, float delay, JSValueRef) override;
     void stylusDownAtPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef) override;
     void stylusMoveToPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef) override;
     void stylusUpAtPoint(long x, long y, JSValueRef) override;
@@ -139,6 +139,9 @@
     void setDidDismissPopoverCallback(JSValueRef) override;
     void setDidEndScrollingCallback(JSValueRef) override;
     void clearAllCallbacks() override;
+
+private:
+    void waitForSingleTapToReset() const;
 };
 
 }

Modified: trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm (249111 => 249112)


--- trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm	2019-08-26 19:18:15 UTC (rev 249111)
+++ trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm	2019-08-26 19:37:29 UTC (rev 249112)
@@ -263,10 +263,21 @@
     singleTapAtPointWithModifiers(x, y, nullptr, callback);
 }
 
+void UIScriptControllerIOS::waitForSingleTapToReset() const
+{
+    bool doneWaitingForSingleTapToReset = false;
+    [webView() _doAfterResettingSingleTapGesture:[&doneWaitingForSingleTapToReset] {
+        doneWaitingForSingleTapToReset = true;
+    }];
+    TestController::singleton().runUntil(doneWaitingForSingleTapToReset, 0.5_s);
+}
+
 void UIScriptControllerIOS::singleTapAtPointWithModifiers(long x, long y, JSValueRef modifierArray, JSValueRef callback)
 {
     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
 
+    waitForSingleTapToReset();
+
     auto modifierFlags = parseModifierArray(m_context->jsContext(), modifierArray);
     for (auto& modifierFlag : modifierFlags)
         [[HIDEventGenerator sharedHIDEventGenerator] keyDown:modifierFlag];
@@ -286,11 +297,11 @@
     }];
 }
 
-void UIScriptControllerIOS::doubleTapAtPoint(long x, long y, JSValueRef callback)
+void UIScriptControllerIOS::doubleTapAtPoint(long x, long y, float delay, JSValueRef callback)
 {
     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
 
-    [[HIDEventGenerator sharedHIDEventGenerator] doubleTap:globalToContentCoordinates(webView(), x, y) completionBlock:^{
+    [[HIDEventGenerator sharedHIDEventGenerator] doubleTap:globalToContentCoordinates(webView(), x, y) delay:delay completionBlock:^{
         if (!m_context)
             return;
         m_context->asyncTaskComplete(callbackID);
@@ -342,6 +353,8 @@
 {
     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
 
+    waitForSingleTapToReset();
+
     auto modifierFlags = parseModifierArray(m_context->jsContext(), modifierArray);
     for (auto& modifierFlag : modifierFlags)
         [[HIDEventGenerator sharedHIDEventGenerator] keyDown:modifierFlag];
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to