Changeset 261480 in webkit


Ignore:
Timestamp:
May 11, 2020 9:22:56 AM (4 years ago)
Author:
Wenson Hsieh
Message:

REGRESSION (r253267): issues on touchstart/touchend/touchmove (pointerdown/pointerup/pointermove) events
https://bugs.webkit.org/show_bug.cgi?id=211521
<rdar://problem/62942374>

Reviewed by Darin Adler.

Source/WebKit:

As a brief refresher, deferring gesture recognizers allow us to handle otherwise blocking touch events
asynchronously by having all preventable native gesture recognizers require the deferring gesture recognizer to
fail; we only fail the deferring gesture recognizer once the web process has handled the touch event, and did
not call preventDefault().

These additional failure requirements can cause preventable gestures to be linked together in the same gesture
dependency subgraph; since each subgraph is reset only once all gestures in the subgraph have failed or ended,
this might cause some gestures to be reset after a delay (rather than being reset immediately). To mitigate
this, we divide the set of preventable gestures into multiple (currently, 2) subgraphs: one for gestures that
are reset after a delay, and another for gestures that are immediately resettable. This way, immediately
resettable gestures are able to reset and recognize again, without having to wait for other slower preventable
gesture recognizers to reset.

When fast-clicking is disabled (e.g. when loading a desktop web page on a mobile form factor, or when the
viewport has been zoomed in), the blocking synthetic double tap gesture recognizer (that is, WKContentView's
_doubleTapGestureRecognizer) is enabled, and adds itself as a dynamic failure requirement to the content
view's synthetic single tap gesture recognizer (_singleTapGestureRecognizer). In terms of the gesture
dependency graph, this causes the single tap gesture to form an edge with the double tap gesture, which ends up
uniting both deferring gesture recognizers under the same subgraph. This means UIWebTouchEventsGestureRecognizer,
which should be one of the gestures in the immediately resettable subgraph, is now connected to the rest of the
delayed resettable gestures, meaning that it cannot recognize until "slowly resettable" gestures such as the
tap-and-half text selection gesture have also been reset. This delay causes touch events to be dropped, as is
the case in this bug.

To fix this, simply quarantine the single tap and double tap gestures inside their own subgraph by introducing a
separate deferring gesture recognizer for them. When fast-clicking is enabled, this does not hinder the ability
for the single tap gesture to fire in rapid succession, since the double tap gesture is disabled (and thus, not
a part of the graph at all). when fast-clicking is disabled, then the double tap gesture will prevent the single
tap gesture from being immediately reset anyways, due to the direct failure requirement between the double and
single tap gesture.

Doing this ensures that no other immediately resettable gesture (UIWebTouchEventsGestureRecognizer included)
is accidentally blocked from immediately resetting due to being linked to the delayed resettable gestures by way
of the synthetic single and double tap gestures.

Test: fast/events/touch/ios/tap-and-half-when-viewport-is-not-responsive.html

  • UIProcess/ios/WKContentViewInteraction.h:

Add a dedicated deferring gesture recognizer for the synthetic single tap and double tap gesture recognizers.

  • UIProcess/ios/WKContentViewInteraction.mm:

(-[WKContentView setUpInteraction]):
(-[WKContentView cleanUpInteraction]):

Use -_deferringGestureRecognizers instead of hard-coding logic for each deferring gesture.

(-[WKContentView _removeDefaultGestureRecognizers]): Ditto.
(-[WKContentView _addDefaultGestureRecognizers]): Ditto.
(-[WKContentView _deferringGestureRecognizers]):

We now have 3 distinct deferring gestures; instead of handling the three deferring gestures individually in
various places in this file, group them all behind a getter that returns an array of deferring gestures, and use
this instead.

(-[WKContentView _doneDeferringNativeGestures:]): Ditto.
(-[WKContentView gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:]): Ditto.
(-[WKContentView deferringGestureRecognizer:shouldDeferOtherGestureRecognizer:]):

Partition the synthetic single tap and double tap gestures into their own subgraph.

LayoutTests:

Add a layout test that synthesizes a tap-and-half gesture over an element with active touch event listeners, and
verifies that the second half of the gesture (i.e. the pan gesture) dispatches touchstart, touchmove, and
touchend events.

  • fast/events/touch/ios/tap-and-half-when-viewport-is-not-responsive-expected.txt: Added.
  • fast/events/touch/ios/tap-and-half-when-viewport-is-not-responsive.html: Added.
Location:
trunk
Files:
2 added
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r261477 r261480  
     12020-05-11  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        REGRESSION (r253267): issues on touchstart/touchend/touchmove (pointerdown/pointerup/pointermove) events
     4        https://bugs.webkit.org/show_bug.cgi?id=211521
     5        <rdar://problem/62942374>
     6
     7        Reviewed by Darin Adler.
     8
     9        Add a layout test that synthesizes a tap-and-half gesture over an element with active touch event listeners, and
     10        verifies that the second half of the gesture (i.e. the pan gesture) dispatches touchstart, touchmove, and
     11        touchend events.
     12
     13        * fast/events/touch/ios/tap-and-half-when-viewport-is-not-responsive-expected.txt: Added.
     14        * fast/events/touch/ios/tap-and-half-when-viewport-is-not-responsive.html: Added.
     15
    1162020-05-11  Jason Lawrence  <lawrence.j@apple.com>
    217
  • trunk/Source/WebKit/ChangeLog

    r261479 r261480  
     12020-05-11  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        REGRESSION (r253267): issues on touchstart/touchend/touchmove (pointerdown/pointerup/pointermove) events
     4        https://bugs.webkit.org/show_bug.cgi?id=211521
     5        <rdar://problem/62942374>
     6
     7        Reviewed by Darin Adler.
     8
     9        As a brief refresher, deferring gesture recognizers allow us to handle otherwise blocking touch events
     10        asynchronously by having all preventable native gesture recognizers require the deferring gesture recognizer to
     11        fail; we only fail the deferring gesture recognizer once the web process has handled the touch event, and did
     12        not call `preventDefault()`.
     13
     14        These additional failure requirements can cause preventable gestures to be linked together in the same gesture
     15        dependency subgraph; since each subgraph is reset only once all gestures in the subgraph have failed or ended,
     16        this might cause some gestures to be reset after a delay (rather than being reset immediately). To mitigate
     17        this, we divide the set of preventable gestures into multiple (currently, 2) subgraphs: one for gestures that
     18        are reset after a delay, and another for gestures that are immediately resettable. This way, immediately
     19        resettable gestures are able to reset and recognize again, without having to wait for other slower preventable
     20        gesture recognizers to reset.
     21
     22        When fast-clicking is disabled (e.g. when loading a desktop web page on a mobile form factor, or when the
     23        viewport has been zoomed in), the blocking synthetic double tap gesture recognizer (that is, `WKContentView`'s
     24        `_doubleTapGestureRecognizer`) is enabled, and adds itself as a dynamic failure requirement to the content
     25        view's synthetic single tap gesture recognizer (`_singleTapGestureRecognizer`). In terms of the gesture
     26        dependency graph, this causes the single tap gesture to form an edge with the double tap gesture, which ends up
     27        uniting both deferring gesture recognizers under the same subgraph. This means UIWebTouchEventsGestureRecognizer,
     28        which should be one of the gestures in the immediately resettable subgraph, is now connected to the rest of the
     29        delayed resettable gestures, meaning that it cannot recognize until "slowly resettable" gestures such as the
     30        tap-and-half text selection gesture have also been reset. This delay causes touch events to be dropped, as is
     31        the case in this bug.
     32
     33        To fix this, simply quarantine the single tap and double tap gestures inside their own subgraph by introducing a
     34        separate deferring gesture recognizer for them. When fast-clicking is enabled, this does not hinder the ability
     35        for the single tap gesture to fire in rapid succession, since the double tap gesture is disabled (and thus, not
     36        a part of the graph at all). when fast-clicking is disabled, then the double tap gesture will prevent the single
     37        tap gesture from being immediately reset anyways, due to the direct failure requirement between the double and
     38        single tap gesture.
     39
     40        Doing this ensures that no other immediately resettable gesture (`UIWebTouchEventsGestureRecognizer` included)
     41        is accidentally blocked from immediately resetting due to being linked to the delayed resettable gestures by way
     42        of the synthetic single and double tap gestures.
     43
     44        Test: fast/events/touch/ios/tap-and-half-when-viewport-is-not-responsive.html
     45
     46        * UIProcess/ios/WKContentViewInteraction.h:
     47
     48        Add a dedicated deferring gesture recognizer for the synthetic single tap and double tap gesture recognizers.
     49
     50        * UIProcess/ios/WKContentViewInteraction.mm:
     51        (-[WKContentView setUpInteraction]):
     52        (-[WKContentView cleanUpInteraction]):
     53
     54        Use -_deferringGestureRecognizers instead of hard-coding logic for each deferring gesture.
     55
     56        (-[WKContentView _removeDefaultGestureRecognizers]): Ditto.
     57        (-[WKContentView _addDefaultGestureRecognizers]): Ditto.
     58        (-[WKContentView _deferringGestureRecognizers]):
     59
     60        We now have 3 distinct deferring gestures; instead of handling the three deferring gestures individually in
     61        various places in this file, group them all behind a getter that returns an array of deferring gestures, and use
     62        this instead.
     63
     64        (-[WKContentView _doneDeferringNativeGestures:]): Ditto.
     65        (-[WKContentView gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:]): Ditto.
     66        (-[WKContentView deferringGestureRecognizer:shouldDeferOtherGestureRecognizer:]):
     67
     68        Partition the synthetic single tap and double tap gestures into their own subgraph.
     69
    1702020-05-11  Per Arne Vollan  <pvollan@apple.com>
    271
  • trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h

    r261457 r261480  
    212212    RetainPtr<WKDeferringGestureRecognizer> _deferringGestureRecognizerForImmediatelyResettableGestures;
    213213    RetainPtr<WKDeferringGestureRecognizer> _deferringGestureRecognizerForDelayedResettableGestures;
     214    RetainPtr<WKDeferringGestureRecognizer> _deferringGestureRecognizerForSyntheticTapGestures;
    214215#endif
    215216    RetainPtr<UIWebTouchEventsGestureRecognizer> _touchEventGestureRecognizer;
  • trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm

    r261457 r261480  
    762762    _deferringGestureRecognizerForImmediatelyResettableGestures = adoptNS([[WKDeferringGestureRecognizer alloc] initWithDeferringGestureDelegate:self]);
    763763    [_deferringGestureRecognizerForImmediatelyResettableGestures setName:@"Touch event deferrer (immediate reset)"];
    764     [_deferringGestureRecognizerForImmediatelyResettableGestures setDelegate:self];
    765     [self addGestureRecognizer:_deferringGestureRecognizerForImmediatelyResettableGestures.get()];
    766764
    767765    _deferringGestureRecognizerForDelayedResettableGestures = adoptNS([[WKDeferringGestureRecognizer alloc] initWithDeferringGestureDelegate:self]);
    768766    [_deferringGestureRecognizerForDelayedResettableGestures setName:@"Touch event deferrer (delayed reset)"];
    769     [_deferringGestureRecognizerForDelayedResettableGestures setDelegate:self];
    770     [self addGestureRecognizer:_deferringGestureRecognizerForDelayedResettableGestures.get()];
     767
     768    _deferringGestureRecognizerForSyntheticTapGestures = adoptNS([[WKDeferringGestureRecognizer alloc] initWithDeferringGestureDelegate:self]);
     769    [_deferringGestureRecognizerForSyntheticTapGestures setName:@"Touch event deferrer (synthetic tap)"];
     770
     771    for (WKDeferringGestureRecognizer *gesture in self._deferringGestureRecognizers) {
     772        gesture.delegate = self;
     773        [self addGestureRecognizer:gesture];
     774    }
    771775#endif
    772776
     
    935939
    936940#if ENABLE(IOS_TOUCH_EVENTS)
    937     [_deferringGestureRecognizerForImmediatelyResettableGestures setDelegate:nil];
    938     [self removeGestureRecognizer:_deferringGestureRecognizerForImmediatelyResettableGestures.get()];
    939 
    940     [_deferringGestureRecognizerForDelayedResettableGestures setDelegate:nil];
    941     [self removeGestureRecognizer:_deferringGestureRecognizerForDelayedResettableGestures.get()];
     941    for (WKDeferringGestureRecognizer *gesture in self._deferringGestureRecognizers) {
     942        gesture.delegate = nil;
     943        [self removeGestureRecognizer:gesture];
     944    }
    942945#endif
    943946
     
    10541057{
    10551058#if ENABLE(IOS_TOUCH_EVENTS)
    1056     [self removeGestureRecognizer:_deferringGestureRecognizerForImmediatelyResettableGestures.get()];
    1057     [self removeGestureRecognizer:_deferringGestureRecognizerForDelayedResettableGestures.get()];
     1059    for (WKDeferringGestureRecognizer *gesture in self._deferringGestureRecognizers)
     1060        [self removeGestureRecognizer:gesture];
    10581061#endif
    10591062    [self removeGestureRecognizer:_touchEventGestureRecognizer.get()];
     
    10821085{
    10831086#if ENABLE(IOS_TOUCH_EVENTS)
    1084     [self addGestureRecognizer:_deferringGestureRecognizerForImmediatelyResettableGestures.get()];
    1085     [self addGestureRecognizer:_deferringGestureRecognizerForDelayedResettableGestures.get()];
     1087    for (WKDeferringGestureRecognizer *gesture in self._deferringGestureRecognizers)
     1088        [self addGestureRecognizer:gesture];
    10861089#endif
    10871090    [self addGestureRecognizer:_touchEventGestureRecognizer.get()];
     
    16571660#if ENABLE(IOS_TOUCH_EVENTS)
    16581661
     1662- (NSArray<WKDeferringGestureRecognizer *> *)_deferringGestureRecognizers
     1663{
     1664    WKDeferringGestureRecognizer *recognizers[3];
     1665    NSUInteger count = 0;
     1666    auto add = [&] (const RetainPtr<WKDeferringGestureRecognizer>& recognizer) {
     1667        if (recognizer)
     1668            recognizers[count++] = recognizer.get();
     1669    };
     1670    add(_deferringGestureRecognizerForImmediatelyResettableGestures);
     1671    add(_deferringGestureRecognizerForDelayedResettableGestures);
     1672    add(_deferringGestureRecognizerForSyntheticTapGestures);
     1673    return [NSArray arrayWithObjects:recognizers count:count];
     1674}
     1675
    16591676- (void)_doneDeferringNativeGestures:(BOOL)preventNativeGestures
    16601677{
    1661     [_deferringGestureRecognizerForImmediatelyResettableGestures setDefaultPrevented:preventNativeGestures];
    1662     [_deferringGestureRecognizerForDelayedResettableGestures setDefaultPrevented:preventNativeGestures];
    1663 }
    1664 
    1665 #endif
     1678    for (WKDeferringGestureRecognizer *gesture in self._deferringGestureRecognizers)
     1679        [gesture setDefaultPrevented:preventNativeGestures];
     1680}
     1681
     1682#endif // ENABLE(IOS_TOUCH_EVENTS)
    16661683
    16671684static NSValue *nsSizeForTapHighlightBorderRadius(WebCore::IntSize borderRadius, CGFloat borderRadiusScale)
     
    20422059{
    20432060#if ENABLE(IOS_TOUCH_EVENTS)
    2044     if (isSamePair(gestureRecognizer, otherGestureRecognizer, _touchEventGestureRecognizer.get(), _deferringGestureRecognizerForImmediatelyResettableGestures.get()))
    2045         return YES;
    2046 
    2047     if (isSamePair(gestureRecognizer, otherGestureRecognizer, _touchEventGestureRecognizer.get(), _deferringGestureRecognizerForDelayedResettableGestures.get()))
    2048         return YES;
     2061    for (WKDeferringGestureRecognizer *gesture in self._deferringGestureRecognizers) {
     2062        if (isSamePair(gestureRecognizer, otherGestureRecognizer, _touchEventGestureRecognizer.get(), gesture))
     2063            return YES;
     2064    }
    20492065#endif
    20502066
     
    71017117    };
    71027118
     7119    if (gestureRecognizer == _doubleTapGestureRecognizer || gestureRecognizer == _singleTapGestureRecognizer)
     7120        return deferringGestureRecognizer == _deferringGestureRecognizerForSyntheticTapGestures;
     7121
    71037122    if (mayDelayResetOfContainingSubgraph(gestureRecognizer))
    71047123        return deferringGestureRecognizer == _deferringGestureRecognizerForDelayedResettableGestures;
Note: See TracChangeset for help on using the changeset viewer.