⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 249112 in webkit


Ignore:
Timestamp:
Aug 26, 2019, 12:37:29 PM (7 years ago)
Author:
Wenson Hsieh
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):

Location:
trunk
Files:
23 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r249108 r249112  
     12019-08-26  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        REGRESSION (iOS 13): Tests that simulate multiple back-to-back single taps fail or time out
     4        https://bugs.webkit.org/show_bug.cgi?id=201129
     5        <rdar://problem/51857277>
     6
     7        Reviewed by Tim Horton.
     8
     9        Adjusts a few layout tests after changes to UIScriptController::doubleTapAtPoint and
     10        UIScriptController::singleTapAtPoint.
     11
     12        * editing/selection/ios/change-selection-by-tapping.html:
     13
     14        Tweak this test to tap the page 12 times instead of 20 (which seems to cause occasional timeouts locally, when
     15        running all layout tests with a dozen active simulators).
     16
     17        * fast/events/ios/double-tap-zoom.html:
     18        * fast/events/ios/viewport-device-width-allows-double-tap-zoom-out.html:
     19        * fast/events/ios/viewport-shrink-to-fit-allows-double-tap.html:
     20
     21        Augment a few call sites of `doubleTapAtPoint` with a 0 delay. Ideally, these should just use ui-helper.js, but
     22        we can refactor these tests as a part of folding basic-gestures.js into ui-helper.js.
     23
     24        * http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt:
     25        * http/tests/security/anchor-download-block-crossorigin-expected.txt:
     26
     27        Rebaseline these layout tests, due to change in line numbers.
     28
     29        * platform/ipad/TestExpectations:
     30
     31        Unskip these tests on iPad, now that they should pass.
     32
     33        * pointerevents/utils.js:
     34        (const.ui.new.UIController.prototype.doubleTapToZoom):
     35        * resources/basic-gestures.js:
     36        (return.new.Promise.):
     37        (return.new.Promise):
     38
     39        Adjust some more call sites of `doubleTapAtPoint`. Ideally, these should use just `ui-helper.js` too.
     40
     41        * resources/ui-helper.js:
     42        (window.UIHelper.doubleTapAt.return.new.Promise):
     43        (window.UIHelper.doubleTapAt):
     44        (window.UIHelper.humanSpeedDoubleTapAt):
     45        (window.UIHelper.humanSpeedZoomByDoubleTappingAt):
     46
     47        Add a delay parameter to `doubleTapAt` to specify a delay after each simulated tap. By default, this is 0, but
     48        the `humanSpeed*` helpers add a delay of 120 milliseconds. Additionally, these helpers were previously calling
     49        `singleTapAtPoint` twice, with a timeout in between to add a delay. Instead, call `doubleTapAtPoint` with a
     50        nonzero delay; otherwise, we'll end up waiting in `singleTapAtPoint` for the gesture subgraph containing both
     51        the double tap gestures and the synthetic single tap gesture to reset, which causes these two single taps to no
     52        longer be recognized as a double tap gesture.
     53
     54        (window.UIHelper.zoomByDoubleTappingAt):
     55
    1562019-08-26  Jiewen Tan  <jiewen_tan@apple.com>
    257
  • trunk/LayoutTests/editing/selection/ios/change-selection-by-tapping.html

    r242682 r249112  
    4141
    4242    await UIHelper.activateElementAndWaitForInputSession(document.getElementById("editor"));
    43     for (let i = 0; i < 5; ++i) {
     43    for (let i = 0; i < 3; ++i) {
    4444        for (const [x, y] of [[40, 40], [220, 40], [40, 240], [220, 240]])
    4545            await tapAndWaitForSelectionChange(x, y);
  • trunk/LayoutTests/fast/events/ios/double-tap-zoom.html

    r190368 r249112  
    1010            };
    1111
    12             uiController.doubleTapAtPoint(50, 50, function() {});
     12            uiController.doubleTapAtPoint(50, 50, 0, function() {});
    1313        })();
    1414    </script>
  • trunk/LayoutTests/fast/events/ios/viewport-device-width-allows-double-tap-zoom-out.html

    r196989 r249112  
    99                uiController.uiScriptComplete(uiController.zoomScale);
    1010            };
    11             uiController.doubleTapAtPoint(15, 15, function() {});
     11            uiController.doubleTapAtPoint(15, 15, 0, function() {});
    1212        })();
    1313    </script>
  • trunk/LayoutTests/fast/events/ios/viewport-shrink-to-fit-allows-double-tap.html

    r202354 r249112  
    1717                        uiController.uiScriptComplete(uiController.zoomScale);
    1818                    };
    19                     uiController.doubleTapAtPoint(15, 60, function() {});
     19                    uiController.doubleTapAtPoint(15, 60, 0, function() {});
    2020                })();`;
    2121        }
  • trunk/LayoutTests/http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt

    r247866 r249112  
    1 CONSOLE MESSAGE: line 192: adcampaignid must have a non-negative value less than or equal to 63 for Ad Click Attribution.
    2 CONSOLE MESSAGE: line 192: adcampaignid must have a non-negative value less than or equal to 63 for Ad Click Attribution.
    3 CONSOLE MESSAGE: line 192: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
    4 CONSOLE MESSAGE: line 192: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
    5 CONSOLE MESSAGE: line 192: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
    6 CONSOLE MESSAGE: line 192: addestination could not be converted to a valid HTTP-family URL.
    7 CONSOLE MESSAGE: line 192: addestination could not be converted to a valid HTTP-family URL.
    8 CONSOLE MESSAGE: line 192: addestination could not be converted to a valid HTTP-family URL.
    9 CONSOLE MESSAGE: line 192: Both adcampaignid and addestination need to be set for Ad Click Attribution to work.
    10 CONSOLE MESSAGE: line 192: Both adcampaignid and addestination need to be set for Ad Click Attribution to work.
    11 CONSOLE MESSAGE: line 192: addestination can not be the same site as the current website.
     1CONSOLE MESSAGE: line 182: adcampaignid must have a non-negative value less than or equal to 63 for Ad Click Attribution.
     2CONSOLE MESSAGE: line 182: adcampaignid must have a non-negative value less than or equal to 63 for Ad Click Attribution.
     3CONSOLE MESSAGE: line 182: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
     4CONSOLE MESSAGE: line 182: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
     5CONSOLE MESSAGE: line 182: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
     6CONSOLE MESSAGE: line 182: addestination could not be converted to a valid HTTP-family URL.
     7CONSOLE MESSAGE: line 182: addestination could not be converted to a valid HTTP-family URL.
     8CONSOLE MESSAGE: line 182: addestination could not be converted to a valid HTTP-family URL.
     9CONSOLE MESSAGE: line 182: Both adcampaignid and addestination need to be set for Ad Click Attribution to work.
     10CONSOLE MESSAGE: line 182: Both adcampaignid and addestination need to be set for Ad Click Attribution to work.
     11CONSOLE MESSAGE: line 182: addestination can not be the same site as the current website.
    1212Test for validity of ad click attribution attributes on anchor tags.
    1313
  • trunk/LayoutTests/http/tests/security/anchor-download-block-crossorigin-expected.txt

    r243262 r249112  
    1 CONSOLE MESSAGE: line 165: The download attribute on anchor was ignored because its href URL has a different security origin.
     1CONSOLE MESSAGE: line 155: The download attribute on anchor was ignored because its href URL has a different security origin.
    22Tests that the download attribute is ignored if the link is cross origin.
    33
  • trunk/LayoutTests/platform/ipad/TestExpectations

    r249099 r249112  
    5656http/tests/paymentrequest/updateWith-method-pmi-handling.https.html [ Skip ]
    5757
    58 # <rdar://problem/51857277> iOS 13 iPad: editing/pasteboard/ios/dom-paste-* layout tests timing out
    59 editing/pasteboard/ios/dom-paste-confirmation.html [ Skip ]
    60 editing/pasteboard/ios/dom-paste-consecutive-confirmations.html [ Skip ]
    61 editing/pasteboard/ios/dom-paste-rejection.html [ Skip ]
    62 editing/pasteboard/ios/dom-paste-requires-user-gesture.html [ Skip ]
    63 
    6458# <rdar://problem/51862629> REGRESSION (r244239) [ iPad Sim ] Layout Test fast/canvas/canvas-too-large-to-draw.html is failing
    6559fast/canvas/canvas-too-large-to-draw.html [ Failure ]
  • trunk/LayoutTests/pointerevents/utils.js

    r247212 r249112  
    127127    {
    128128        const durationInSeconds = 0.35;
    129         return new Promise(resolve => this._run(`uiController.doubleTapAtPoint(${options.x}, ${options.y})`).then(() =>
     129        return new Promise(resolve => this._run(`uiController.doubleTapAtPoint(${options.x}, ${options.y}, 0)`).then(() =>
    130130            setTimeout(resolve, durationInSeconds * 1000)
    131131        ));
  • trunk/LayoutTests/resources/basic-gestures.js

    r248752 r249112  
    3333        testRunner.runUIScript(`
    3434            (function() {
    35                 uiController.doubleTapAtPoint(${x}, ${y}, function() {
     35                uiController.doubleTapAtPoint(${x}, ${y}, 0, function() {
    3636                    uiController.uiScriptComplete();
    3737                });
  • trunk/LayoutTests/resources/ui-helper.js

    r249051 r249112  
    5151    }
    5252
    53     static doubleTapAt(x, y)
     53    static doubleTapAt(x, y, delay = 0)
    5454    {
    5555        console.assert(this.isIOSFamily());
     
    6969        return new Promise((resolve) => {
    7070            testRunner.runUIScript(`
    71                 uiController.doubleTapAtPoint(${x}, ${y}, function() {
     71                uiController.doubleTapAtPoint(${x}, ${y}, ${delay}, function() {
    7272                    uiController.uiScriptComplete();
    7373                });`, resolve);
     
    9292        }
    9393
    94         return new Promise(async (resolve) => {
    95             await UIHelper.tapAt(x, y);
    96             await new Promise(resolveAfterDelay => setTimeout(resolveAfterDelay, 120));
    97             await UIHelper.tapAt(x, y);
    98             resolve();
    99         });
     94        return UIHelper.doubleTapAt(x, y, 0.12);
    10095    }
    10196
     
    118113
    119114        return new Promise(async (resolve) => {
    120             await UIHelper.tapAt(x, y);
    121             await new Promise(resolveAfterDelay => setTimeout(resolveAfterDelay, 120));
    122             await new Promise((resolveAfterZoom) => {
    123                 testRunner.runUIScript(`
    124                     uiController.didEndZoomingCallback = () => {
    125                         uiController.didEndZoomingCallback = null;
    126                         uiController.uiScriptComplete(uiController.zoomScale);
    127                     };
    128                     uiController.singleTapAtPoint(${x}, ${y}, () => {});`, resolveAfterZoom);
    129             });
    130             resolve();
     115            testRunner.runUIScript(`
     116                uiController.didEndZoomingCallback = () => {
     117                    uiController.didEndZoomingCallback = null;
     118                    uiController.uiScriptComplete(uiController.zoomScale);
     119                };
     120                uiController.doubleTapAtPoint(${x}, ${y}, 0.12, () => { });`, resolve);
    131121        });
    132122    }
     
    154144                    uiController.uiScriptComplete(uiController.zoomScale);
    155145                };
    156                 uiController.doubleTapAtPoint(${x}, ${y}, () => {});`, resolve);
     146                uiController.doubleTapAtPoint(${x}, ${y}, 0, () => { });`, resolve);
    157147        });
    158148    }
  • trunk/Source/WebKit/ChangeLog

    r249110 r249112  
     12019-08-26  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        REGRESSION (iOS 13): Tests that simulate multiple back-to-back single taps fail or time out
     4        https://bugs.webkit.org/show_bug.cgi?id=201129
     5        <rdar://problem/51857277>
     6
     7        Reviewed by Tim Horton.
     8
     9        Adds a new SPI hook in WebKit to let clients know when a synthetic tap gesture that has ended has been reset.
     10        See Tools/ChangeLog and LayoutTests/ChangeLog for more details.
     11
     12        * UIProcess/API/Cocoa/WKWebView.mm:
     13        (-[WKWebView _doAfterResettingSingleTapGesture:]):
     14        * UIProcess/API/Cocoa/WKWebViewPrivate.h:
     15        * UIProcess/ios/WKContentViewInteraction.h:
     16        * UIProcess/ios/WKContentViewInteraction.mm:
     17        (-[WKContentView _singleTapDidReset:]):
     18        (-[WKContentView _doAfterResettingSingleTapGesture:]):
     19
    1202019-08-26  Brent Fulgham  <bfulgham@apple.com>
    221
  • trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm

    r249093 r249112  
    70037003}
    70047004
     7005- (void)_doAfterResettingSingleTapGesture:(dispatch_block_t)action
     7006{
     7007    [_contentView _doAfterResettingSingleTapGesture:action];
     7008}
     7009
    70057010- (void)_doAfterReceivingEditDragSnapshotForTesting:(dispatch_block_t)action
    70067011{
  • trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h

    r247490 r249112  
    482482- (void)_didDismissForcePressPreview WK_API_AVAILABLE(ios(10.3));
    483483- (void)_doAfterNextStablePresentationUpdate:(dispatch_block_t)updateBlock WK_API_AVAILABLE(ios(10.3));
     484- (void)_doAfterResettingSingleTapGesture:(dispatch_block_t)action WK_API_AVAILABLE(ios(WK_IOS_TBA));
    484485
    485486@property (nonatomic, readonly) NSArray<NSValue *> *_uiTextSelectionRects WK_API_AVAILABLE(ios(10.3));
  • trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h

    r249093 r249112  
    386386    std::unique_ptr<WebKit::TextCheckingController> _textCheckingController;
    387387#endif
     388
     389    Vector<BlockPtr<void()>> _actionsToPerformAfterResettingSingleTapGestureRecognizer;
    388390}
    389391
     
    556558- (void)setTimePickerValueToHour:(NSInteger)hour minute:(NSInteger)minute;
    557559- (NSDictionary *)_contentsOfUserInterfaceItem:(NSString *)userInterfaceItem;
     560- (void)_doAfterResettingSingleTapGesture:(dispatch_block_t)action;
    558561- (void)_doAfterReceivingEditDragSnapshotForTesting:(dispatch_block_t)action;
    559562
  • trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm

    r249093 r249112  
    24512451    }
    24522452#endif
     2453    auto actionsToPerform = std::exchange(_actionsToPerformAfterResettingSingleTapGestureRecognizer, { });
     2454    for (auto action : actionsToPerform)
     2455        action();
    24532456}
    24542457
     
    75427545@implementation WKContentView (WKTesting)
    75437546
     7547- (void)_doAfterResettingSingleTapGesture:(dispatch_block_t)action
     7548{
     7549    if ([_singleTapGestureRecognizer state] != UIGestureRecognizerStateEnded) {
     7550        action();
     7551        return;
     7552    }
     7553    _actionsToPerformAfterResettingSingleTapGestureRecognizer.append(makeBlockPtr(action));
     7554}
     7555
    75447556- (void)_doAfterReceivingEditDragSnapshotForTesting:(dispatch_block_t)action
    75457557{
  • trunk/Tools/ChangeLog

    r249111 r249112  
     12019-08-26  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        REGRESSION (iOS 13): Tests that simulate multiple back-to-back single taps fail or time out
     4        https://bugs.webkit.org/show_bug.cgi?id=201129
     5        <rdar://problem/51857277>
     6
     7        Reviewed by Tim Horton.
     8
     9        The tests in editing/pasteboard/ios were timing out on iOS 13 before this change. This is because they simulate
     10        back-to-back single taps; while this is recognized as two single taps on iOS 12 and prior, only the first single
     11        tap is recognized on iOS 13 (and the second is simply dropped on the floor). This occurs because the synthetic
     12        single tap gesture is reset slightly later on iOS 13 compared to iOS 12, so when the second tap is dispatched,
     13        the gesture recognizer is still in "ended" state after the first tap on iOS 13, which means the gesture isn't
     14        capable of recognizing further touches yet.
     15
     16        In UIKit, a gesture recognizer is only reset once its UIGestureEnvironment's containing dependency subgraph no
     17        longer contains gestures that are active. In iOS 12, the synthetic click gesture is a part of a dependency
     18        subgraph that contains only itself and the normal (blocking) double tap gesture which requires the click to fail
     19        before it can be recognized; immediately after simulating the tap, both these gestures are inactive, which
     20        allows both of them to be reset.
     21
     22        However, in iOS 13, the synthetic click gesture is part of a gesture dependency graph that contains the double
     23        tap for double click gesture, as well as the non-blocking double tap gesture, both of which are still active
     24        immediately after sending the first tap. This change in dependencies is caused by the introduction of
     25        UIUndoGestureInteraction's single and double three-finger tap gestures, which (in -[UIUndoGestureInteraction
     26        gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:]) explicitly add all other taps as failure
     27        requirements. This effectively links the synthetic single tap gesture to most of the other gestures in
     28        WKContentView's dependency graph by way of these tap gestures for the undo interaction.
     29
     30        All this means that there is now a short (~50 ms) delay after the synthetic single tap gestures is recognized,
     31        before it can be recognized again. To account for this new delay in our test infrastructure, simply wait for
     32        single tap gestures that have ended to reset before attempting to send subsequent single taps. We do this by
     33        introducing WebKit testing SPI to invoke a completion handler after resetting the synthetic click gesture (only
     34        if necessary - i.e., if the gesture is in ended state when we are about to begin simulating the tap). This
     35        allows calls to `UIScriptController::singleTapAtPoint` to be reliably recognized as single taps without
     36        requiring arbitrary 120 ms "human speed" delays.
     37
     38        This fixes a number of flaky or failing layout tests, including the tests in editing/pasteboard/ios.
     39
     40        * TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
     41        * TestRunnerShared/UIScriptContext/UIScriptController.h:
     42        (WTR::UIScriptController::doubleTapAtPoint):
     43
     44        Add a `delay` parameter to `doubleTapAtPoint`. A number of layout tests were actually simulating double click
     45        gestures by simulating two back-to-back single taps; this is done for the purposes of being able to add a "human
     46        speed" delay prior to the second single tap gesture. After the change to wait for the single tap gesture to
     47        reset before attempting to simulate the next tap, this strategy no longer works, since the second gesture is
     48        recognized only as a single tap instead of a double tap.
     49
     50        Instead, we add a delay parameter to `UIScriptController::doubleTapAtPoint`, which the "human speed" double tap
     51        gestures use instead to wait after simulating the first tap.
     52
     53        * WebKitTestRunner/ios/HIDEventGenerator.h:
     54        * WebKitTestRunner/ios/HIDEventGenerator.mm:
     55        (-[HIDEventGenerator _waitFor:]):
     56        (-[HIDEventGenerator sendTaps:location:withNumberOfTouches:delay:completionBlock:]):
     57
     58        Plumb the tap gesture delay through to this helper method.
     59
     60        (-[HIDEventGenerator tap:completionBlock:]):
     61        (-[HIDEventGenerator doubleTap:delay:completionBlock:]):
     62        (-[HIDEventGenerator twoFingerTap:completionBlock:]):
     63        (-[HIDEventGenerator sendTaps:location:withNumberOfTouches:completionBlock:]): Deleted.
     64        (-[HIDEventGenerator doubleTap:completionBlock:]): Deleted.
     65        * WebKitTestRunner/ios/UIScriptControllerIOS.h:
     66        * WebKitTestRunner/ios/UIScriptControllerIOS.mm:
     67        (WTR::UIScriptControllerIOS::waitForSingleTapToReset const):
     68
     69        Add a new helper to wait for the content view's single tap gesture to reset if needed; call this before
     70        attempting to simulate single taps (either using a stylus, or with a regular touch).
     71
     72        (WTR::UIScriptControllerIOS::singleTapAtPointWithModifiers):
     73        (WTR::UIScriptControllerIOS::doubleTapAtPoint):
     74        (WTR::UIScriptControllerIOS::stylusTapAtPointWithModifiers):
     75
    1762019-08-26  Jonathan Bedard  <jbedard@apple.com>
    277
  • trunk/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl

    r248531 r249112  
    5858    void singleTapAtPoint(long x, long y, object callback);
    5959    void singleTapAtPointWithModifiers(long x, long y, object modifierArray, object callback);
    60     void doubleTapAtPoint(long x, long y, object callback);
     60    void doubleTapAtPoint(long x, long y, float delay, object callback);
    6161    void dragFromPointToPoint(long startX, long startY, long endX, long endY, double durationSeconds, object callback);
    6262
  • trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h

    r248531 r249112  
    136136    virtual void singleTapAtPoint(long x, long y, JSValueRef callback) { notImplemented(); }
    137137    virtual void singleTapAtPointWithModifiers(long x, long y, JSValueRef modifierArray, JSValueRef callback) { notImplemented(); }
    138     virtual void doubleTapAtPoint(long x, long y, JSValueRef callback) { notImplemented(); }
     138    virtual void doubleTapAtPoint(long x, long y, float delay, JSValueRef callback) { notImplemented(); }
    139139    virtual void dragFromPointToPoint(long startX, long startY, long endX, long endY, double durationSeconds, JSValueRef callback) { notImplemented(); }
    140140    virtual void longPressAtPoint(long x, long y, JSValueRef callback) { notImplemented(); }
  • trunk/Tools/WebKitTestRunner/ios/HIDEventGenerator.h

    r244955 r249112  
    8888// Taps
    8989- (void)tap:(CGPoint)location completionBlock:(void (^)(void))completionBlock;
    90 - (void)doubleTap:(CGPoint)location completionBlock:(void (^)(void))completionBlock;
     90- (void)doubleTap:(CGPoint)location delay:(NSTimeInterval)delay completionBlock:(void (^)(void))completionBlock;
    9191- (void)twoFingerTap:(CGPoint)location completionBlock:(void (^)(void))completionBlock;
    9292
  • trunk/Tools/WebKitTestRunner/ios/HIDEventGenerator.mm

    r245161 r249112  
    713713}
    714714
    715 - (void)sendTaps:(int)tapCount location:(CGPoint)location withNumberOfTouches:(int)touchCount completionBlock:(void (^)(void))completionBlock
     715- (void)_waitFor:(NSTimeInterval)delay
     716{
     717    if (delay <= 0)
     718        return;
     719
     720    bool doneWaitingForDelay = false;
     721    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), [&doneWaitingForDelay] {
     722        doneWaitingForDelay = true;
     723    });
     724
     725    while (!doneWaitingForDelay)
     726        [NSRunLoop.currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:NSDate.distantFuture];
     727}
     728
     729- (void)sendTaps:(int)tapCount location:(CGPoint)location withNumberOfTouches:(int)touchCount delay:(NSTimeInterval)delay completionBlock:(void (^)(void))completionBlock
    716730{
    717731    struct timespec doubleDelay = { 0, static_cast<long>(multiTapInterval * nanosecondsPerSecond) };
     
    724738        if (i + 1 != tapCount)
    725739            nanosleep(&doubleDelay, 0);
     740
     741        [self _waitFor:delay];
    726742    }
    727743   
     
    731747- (void)tap:(CGPoint)location completionBlock:(void (^)(void))completionBlock
    732748{
    733     [self sendTaps:1 location:location withNumberOfTouches:1 completionBlock:completionBlock];
    734 }
    735 
    736 - (void)doubleTap:(CGPoint)location completionBlock:(void (^)(void))completionBlock
    737 {
    738     [self sendTaps:2 location:location withNumberOfTouches:1 completionBlock:completionBlock];
     749    [self sendTaps:1 location:location withNumberOfTouches:1 delay:0 completionBlock:completionBlock];
     750}
     751
     752- (void)doubleTap:(CGPoint)location delay:(NSTimeInterval)delay completionBlock:(void (^)(void))completionBlock
     753{
     754    [self sendTaps:2 location:location withNumberOfTouches:1 delay:delay completionBlock:completionBlock];
    739755}
    740756
    741757- (void)twoFingerTap:(CGPoint)location completionBlock:(void (^)(void))completionBlock
    742758{
    743     [self sendTaps:1 location:location withNumberOfTouches:2 completionBlock:completionBlock];
     759    [self sendTaps:1 location:location withNumberOfTouches:2 delay:0 completionBlock:completionBlock];
    744760}
    745761
  • trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.h

    r248531 r249112  
    5353    void singleTapAtPoint(long x, long y, JSValueRef) override;
    5454    void singleTapAtPointWithModifiers(long x, long y, JSValueRef modifierArray, JSValueRef) override;
    55     void doubleTapAtPoint(long x, long y, JSValueRef) override;
     55    void doubleTapAtPoint(long x, long y, float delay, JSValueRef) override;
    5656    void stylusDownAtPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef) override;
    5757    void stylusMoveToPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef) override;
     
    140140    void setDidEndScrollingCallback(JSValueRef) override;
    141141    void clearAllCallbacks() override;
     142
     143private:
     144    void waitForSingleTapToReset() const;
    142145};
    143146
  • trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm

    r248531 r249112  
    264264}
    265265
     266void UIScriptControllerIOS::waitForSingleTapToReset() const
     267{
     268    bool doneWaitingForSingleTapToReset = false;
     269    [webView() _doAfterResettingSingleTapGesture:[&doneWaitingForSingleTapToReset] {
     270        doneWaitingForSingleTapToReset = true;
     271    }];
     272    TestController::singleton().runUntil(doneWaitingForSingleTapToReset, 0.5_s);
     273}
     274
    266275void UIScriptControllerIOS::singleTapAtPointWithModifiers(long x, long y, JSValueRef modifierArray, JSValueRef callback)
    267276{
    268277    unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
     278
     279    waitForSingleTapToReset();
    269280
    270281    auto modifierFlags = parseModifierArray(m_context->jsContext(), modifierArray);
     
    287298}
    288299
    289 void UIScriptControllerIOS::doubleTapAtPoint(long x, long y, JSValueRef callback)
    290 {
    291     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
    292 
    293     [[HIDEventGenerator sharedHIDEventGenerator] doubleTap:globalToContentCoordinates(webView(), x, y) completionBlock:^{
     300void UIScriptControllerIOS::doubleTapAtPoint(long x, long y, float delay, JSValueRef callback)
     301{
     302    unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
     303
     304    [[HIDEventGenerator sharedHIDEventGenerator] doubleTap:globalToContentCoordinates(webView(), x, y) delay:delay completionBlock:^{
    294305        if (!m_context)
    295306            return;
     
    342353{
    343354    unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
     355
     356    waitForSingleTapToReset();
    344357
    345358    auto modifierFlags = parseModifierArray(m_context->jsContext(), modifierArray);
Note: See TracChangeset for help on using the changeset viewer.