- Revision
- 242564
- Author
- [email protected]
- Date
- 2019-03-06 13:05:07 -0800 (Wed, 06 Mar 2019)
Log Message
[iOS] Basic hit testing for content overlapping fast-scrollable overflow
https://bugs.webkit.org/show_bug.cgi?id=195360
Reviewed by Simon Fraser.
Source/WebKit:
* UIProcess/RemoteLayerTree/ios/RemoteLayerTreeHostIOS.mm:
(WebKit::RemoteLayerTreeHost::makeNode):
Use new UIView subclass for tiled layer hosting (so we know they have content even when contents property is nil).
* UIProcess/RemoteLayerTree/ios/RemoteLayerTreeViews.h:
* UIProcess/RemoteLayerTree/ios/RemoteLayerTreeViews.mm:
(collectDescendantViewsAtPoint):
Factor collection step into a function.
Do basic skipping of event-transparent layers.
(-[UIView _web_findDescendantViewAtPoint:withEvent:]):
To handle overlap cases, find the scroll view that has the deepest non-transparent view hit as its descendant.
(-[UIView _web_recursiveFindDescendantInteractibleViewAtPoint:withEvent:]): Deleted.
LayoutTests:
* fast/scrolling/ios/overflow-scroll-overlap-expected.txt: Added.
* fast/scrolling/ios/overflow-scroll-overlap.html: Added.
Modified Paths
Added Paths
Diff
Modified: trunk/LayoutTests/ChangeLog (242563 => 242564)
--- trunk/LayoutTests/ChangeLog 2019-03-06 20:45:54 UTC (rev 242563)
+++ trunk/LayoutTests/ChangeLog 2019-03-06 21:05:07 UTC (rev 242564)
@@ -1,3 +1,13 @@
+2019-03-06 Antti Koivisto <[email protected]>
+
+ [iOS] Basic hit testing for content overlapping fast-scrollable overflow
+ https://bugs.webkit.org/show_bug.cgi?id=195360
+
+ Reviewed by Simon Fraser.
+
+ * fast/scrolling/ios/overflow-scroll-overlap-expected.txt: Added.
+ * fast/scrolling/ios/overflow-scroll-overlap.html: Added.
+
2019-03-06 Joseph Pecoraro <[email protected]>
Web Inspector: CPU Usage Timeline - Statistics and Sources sections
Added: trunk/LayoutTests/fast/scrolling/ios/overflow-scroll-overlap-expected.txt (0 => 242564)
--- trunk/LayoutTests/fast/scrolling/ios/overflow-scroll-overlap-expected.txt (rev 0)
+++ trunk/LayoutTests/fast/scrolling/ios/overflow-scroll-overlap-expected.txt 2019-03-06 21:05:07 UTC (rev 242564)
@@ -0,0 +1,14 @@
+Test that overlapped, nested and clipped scrollable areas are correctly targeted.
+
+case 1: Scrollable 1
+case 2:
+case 3: Scrollable 3
+case 4: Scrollable 6
+case 5: Scrollable 8
+case 6: Scrollable 9
+case 7: Scrollable 12
+case 8: Scrollable 13
+case 9: Scrollable 15
+case 10:
+case 11:
+
Added: trunk/LayoutTests/fast/scrolling/ios/overflow-scroll-overlap.html (0 => 242564)
--- trunk/LayoutTests/fast/scrolling/ios/overflow-scroll-overlap.html (rev 0)
+++ trunk/LayoutTests/fast/scrolling/ios/overflow-scroll-overlap.html 2019-03-06 21:05:07 UTC (rev 242564)
@@ -0,0 +1,230 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<style>
+body {
+ touch-action: none;
+}
+.case {
+ width: 150px;
+ height: 150px;
+ display: inline-block;
+ position: relative;
+}
+.scrollcontent {
+ width: 500px;
+ height: 500px;
+ background: green;
+}
+
+.overflowscroll {
+ overflow: scroll;
+ height: 100px;
+ width: 100px;
+ position: absolute;
+ border: 2px solid black;
+}
+.overlapping {
+ position:absolute;
+ left: 25px;
+ top: 25px;
+ width: 100px;
+ height: 100px;
+ background: red;
+}
+.clip {
+ position:absolute;
+ width: 100px;
+ height: 100px;
+ overflow:hidden;
+}
+.large {
+ width: 3000px;
+ height: 150px;
+}
+#log {
+ position:relative;
+ white-space: pre;
+}
+</style>
+<script src=""
+<script type="text/_javascript_">
+if (window.testRunner) {
+ testRunner.dumpAsText();
+ testRunner.waitUntilDone();
+ internals.settings.setAsyncFrameScrollingEnabled(true);
+ internals.settings.setAsyncOverflowScrollingEnabled(true);
+}
+
+function sleep(delay)
+{
+ return new Promise((resolve) => { setTimeout(resolve, delay); });
+}
+
+async function runTest() {
+ for (const scrollable of document.querySelectorAll('.overflowscroll')) {
+ scrollable.addEventListener('scroll', function(e) {
+ logScroll(e.target);
+ });
+ }
+
+ {
+ let i = 0;
+ for (const scrollcontent of document.querySelectorAll('.scrollcontent'))
+ scrollcontent.innerText = "Scrollable " + ++i;
+ }
+ {
+ let i = 0;
+ for (const overlapping of document.querySelectorAll('.overlapping'))
+ overlapping.innerText = "Overlapping " + ++i;
+ }
+
+
+ if (!window.testRunner || !testRunner.runUIScript)
+ return;
+
+ for (const testcase of document.querySelectorAll('.case'))
+ testcase.style.display = 'none';
+
+ {
+ let i = 0;
+ for (const testcase of document.querySelectorAll('.case')) {
+ ++i;
+ testcase.style.display = 'inline-block';
+
+ const target = testcase.querySelector('.target');
+ const rect = target.getBoundingClientRect();
+ const centerX = (rect.left + rect.right) / 2;
+ const centerY = (rect.top + rect.bottom) / 2;
+ await touchAndDragFromPointToPoint(centerX, centerY, centerX, centerY - 30);
+ await liftUpAtPoint(centerX, centerY - 30);
+ await sleep(500);
+
+ testcase.style.display = 'none';
+ outputCase(i);
+ }
+ }
+
+ for (const testcase of document.querySelectorAll('.case'))
+ testcase.style.display = 'none';
+
+ testRunner.notifyDone();
+}
+
+const scrolledElements = new Set();
+
+function logScroll(element) {
+ if (scrolledElements.has(element))
+ return;
+ scrolledElements.add(element);
+}
+
+function outputCase(i) {
+ log.innerText += "case " + i + ": ";
+ for (const scrolled of scrolledElements)
+ log.innerText += scrolled.getElementsByClassName("scrollcontent")[0].innerText + " ";
+ log.innerText += "\n";
+ scrolledElements.clear();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+<p>
+Test that overlapped, nested and clipped scrollable areas are correctly targeted.
+</p>
+<div class="case">
+ <div class="overflowscroll target" style="z-index:1">
+ <div class="scrollcontent"></div>
+ </div>
+ <div class="overlapping"></div>
+</div>
+
+<div class="case">
+ <div class="overflowscroll target">
+ <div class="scrollcontent"></div>
+ </div>
+ <div class="overlapping" style="z-index:1"></div>
+</div>
+
+<div class="case ">
+ <div class="overflowscroll target" style="z-index:1">
+ <div class="scrollcontent"></div>
+ </div>
+ <div class="overflowscroll" style="left:20px; top:20px; z-index:0;">
+ <div class="scrollcontent"></div>
+ </div>
+</div>
+
+<div class="case">
+ <div class="overflowscroll target">
+ <div class="scrollcontent"></div>
+ </div>
+ <div class="overflowscroll" style="left:20px; top:20px; z-index:1;">
+ <div class="scrollcontent"></div>
+ </div>
+</div>
+
+<div class="case">
+ <div class="overflowscroll target">
+ <div class="scrollcontent" style="z-index:1"></div>
+ <div class="overflowscroll" style="left:20px; top:20px;">
+ <div class="scrollcontent"></div>
+ </div>
+ </div>
+</div>
+
+<div class="case">
+ <div class="overflowscroll target">
+ <div class="scrollcontent" style="z-index:1"></div>
+ <div class="overflowscroll" style="left:60px; top:60px;">
+ <div class="scrollcontent"></div>
+ </div>
+ </div>
+</div>
+
+<div class="case">
+ <div class="overflowscroll target">
+ <div class="scrollcontent large" style="z-index:1"></div>
+ <div class="overflowscroll" style="left:20px; top:20px;">
+ <div class="scrollcontent large"></div>
+ </div>
+ </div>
+</div>
+
+<div class="case">
+ <div class="overflowscroll target">
+ <div class="scrollcontent large" style="z-index:1"></div>
+ <div class="overflowscroll" style="left:60px; top:60px;">
+ <div class="scrollcontent large"></div>
+ </div>
+ </div>
+</div>
+
+<div class="case">
+ <div class="clip" style="left:20px; top:20px;">
+ <div class="overflowscroll target" style="left:-20px; top:-20px;">
+ <div class="scrollcontent"></div>
+ </div>
+ </div>
+</div>
+
+<div class="case">
+ <div class="clip" style="left:60px; top:60px;">
+ <div class="overflowscroll target" style="left:-60px; top:-60px;">
+ <div class="scrollcontent"></div>
+ </div>
+ </div>
+</div>
+
+<div class="case">
+ <div class="overflowscroll target">
+ <div class="scrollcontent"></div>
+ </div>
+ <div class="overlapping large" style="z-index:1"></div>
+</div>
+
+<div id=log></div>
+
+</body>
+</html>
Modified: trunk/Source/WebKit/ChangeLog (242563 => 242564)
--- trunk/Source/WebKit/ChangeLog 2019-03-06 20:45:54 UTC (rev 242563)
+++ trunk/Source/WebKit/ChangeLog 2019-03-06 21:05:07 UTC (rev 242564)
@@ -1,3 +1,28 @@
+2019-03-06 Antti Koivisto <[email protected]>
+
+ [iOS] Basic hit testing for content overlapping fast-scrollable overflow
+ https://bugs.webkit.org/show_bug.cgi?id=195360
+
+ Reviewed by Simon Fraser.
+
+ * UIProcess/RemoteLayerTree/ios/RemoteLayerTreeHostIOS.mm:
+ (WebKit::RemoteLayerTreeHost::makeNode):
+
+ Use new UIView subclass for tiled layer hosting (so we know they have content even when contents property is nil).
+
+ * UIProcess/RemoteLayerTree/ios/RemoteLayerTreeViews.h:
+ * UIProcess/RemoteLayerTree/ios/RemoteLayerTreeViews.mm:
+ (collectDescendantViewsAtPoint):
+
+ Factor collection step into a function.
+ Do basic skipping of event-transparent layers.
+
+ (-[UIView _web_findDescendantViewAtPoint:withEvent:]):
+
+ To handle overlap cases, find the scroll view that has the deepest non-transparent view hit as its descendant.
+
+ (-[UIView _web_recursiveFindDescendantInteractibleViewAtPoint:withEvent:]): Deleted.
+
2019-03-06 Wenson Hsieh <[email protected]>
Move RenderObject::isTransparentOrFullyClippedRespectingParentFrames() to RenderLayer
Modified: trunk/Source/WebKit/UIProcess/RemoteLayerTree/ios/RemoteLayerTreeHostIOS.mm (242563 => 242564)
--- trunk/Source/WebKit/UIProcess/RemoteLayerTree/ios/RemoteLayerTreeHostIOS.mm 2019-03-06 20:45:54 UTC (rev 242563)
+++ trunk/Source/WebKit/UIProcess/RemoteLayerTree/ios/RemoteLayerTreeHostIOS.mm 2019-03-06 21:05:07 UTC (rev 242564)
@@ -72,9 +72,11 @@
case PlatformCALayer::LayerTypeWebLayer:
case PlatformCALayer::LayerTypeRootLayer:
case PlatformCALayer::LayerTypeSimpleLayer:
+ return makeAdoptingView([[WKCompositingView alloc] init]);
+
case PlatformCALayer::LayerTypeTiledBackingLayer:
case PlatformCALayer::LayerTypePageTiledBackingLayer:
- return makeAdoptingView([[WKCompositingView alloc] init]);
+ return makeAdoptingView([[WKTiledBackingView alloc] init]);
case PlatformCALayer::LayerTypeTiledBackingTileLayer:
return RemoteLayerTreeNode::createWithPlainLayer(properties.layerID);
Modified: trunk/Source/WebKit/UIProcess/RemoteLayerTree/ios/RemoteLayerTreeViews.h (242563 => 242564)
--- trunk/Source/WebKit/UIProcess/RemoteLayerTree/ios/RemoteLayerTreeViews.h 2019-03-06 20:45:54 UTC (rev 242563)
+++ trunk/Source/WebKit/UIProcess/RemoteLayerTree/ios/RemoteLayerTreeViews.h 2019-03-06 21:05:07 UTC (rev 242564)
@@ -40,6 +40,9 @@
@interface WKCompositingView : UIView
@end
+@interface WKTiledBackingView : WKCompositingView
+@end
+
@interface WKTransformView : WKCompositingView
@end
Modified: trunk/Source/WebKit/UIProcess/RemoteLayerTree/ios/RemoteLayerTreeViews.mm (242563 => 242564)
--- trunk/Source/WebKit/UIProcess/RemoteLayerTree/ios/RemoteLayerTreeViews.mm 2019-03-06 20:45:54 UTC (rev 242563)
+++ trunk/Source/WebKit/UIProcess/RemoteLayerTree/ios/RemoteLayerTreeViews.mm 2019-03-06 21:05:07 UTC (rev 242564)
@@ -34,50 +34,63 @@
#import <pal/spi/cocoa/QuartzCoreSPI.h>
#import <wtf/SoftLinking.h>
-@interface UIView (WKHitTesting)
-- (UIView *)_web_findDescendantViewAtPoint:(CGPoint)point withEvent:(UIEvent *)event;
-@end
-
-@implementation UIView (WKHitTesting)
-
-// UIView hit testing assumes that views should only hit test subviews that are entirely contained
-// in the view. This is not true of web content.
-// We only want to find views that allow native interaction here. Other views are ignored.
-- (UIView *)_web_recursiveFindDescendantInteractibleViewAtPoint:(CGPoint)point withEvent:(UIEvent *)event
+static void collectDescendantViewsAtPoint(Vector<UIView *, 16>& viewsAtPoint, UIView *parent, CGPoint point, UIEvent *event)
{
- if (self.clipsToBounds && ![self pointInside:point withEvent:event])
- return nil;
+ if (parent.clipsToBounds && ![parent pointInside:point withEvent:event])
+ return;
- __block UIView *foundView = nil;
- [[self subviews] enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
- CGPoint subviewPoint = [view convertPoint:point fromView:self];
+ for (UIView *view in [parent subviews]) {
+ CGPoint subviewPoint = [view convertPoint:point fromView:parent];
- if (view.isUserInteractionEnabled && [view pointInside:subviewPoint withEvent:event]) {
- if ([view conformsToProtocol:@protocol(WKNativelyInteractible)]) {
- foundView = view;
+ // FIXME: This doesn't cover all possible cases yet.
+ auto isTransparent = [&] {
+ if ([view isKindOfClass:[WKTiledBackingView class]])
+ return false;
+ if (![view isKindOfClass:[WKCompositingView class]])
+ return false;
+ if (view.layer.contents)
+ return false;
+ return true;
+ }();
- if (![view subviews])
- return;
+ if (!isTransparent && [view pointInside:subviewPoint withEvent:event])
+ viewsAtPoint.append(view);
- if (UIView *hitView = [view hitTest:subviewPoint withEvent:event])
- foundView = hitView;
- } else if ([view isKindOfClass:[WKChildScrollView class]])
- foundView = view;
- }
-
if (![view subviews])
return;
- if (UIView *hitView = [view _web_recursiveFindDescendantInteractibleViewAtPoint:subviewPoint withEvent:event])
- foundView = hitView;
- }];
-
- return foundView;
+ collectDescendantViewsAtPoint(viewsAtPoint, view, subviewPoint, event);
+ };
}
+@interface UIView (WKHitTesting)
+- (UIView *)_web_findDescendantViewAtPoint:(CGPoint)point withEvent:(UIEvent *)event;
+@end
+
+@implementation UIView (WKHitTesting)
+
- (UIView *)_web_findDescendantViewAtPoint:(CGPoint)point withEvent:(UIEvent *)event
{
- return [self _web_recursiveFindDescendantInteractibleViewAtPoint:point withEvent:event];
+ Vector<UIView *, 16> viewsAtPoint;
+ collectDescendantViewsAtPoint(viewsAtPoint, self, point, event);
+
+ for (auto i = viewsAtPoint.size(); i--;) {
+ auto *view = viewsAtPoint[i];
+ if (!view.isUserInteractionEnabled)
+ continue;
+
+ if ([view conformsToProtocol:@protocol(WKNativelyInteractible)]) {
+ CGPoint subviewPoint = [view convertPoint:point fromView:self];
+ return [view hitTest:subviewPoint withEvent:event];
+ }
+
+ if ([view isKindOfClass:[WKChildScrollView class]]) {
+ // See if the deepest view hit is actually a child of the scrollview.
+ if ([viewsAtPoint.last() isDescendantOfView:view])
+ return view;
+ }
+ }
+ return nil;
}
@end
@@ -96,6 +109,9 @@
@end
+@implementation WKTiledBackingView
+@end
+
@implementation WKTransformView
+ (Class)layerClass