Layer flashing and poor perf during scrolling of message list on gmail.com and hotmai...
authorsimon.fraser@apple.com <simon.fraser@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 22 May 2019 00:12:49 +0000 (00:12 +0000)
committersimon.fraser@apple.com <simon.fraser@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 22 May 2019 00:12:49 +0000 (00:12 +0000)
https://bugs.webkit.org/show_bug.cgi?id=198091
<rdar://problem/49403082>

Reviewed by Antti Koivisto.
Source/WebCore:

When overflow:scroll is scrolled asynchronously, we need to have already created compositing layers where necessary
for clipped-out layers in the scrolled content so that we have something to reveal. We also have ensure
that layers inside the scroller (but scrolled out of view) don't trigger overlap with layers outside the scroller.
All this has to work when the containing block hierarchy (clipping/scrolling) doesn't match the paint order hierarchy (structure
of the z-order and compositing trees).

Overlap testing previously simply used a list of rectangles per compositing container (OverlapMapContainer). This is
a series of layer bounds, built up as we traver the layer tree in z-order. Layers contribute to container N-2, and test
against container N-1.

To handle overlap with non-stacking-context scrollers, introduce the concept of a ClippingScope, which encompasses
a set of layers sharing the same composited-scrolling containing-block ancestor. Within a ClippingScope, layer bounds
are computed unclipped. Between them, bounds are tested clipped.

Conceptually, each OverlapMapContainer has a tree of ClippingScopes (reflecting the containing-block order tree of
composited overflow scroll), and rects are added to the appropriate ClippingScope. This tree is currently always
root-relative; the root node is the RenderView's RenderLayer, and will accumulate the bounds of layers not inside
composited overflow scroll (just like the old code).

When a OverlapMapContainer is popped, the list of rectangles in its ClippingScope tree is merged with that of the previous
container.

Tests: compositing/layer-creation/clipping-scope/nested-scroller-overlap.html
       compositing/layer-creation/clipping-scope/overlap-constrained-inside-scroller.html
       compositing/layer-creation/clipping-scope/overlap-constrained-inside-stacking-context-scroller.html
       compositing/layer-creation/clipping-scope/scroller-with-negative-z-children.html
       compositing/layer-creation/clipping-scope/shared-layers-in-scroller.html

* rendering/LayerOverlapMap.cpp:
(WebCore::operator<<):
(WebCore::OverlapMapContainer::OverlapMapContainer):
(WebCore::OverlapMapContainer::ClippingScope::ClippingScope):
(WebCore::OverlapMapContainer::ClippingScope::childWithLayer const):
(WebCore::OverlapMapContainer::ClippingScope::addChildWithLayerAndBounds):
(WebCore::OverlapMapContainer::ClippingScope::addChild):
(WebCore::OverlapMapContainer::ClippingScope::appendRect):
(WebCore::OverlapMapContainer::clippingScopeContainingLayerChildRecursive):
(WebCore::OverlapMapContainer::scopeContainingLayer const):
(WebCore::OverlapMapContainer::rootScope const):
(WebCore::OverlapMapContainer::rootScope):
(WebCore::OverlapMapContainer::add):
(WebCore::OverlapMapContainer::overlapsLayers const):
(WebCore::OverlapMapContainer::mergeClippingScopesRecursive):
(WebCore::OverlapMapContainer::append):
(WebCore::OverlapMapContainer::ensureClippingScopeForLayers):
(WebCore::OverlapMapContainer::findClippingScopeForLayers const):
(WebCore::OverlapMapContainer::recursiveOutputToStream const):
(WebCore::OverlapMapContainer::dump const):
(WebCore::LayerOverlapMap::LayerOverlapMap):
(WebCore::LayerOverlapMap::add):
(WebCore::LayerOverlapMap::overlapsLayers const):
(WebCore::LayerOverlapMap::pushCompositingContainer):
(WebCore::LayerOverlapMap::popCompositingContainer):
(WebCore::OverlapMapContainer::unite): Deleted.
(WebCore::OverlapMapContainer::rectList const): Deleted.
* rendering/LayerOverlapMap.h:
* rendering/RenderLayerCompositor.cpp:
(WebCore::RenderLayerCompositor::BackingSharingState::appendSharingLayer):
(WebCore::RenderLayerCompositor::BackingSharingState::updateBeforeDescendantTraversal):
(WebCore::RenderLayerCompositor::updateCompositingLayers):
(WebCore::RenderLayerCompositor::computeCompositingRequirements):
(WebCore::RenderLayerCompositor::traverseUnchangedSubtree):
(WebCore::RenderLayerCompositor::computeExtent const):
(WebCore::createsClippingScope):
(WebCore::enclosingClippingScopes):
(WebCore::RenderLayerCompositor::addToOverlapMap const):
(WebCore::RenderLayerCompositor::updateOverlapMap const):
(WebCore::RenderLayerCompositor::layerOverlaps const):
* rendering/RenderLayerCompositor.h:

Source/WebCore/../../LayoutTests:

* TestExpectations:
* compositing/layer-creation/clipping-scope/nested-scroller-overlap-expected.txt: Added.
* compositing/layer-creation/clipping-scope/nested-scroller-overlap.html: Added.
* compositing/layer-creation/clipping-scope/overlap-constrained-inside-scroller-expected.txt: Added.
* compositing/layer-creation/clipping-scope/overlap-constrained-inside-scroller.html: Added.
* compositing/layer-creation/clipping-scope/overlap-constrained-inside-stacking-context-scroller-expected.txt: Added.
* compositing/layer-creation/clipping-scope/overlap-constrained-inside-stacking-context-scroller.html: Added.
* compositing/layer-creation/clipping-scope/scroller-with-negative-z-children-expected.txt: Added.
* compositing/layer-creation/clipping-scope/scroller-with-negative-z-children.html: Added.
* compositing/layer-creation/clipping-scope/shared-layers-in-scroller-expected.txt: Added.
* compositing/layer-creation/clipping-scope/shared-layers-in-scroller.html: Added.
* platform/ios-wk2/TestExpectations:
* platform/ios-wk2/compositing/layer-creation/clipping-scope/nested-scroller-overlap-expected.txt: Added.
* platform/ios-wk2/compositing/layer-creation/clipping-scope/overlap-constrained-inside-scroller-expected.txt: Added.
* platform/ios-wk2/compositing/layer-creation/clipping-scope/overlap-constrained-inside-stacking-context-scroller-expected.txt: Added.
* platform/ios-wk2/compositing/layer-creation/clipping-scope/scroller-with-negative-z-children-expected.txt: Added.
* platform/ios-wk2/compositing/layer-creation/clipping-scope/shared-layers-in-scroller-expected.txt: Added.
* platform/mac-wk2/TestExpectations:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@245602 268f45cc-cd09-0410-ab3c-d52691b4dbfc

24 files changed:
LayoutTests/ChangeLog
LayoutTests/TestExpectations
LayoutTests/compositing/layer-creation/clipping-scope/nested-scroller-overlap-expected.txt [new file with mode: 0644]
LayoutTests/compositing/layer-creation/clipping-scope/nested-scroller-overlap.html [new file with mode: 0644]
LayoutTests/compositing/layer-creation/clipping-scope/overlap-constrained-inside-scroller-expected.txt [new file with mode: 0644]
LayoutTests/compositing/layer-creation/clipping-scope/overlap-constrained-inside-scroller.html [new file with mode: 0644]
LayoutTests/compositing/layer-creation/clipping-scope/overlap-constrained-inside-stacking-context-scroller-expected.txt [new file with mode: 0644]
LayoutTests/compositing/layer-creation/clipping-scope/overlap-constrained-inside-stacking-context-scroller.html [new file with mode: 0644]
LayoutTests/compositing/layer-creation/clipping-scope/scroller-with-negative-z-children-expected.txt [new file with mode: 0644]
LayoutTests/compositing/layer-creation/clipping-scope/scroller-with-negative-z-children.html [new file with mode: 0644]
LayoutTests/compositing/layer-creation/clipping-scope/shared-layers-in-scroller-expected.txt [new file with mode: 0644]
LayoutTests/compositing/layer-creation/clipping-scope/shared-layers-in-scroller.html [new file with mode: 0644]
LayoutTests/platform/ios-wk2/TestExpectations
LayoutTests/platform/ios-wk2/compositing/layer-creation/clipping-scope/nested-scroller-overlap-expected.txt [new file with mode: 0644]
LayoutTests/platform/ios-wk2/compositing/layer-creation/clipping-scope/overlap-constrained-inside-scroller-expected.txt [new file with mode: 0644]
LayoutTests/platform/ios-wk2/compositing/layer-creation/clipping-scope/overlap-constrained-inside-stacking-context-scroller-expected.txt [new file with mode: 0644]
LayoutTests/platform/ios-wk2/compositing/layer-creation/clipping-scope/scroller-with-negative-z-children-expected.txt [new file with mode: 0644]
LayoutTests/platform/ios-wk2/compositing/layer-creation/clipping-scope/shared-layers-in-scroller-expected.txt [new file with mode: 0644]
LayoutTests/platform/mac-wk2/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/rendering/LayerOverlapMap.cpp
Source/WebCore/rendering/LayerOverlapMap.h
Source/WebCore/rendering/RenderLayerCompositor.cpp
Source/WebCore/rendering/RenderLayerCompositor.h

index 72302c7..839b0fd 100644 (file)
         * fast/text/variations/resources/Amstelvar/COPYRIGHT.md: Added.
         * fast/text/variations/resources/Amstelvar/OFL.txt: Added.
 
+2019-05-21  Simon Fraser  <simon.fraser@apple.com>
+
+        Layer flashing and poor perf during scrolling of message list on gmail.com and hotmail.com - overlap testing needs to constrained to clipping scopes
+        https://bugs.webkit.org/show_bug.cgi?id=198091
+        <rdar://problem/49403082>
+
+        Reviewed by Antti Koivisto.
+
+        * TestExpectations:
+        * compositing/layer-creation/clipping-scope/nested-scroller-overlap-expected.txt: Added.
+        * compositing/layer-creation/clipping-scope/nested-scroller-overlap.html: Added.
+        * compositing/layer-creation/clipping-scope/overlap-constrained-inside-scroller-expected.txt: Added.
+        * compositing/layer-creation/clipping-scope/overlap-constrained-inside-scroller.html: Added.
+        * compositing/layer-creation/clipping-scope/overlap-constrained-inside-stacking-context-scroller-expected.txt: Added.
+        * compositing/layer-creation/clipping-scope/overlap-constrained-inside-stacking-context-scroller.html: Added.
+        * compositing/layer-creation/clipping-scope/scroller-with-negative-z-children-expected.txt: Added.
+        * compositing/layer-creation/clipping-scope/scroller-with-negative-z-children.html: Added.
+        * compositing/layer-creation/clipping-scope/shared-layers-in-scroller-expected.txt: Added.
+        * compositing/layer-creation/clipping-scope/shared-layers-in-scroller.html: Added.
+        * platform/ios-wk2/TestExpectations:
+        * platform/ios-wk2/compositing/layer-creation/clipping-scope/nested-scroller-overlap-expected.txt: Added.
+        * platform/ios-wk2/compositing/layer-creation/clipping-scope/overlap-constrained-inside-scroller-expected.txt: Added.
+        * platform/ios-wk2/compositing/layer-creation/clipping-scope/overlap-constrained-inside-stacking-context-scroller-expected.txt: Added.
+        * platform/ios-wk2/compositing/layer-creation/clipping-scope/scroller-with-negative-z-children-expected.txt: Added.
+        * platform/ios-wk2/compositing/layer-creation/clipping-scope/shared-layers-in-scroller-expected.txt: Added.
+        * platform/mac-wk2/TestExpectations:
+
 2019-05-21  Antti Koivisto  <antti@apple.com>
 
         RTL/overflow scroll tests fail with async overflow enabled
index 0967119..99cfec5 100644 (file)
@@ -63,6 +63,7 @@ fast/media/ios [ Skip ]
 # Requires async overflow scrolling
 compositing/shared-backing/overflow-scroll [ Skip ]
 compositing/scrolling/async-overflow-scrolling [ Skip ]
+compositing/layer-creation/clipping-scope [ Skip ]
 
 # WebKit2 only.
 printing/printing-events.html [ Skip ]
diff --git a/LayoutTests/compositing/layer-creation/clipping-scope/nested-scroller-overlap-expected.txt b/LayoutTests/compositing/layer-creation/clipping-scope/nested-scroller-overlap-expected.txt
new file mode 100644 (file)
index 0000000..a608569
--- /dev/null
@@ -0,0 +1,112 @@
+(GraphicsLayer
+  (anchor 0.00 0.00)
+  (bounds 800.00 600.00)
+  (children 1
+    (GraphicsLayer
+      (bounds 800.00 600.00)
+      (contentsOpaque 1)
+      (children 7
+        (GraphicsLayer
+          (position 23.00 105.00)
+          (bounds 402.00 352.00)
+          (drawsContent 1)
+          (children 1
+            (GraphicsLayer
+              (offsetFromRenderer width=1 height=1)
+              (position 1.00 1.00)
+              (bounds 385.00 350.00)
+              (children 1
+                (GraphicsLayer
+                  (offsetFromRenderer width=1 height=1)
+                  (anchor 0.00 0.00)
+                  (bounds 385.00 832.00)
+                  (children 1
+                    (GraphicsLayer
+                      (position 20.00 110.00)
+                      (bounds 302.00 202.00)
+                      (contentsOpaque 1)
+                      (drawsContent 1)
+                      (children 1
+                        (GraphicsLayer
+                          (offsetFromRenderer width=1 height=1)
+                          (position 1.00 1.00)
+                          (bounds 285.00 200.00)
+                          (children 1
+                            (GraphicsLayer
+                              (offsetFromRenderer width=1 height=1)
+                              (anchor 0.00 0.00)
+                              (bounds 285.00 280.00)
+                              (drawsContent 1)
+                            )
+                          )
+                        )
+                      )
+                    )
+                  )
+                )
+              )
+            )
+          )
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-10 height=-10)
+          (position 24.00 106.00)
+          (bounds 385.00 350.00)
+          (children 1
+            (GraphicsLayer
+              (position 10.00 10.00)
+              (bounds 100.00 80.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+        (GraphicsLayer
+          (position 10.00 10.00)
+          (bounds 60.00 550.00)
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-10 height=-10)
+          (position 45.00 217.00)
+          (bounds 285.00 200.00)
+          (children 1
+            (GraphicsLayer
+              (position 10.00 10.00)
+              (bounds 100.00 80.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-10 height=-100)
+          (position 45.00 217.00)
+          (bounds 285.00 200.00)
+          (children 1
+            (GraphicsLayer
+              (position 10.00 100.00)
+              (bounds 100.00 80.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-10 height=-190)
+          (position 45.00 217.00)
+          (bounds 285.00 200.00)
+          (children 1
+            (GraphicsLayer
+              (position 10.00 190.00)
+              (bounds 100.00 80.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+        (GraphicsLayer
+          (position 18.00 472.00)
+          (bounds 100.00 80.00)
+          (contentsOpaque 1)
+        )
+      )
+    )
+  )
+)
+
diff --git a/LayoutTests/compositing/layer-creation/clipping-scope/nested-scroller-overlap.html b/LayoutTests/compositing/layer-creation/clipping-scope/nested-scroller-overlap.html
new file mode 100644 (file)
index 0000000..56fa0b7
--- /dev/null
@@ -0,0 +1,71 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ internal:AsyncOverflowScrollingEnabled=true ] -->
+<html>
+<head>
+    <style>
+        .scroller {
+            overflow-y: scroll;
+                       margin: 15px;
+            height: 350px;
+            width: 400px;
+            border: 1px solid black;
+        }
+        
+        .inner {
+            width: 300px;
+            height: 200px;
+            margin: 20px;
+                       background-color: white;
+        }
+        
+        .box {
+            position: relative;
+            width: 100px;
+            height: 80px;
+            margin: 10px;
+            background-color: green;
+        }
+        
+        .trigger {
+            position: absolute;
+            top: 10px;
+            left: 10px;
+            width: 60px;
+            height: 550px;
+            transform: translateZ(0);
+            background-color: rgba(128, 128, 200, 0.7);
+        }
+        
+        .outside {
+            background-color: gray;
+        }
+        
+        .spacer {
+            height: 500px;
+        }
+    </style>
+       <script>
+               if (window.testRunner)
+                       testRunner.dumpAsText();
+
+               window.addEventListener('load', () => {
+                       if (window.testRunner)
+                               document.getElementById('layers').innerText = window.internals.layerTreeAsText(document);
+               }, false);
+       </script>
+</head>
+<body>
+    <div class="outside box"></div>
+    <div class="scroller">
+        <div class="inner box"></div>
+        <div class="inner scroller">
+            <div class="trigger"></div>
+            <div class="inside box"></div>
+            <div class="inside box"></div>
+            <div class="inside box"></div>
+        </div>
+        <div class="spacer"></div>
+    </div>
+    <div class="outside box"></div>
+<pre id="layers">Layer tree goes here</pre>
+</body>
+</html>
diff --git a/LayoutTests/compositing/layer-creation/clipping-scope/overlap-constrained-inside-scroller-expected.txt b/LayoutTests/compositing/layer-creation/clipping-scope/overlap-constrained-inside-scroller-expected.txt
new file mode 100644 (file)
index 0000000..2e6044a
--- /dev/null
@@ -0,0 +1,80 @@
+(GraphicsLayer
+  (anchor 0.00 0.00)
+  (bounds 800.00 600.00)
+  (children 1
+    (GraphicsLayer
+      (bounds 800.00 600.00)
+      (contentsOpaque 1)
+      (children 5
+        (GraphicsLayer
+          (position 8.00 8.00)
+          (bounds 302.00 302.00)
+          (drawsContent 1)
+          (children 1
+            (GraphicsLayer
+              (offsetFromRenderer width=1 height=1)
+              (position 1.00 1.00)
+              (bounds 285.00 300.00)
+              (children 1
+                (GraphicsLayer
+                  (offsetFromRenderer width=1 height=1)
+                  (anchor 0.00 0.00)
+                  (bounds 285.00 500.00)
+                )
+              )
+            )
+          )
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-10 height=-10)
+          (position 9.00 9.00)
+          (bounds 285.00 300.00)
+          (children 1
+            (GraphicsLayer
+              (position 10.00 10.00)
+              (bounds 50.00 200.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-40 height=-20)
+          (position 9.00 9.00)
+          (bounds 285.00 300.00)
+          (children 1
+            (GraphicsLayer
+              (position 40.00 20.00)
+              (bounds 100.00 100.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-40 height=-140)
+          (position 9.00 9.00)
+          (bounds 285.00 300.00)
+          (children 1
+            (GraphicsLayer
+              (position 40.00 140.00)
+              (bounds 100.00 100.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-40 height=-260)
+          (position 9.00 9.00)
+          (bounds 285.00 300.00)
+          (children 1
+            (GraphicsLayer
+              (position 40.00 260.00)
+              (bounds 100.00 100.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+      )
+    )
+  )
+)
+
diff --git a/LayoutTests/compositing/layer-creation/clipping-scope/overlap-constrained-inside-scroller.html b/LayoutTests/compositing/layer-creation/clipping-scope/overlap-constrained-inside-scroller.html
new file mode 100644 (file)
index 0000000..fb68372
--- /dev/null
@@ -0,0 +1,64 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ internal:AsyncOverflowScrollingEnabled=true ] -->
+<html>
+<head>
+    <style>
+               .scroller {
+                       position: relative;
+                       overflow-y: scroll;
+                       width: 300px;
+                       height: 300px;
+                       border: 1px solid black;
+               }
+               
+               .box {
+                       position: relative;
+                       margin: 20px;
+                   width: 100px;
+                   height: 100px;
+                   background-color: green;
+               }
+
+               .inside {
+                       left: 20px;
+               }
+
+               .outside {
+                       position: relative;
+                       margin: 20px;
+                   width: 100px;
+                   height: 100px;
+                   background-color: silver;
+               }
+
+               .trigger {
+                       position: absolute;
+            top: 10px;
+                       left: 10px;
+                       width: 50px;
+                       height: 200px;
+                       background-color: silver;
+                   transform: translateZ(0);
+               }
+    </style>
+       <script>
+               if (window.testRunner)
+                       testRunner.dumpAsText();
+
+               window.addEventListener('load', () => {
+                       if (window.testRunner)
+                               document.getElementById('layers').innerText = window.internals.layerTreeAsText(document);
+               }, false);
+       </script>
+</head>
+<body>
+       <div class="scroller">
+               <div class="trigger"></div>
+        <div class="inside box"></div>
+        <div class="inside box"></div>
+        <div class="inside box"></div>
+        <div class="inside box"></div>
+       </div>
+       <div class="outside"></div>
+<pre id="layers">Layer tree goes here</pre>
+</body>
+</html>
diff --git a/LayoutTests/compositing/layer-creation/clipping-scope/overlap-constrained-inside-stacking-context-scroller-expected.txt b/LayoutTests/compositing/layer-creation/clipping-scope/overlap-constrained-inside-stacking-context-scroller-expected.txt
new file mode 100644 (file)
index 0000000..fa4f58a
--- /dev/null
@@ -0,0 +1,50 @@
+(GraphicsLayer
+  (anchor 0.00 0.00)
+  (bounds 800.00 600.00)
+  (children 1
+    (GraphicsLayer
+      (bounds 800.00 600.00)
+      (contentsOpaque 1)
+      (children 1
+        (GraphicsLayer
+          (position 8.00 8.00)
+          (bounds 302.00 302.00)
+          (drawsContent 1)
+          (children 1
+            (GraphicsLayer
+              (offsetFromRenderer width=1 height=1)
+              (position 1.00 1.00)
+              (bounds 285.00 300.00)
+              (children 1
+                (GraphicsLayer
+                  (offsetFromRenderer width=1 height=1)
+                  (anchor 0.00 0.00)
+                  (bounds 285.00 500.00)
+                  (drawsContent 1)
+                  (children 3
+                    (GraphicsLayer
+                      (position 10.00 10.00)
+                      (bounds 50.00 200.00)
+                      (contentsOpaque 1)
+                    )
+                    (GraphicsLayer
+                      (position 40.00 20.00)
+                      (bounds 100.00 100.00)
+                      (contentsOpaque 1)
+                    )
+                    (GraphicsLayer
+                      (position 40.00 140.00)
+                      (bounds 100.00 100.00)
+                      (contentsOpaque 1)
+                    )
+                  )
+                )
+              )
+            )
+          )
+        )
+      )
+    )
+  )
+)
+
diff --git a/LayoutTests/compositing/layer-creation/clipping-scope/overlap-constrained-inside-stacking-context-scroller.html b/LayoutTests/compositing/layer-creation/clipping-scope/overlap-constrained-inside-stacking-context-scroller.html
new file mode 100644 (file)
index 0000000..ad62b14
--- /dev/null
@@ -0,0 +1,65 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ internal:AsyncOverflowScrollingEnabled=true ] -->
+<html>
+<head>
+    <style>
+               .scroller {
+                       position: relative;
+                       z-index: 0;
+                       overflow-y: scroll;
+                       width: 300px;
+                       height: 300px;
+                       border: 1px solid black;
+               }
+               
+               .box {
+                       position: relative;
+                       margin: 20px;
+                   width: 100px;
+                   height: 100px;
+                   background-color: green;
+               }
+
+               .inside {
+                       left: 20px;
+               }
+
+               .outside {
+                       position: relative;
+                       margin: 20px;
+                   width: 100px;
+                   height: 100px;
+                   background-color: silver;
+               }
+
+               .trigger {
+                       position: absolute;
+            top: 10px;
+                       left: 10px;
+                       width: 50px;
+                       height: 200px;
+                       background-color: silver;
+                   transform: translateZ(0);
+               }
+    </style>
+       <script>
+               if (window.testRunner)
+                       testRunner.dumpAsText();
+
+               window.addEventListener('load', () => {
+                       if (window.testRunner)
+                               document.getElementById('layers').innerText = window.internals.layerTreeAsText(document);
+               }, false);
+       </script>
+</head>
+<body>
+       <div class="scroller">
+               <div class="trigger"></div>
+        <div class="inside box"></div>
+        <div class="inside box"></div>
+        <div class="inside box"></div>
+        <div class="inside box"></div>
+       </div>
+       <div class="outside"></div>
+<pre id="layers">Layer tree goes here</pre>
+</body>
+</html>
diff --git a/LayoutTests/compositing/layer-creation/clipping-scope/scroller-with-negative-z-children-expected.txt b/LayoutTests/compositing/layer-creation/clipping-scope/scroller-with-negative-z-children-expected.txt
new file mode 100644 (file)
index 0000000..b8a99a2
--- /dev/null
@@ -0,0 +1,68 @@
+(GraphicsLayer
+  (anchor 0.00 0.00)
+  (bounds 800.00 600.00)
+  (children 1
+    (GraphicsLayer
+      (bounds 800.00 600.00)
+      (contentsOpaque 1)
+      (children 4
+        (GraphicsLayer
+          (position 8.00 8.00)
+          (bounds 302.00 302.00)
+          (drawsContent 1)
+          (children 1
+            (GraphicsLayer
+              (offsetFromRenderer width=1 height=1)
+              (position 1.00 1.00)
+              (bounds 285.00 300.00)
+              (children 1
+                (GraphicsLayer
+                  (offsetFromRenderer width=1 height=1)
+                  (anchor 0.00 0.00)
+                  (bounds 285.00 500.00)
+                )
+              )
+            )
+          )
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-10 height=-10)
+          (position 9.00 9.00)
+          (bounds 285.00 300.00)
+          (children 1
+            (GraphicsLayer
+              (position 10.00 10.00)
+              (bounds 50.00 200.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-40 height=-20)
+          (position 9.00 9.00)
+          (bounds 285.00 300.00)
+          (children 1
+            (GraphicsLayer
+              (position 40.00 20.00)
+              (bounds 100.00 100.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-40 height=-260)
+          (position 9.00 9.00)
+          (bounds 285.00 300.00)
+          (children 1
+            (GraphicsLayer
+              (position 40.00 260.00)
+              (bounds 100.00 100.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+      )
+    )
+  )
+)
+
diff --git a/LayoutTests/compositing/layer-creation/clipping-scope/scroller-with-negative-z-children.html b/LayoutTests/compositing/layer-creation/clipping-scope/scroller-with-negative-z-children.html
new file mode 100644 (file)
index 0000000..48b5124
--- /dev/null
@@ -0,0 +1,68 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ internal:AsyncOverflowScrollingEnabled=true ] -->
+<html>
+<head>
+    <style>
+               .scroller {
+                       position: relative;
+                       overflow-y: scroll;
+                       width: 300px;
+                       height: 300px;
+                       border: 1px solid black;
+               }
+               
+               .box {
+                       position: relative;
+                       margin: 20px;
+                   width: 100px;
+                   height: 100px;
+                   background-color: green;
+               }
+
+               .inside {
+                       left: 20px;
+               }
+               
+               .negative {
+                       z-index: -1;
+               }
+
+               .outside {
+                       position: relative;
+                       margin: 20px;
+                   width: 100px;
+                   height: 100px;
+                   background-color: silver;
+               }
+
+               .trigger {
+                       position: absolute;
+            top: 10px;
+                       left: 10px;
+                       width: 50px;
+                       height: 200px;
+                       background-color: silver;
+                   transform: translateZ(0);
+               }
+    </style>
+       <script>
+               if (window.testRunner)
+                       testRunner.dumpAsText();
+
+               window.addEventListener('load', () => {
+                       if (window.testRunner)
+                               document.getElementById('layers').innerText = window.internals.layerTreeAsText(document);
+               }, false);
+       </script>
+</head>
+<body>
+       <div class="scroller">
+               <div class="trigger"></div>
+        <div class="inside box"></div>
+        <div class="inside negative box"></div>
+        <div class="inside box"></div>
+        <div class="inside box"></div>
+       </div>
+       <div class="outside"></div>
+<pre id="layers">Layer tree goes here</pre>
+</body>
+</html>
diff --git a/LayoutTests/compositing/layer-creation/clipping-scope/shared-layers-in-scroller-expected.txt b/LayoutTests/compositing/layer-creation/clipping-scope/shared-layers-in-scroller-expected.txt
new file mode 100644 (file)
index 0000000..230323b
--- /dev/null
@@ -0,0 +1,38 @@
+(GraphicsLayer
+  (anchor 0.00 0.00)
+  (bounds 800.00 600.00)
+  (children 1
+    (GraphicsLayer
+      (bounds 800.00 600.00)
+      (contentsOpaque 1)
+      (children 2
+        (GraphicsLayer
+          (position 8.00 8.00)
+          (bounds 302.00 302.00)
+          (drawsContent 1)
+          (children 1
+            (GraphicsLayer
+              (offsetFromRenderer width=1 height=1)
+              (position 1.00 1.00)
+              (bounds 285.00 300.00)
+              (children 1
+                (GraphicsLayer
+                  (offsetFromRenderer width=1 height=1)
+                  (anchor 0.00 0.00)
+                  (bounds 285.00 500.00)
+                  (drawsContent 1)
+                )
+              )
+            )
+          )
+        )
+        (GraphicsLayer
+          (position 78.00 300.00)
+          (bounds 100.00 100.00)
+          (contentsOpaque 1)
+        )
+      )
+    )
+  )
+)
+
diff --git a/LayoutTests/compositing/layer-creation/clipping-scope/shared-layers-in-scroller.html b/LayoutTests/compositing/layer-creation/clipping-scope/shared-layers-in-scroller.html
new file mode 100644 (file)
index 0000000..287ddd9
--- /dev/null
@@ -0,0 +1,54 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ internal:AsyncOverflowScrollingEnabled=true ] -->
+<html>
+<head>
+    <style>
+               .scroller {
+                       overflow-y: scroll;
+                       width: 300px;
+                       height: 300px;
+                       border: 1px solid black;
+               }
+               
+               .box {
+                       position: relative;
+                       margin: 20px;
+                   width: 100px;
+                   height: 100px;
+                   background-color: green;
+               }
+               
+               .spacer {
+                       height: 200px;
+                       width: 10px;
+                       background-color: silver;
+               }
+               
+               .outside {
+                       position: relative;
+                       top: -30px;
+                       left: 50px;
+                       background-color: gray;
+               }
+               
+    </style>
+       <script>
+               if (window.testRunner)
+                       testRunner.dumpAsText();
+
+               window.addEventListener('load', () => {
+                       if (window.testRunner)
+                               document.getElementById('layers').innerText = window.internals.layerTreeAsText(document);
+               }, false);
+       </script>
+</head>
+<body>
+       <div class="scroller">
+               <div class="box"></div>
+               <div class="box"></div>
+               <div class="box"></div>
+               <div class="box"></div>
+       </div>
+       <div class="outside box"></div>
+<pre id="layers">Layer tree goes here</pre>
+</body>
+</html>
index 08099af..680cad3 100644 (file)
@@ -9,6 +9,7 @@
 compositing/ios [ Pass ]
 compositing/shared-backing/overflow-scroll [ Pass ]
 compositing/scrolling/async-overflow-scrolling [ Pass ]
+compositing/layer-creation/clipping-scope [ Pass ]
 fast/device-orientation [ Pass ]
 fast/history/ios [ Pass ]
 fast/scrolling/ios [ Pass ]
diff --git a/LayoutTests/platform/ios-wk2/compositing/layer-creation/clipping-scope/nested-scroller-overlap-expected.txt b/LayoutTests/platform/ios-wk2/compositing/layer-creation/clipping-scope/nested-scroller-overlap-expected.txt
new file mode 100644 (file)
index 0000000..9ba74cd
--- /dev/null
@@ -0,0 +1,112 @@
+(GraphicsLayer
+  (anchor 0.00 0.00)
+  (bounds 800.00 600.00)
+  (children 1
+    (GraphicsLayer
+      (bounds 800.00 600.00)
+      (contentsOpaque 1)
+      (children 7
+        (GraphicsLayer
+          (position 23.00 105.00)
+          (bounds 402.00 352.00)
+          (drawsContent 1)
+          (children 1
+            (GraphicsLayer
+              (offsetFromRenderer width=1 height=1)
+              (position 1.00 1.00)
+              (bounds 400.00 350.00)
+              (children 1
+                (GraphicsLayer
+                  (offsetFromRenderer width=1 height=1)
+                  (anchor 0.00 0.00)
+                  (bounds 400.00 832.00)
+                  (children 1
+                    (GraphicsLayer
+                      (position 20.00 110.00)
+                      (bounds 302.00 202.00)
+                      (contentsOpaque 1)
+                      (drawsContent 1)
+                      (children 1
+                        (GraphicsLayer
+                          (offsetFromRenderer width=1 height=1)
+                          (position 1.00 1.00)
+                          (bounds 300.00 200.00)
+                          (children 1
+                            (GraphicsLayer
+                              (offsetFromRenderer width=1 height=1)
+                              (anchor 0.00 0.00)
+                              (bounds 300.00 280.00)
+                              (drawsContent 1)
+                            )
+                          )
+                        )
+                      )
+                    )
+                  )
+                )
+              )
+            )
+          )
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-10 height=-10)
+          (position 24.00 106.00)
+          (bounds 400.00 350.00)
+          (children 1
+            (GraphicsLayer
+              (position 10.00 10.00)
+              (bounds 100.00 80.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+        (GraphicsLayer
+          (position 10.00 10.00)
+          (bounds 60.00 550.00)
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-10 height=-10)
+          (position 45.00 217.00)
+          (bounds 300.00 200.00)
+          (children 1
+            (GraphicsLayer
+              (position 10.00 10.00)
+              (bounds 100.00 80.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-10 height=-100)
+          (position 45.00 217.00)
+          (bounds 300.00 200.00)
+          (children 1
+            (GraphicsLayer
+              (position 10.00 100.00)
+              (bounds 100.00 80.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-10 height=-190)
+          (position 45.00 217.00)
+          (bounds 300.00 200.00)
+          (children 1
+            (GraphicsLayer
+              (position 10.00 190.00)
+              (bounds 100.00 80.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+        (GraphicsLayer
+          (position 18.00 472.00)
+          (bounds 100.00 80.00)
+          (contentsOpaque 1)
+        )
+      )
+    )
+  )
+)
+
diff --git a/LayoutTests/platform/ios-wk2/compositing/layer-creation/clipping-scope/overlap-constrained-inside-scroller-expected.txt b/LayoutTests/platform/ios-wk2/compositing/layer-creation/clipping-scope/overlap-constrained-inside-scroller-expected.txt
new file mode 100644 (file)
index 0000000..88f8318
--- /dev/null
@@ -0,0 +1,80 @@
+(GraphicsLayer
+  (anchor 0.00 0.00)
+  (bounds 800.00 600.00)
+  (children 1
+    (GraphicsLayer
+      (bounds 800.00 600.00)
+      (contentsOpaque 1)
+      (children 5
+        (GraphicsLayer
+          (position 8.00 8.00)
+          (bounds 302.00 302.00)
+          (drawsContent 1)
+          (children 1
+            (GraphicsLayer
+              (offsetFromRenderer width=1 height=1)
+              (position 1.00 1.00)
+              (bounds 300.00 300.00)
+              (children 1
+                (GraphicsLayer
+                  (offsetFromRenderer width=1 height=1)
+                  (anchor 0.00 0.00)
+                  (bounds 300.00 500.00)
+                )
+              )
+            )
+          )
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-10 height=-10)
+          (position 9.00 9.00)
+          (bounds 300.00 300.00)
+          (children 1
+            (GraphicsLayer
+              (position 10.00 10.00)
+              (bounds 50.00 200.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-40 height=-20)
+          (position 9.00 9.00)
+          (bounds 300.00 300.00)
+          (children 1
+            (GraphicsLayer
+              (position 40.00 20.00)
+              (bounds 100.00 100.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-40 height=-140)
+          (position 9.00 9.00)
+          (bounds 300.00 300.00)
+          (children 1
+            (GraphicsLayer
+              (position 40.00 140.00)
+              (bounds 100.00 100.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-40 height=-260)
+          (position 9.00 9.00)
+          (bounds 300.00 300.00)
+          (children 1
+            (GraphicsLayer
+              (position 40.00 260.00)
+              (bounds 100.00 100.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+      )
+    )
+  )
+)
+
diff --git a/LayoutTests/platform/ios-wk2/compositing/layer-creation/clipping-scope/overlap-constrained-inside-stacking-context-scroller-expected.txt b/LayoutTests/platform/ios-wk2/compositing/layer-creation/clipping-scope/overlap-constrained-inside-stacking-context-scroller-expected.txt
new file mode 100644 (file)
index 0000000..fb5b1ce
--- /dev/null
@@ -0,0 +1,50 @@
+(GraphicsLayer
+  (anchor 0.00 0.00)
+  (bounds 800.00 600.00)
+  (children 1
+    (GraphicsLayer
+      (bounds 800.00 600.00)
+      (contentsOpaque 1)
+      (children 1
+        (GraphicsLayer
+          (position 8.00 8.00)
+          (bounds 302.00 302.00)
+          (drawsContent 1)
+          (children 1
+            (GraphicsLayer
+              (offsetFromRenderer width=1 height=1)
+              (position 1.00 1.00)
+              (bounds 300.00 300.00)
+              (children 1
+                (GraphicsLayer
+                  (offsetFromRenderer width=1 height=1)
+                  (anchor 0.00 0.00)
+                  (bounds 300.00 500.00)
+                  (drawsContent 1)
+                  (children 3
+                    (GraphicsLayer
+                      (position 10.00 10.00)
+                      (bounds 50.00 200.00)
+                      (contentsOpaque 1)
+                    )
+                    (GraphicsLayer
+                      (position 40.00 20.00)
+                      (bounds 100.00 100.00)
+                      (contentsOpaque 1)
+                    )
+                    (GraphicsLayer
+                      (position 40.00 140.00)
+                      (bounds 100.00 100.00)
+                      (contentsOpaque 1)
+                    )
+                  )
+                )
+              )
+            )
+          )
+        )
+      )
+    )
+  )
+)
+
diff --git a/LayoutTests/platform/ios-wk2/compositing/layer-creation/clipping-scope/scroller-with-negative-z-children-expected.txt b/LayoutTests/platform/ios-wk2/compositing/layer-creation/clipping-scope/scroller-with-negative-z-children-expected.txt
new file mode 100644 (file)
index 0000000..d4b4263
--- /dev/null
@@ -0,0 +1,68 @@
+(GraphicsLayer
+  (anchor 0.00 0.00)
+  (bounds 800.00 600.00)
+  (children 1
+    (GraphicsLayer
+      (bounds 800.00 600.00)
+      (contentsOpaque 1)
+      (children 4
+        (GraphicsLayer
+          (position 8.00 8.00)
+          (bounds 302.00 302.00)
+          (drawsContent 1)
+          (children 1
+            (GraphicsLayer
+              (offsetFromRenderer width=1 height=1)
+              (position 1.00 1.00)
+              (bounds 300.00 300.00)
+              (children 1
+                (GraphicsLayer
+                  (offsetFromRenderer width=1 height=1)
+                  (anchor 0.00 0.00)
+                  (bounds 300.00 500.00)
+                )
+              )
+            )
+          )
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-10 height=-10)
+          (position 9.00 9.00)
+          (bounds 300.00 300.00)
+          (children 1
+            (GraphicsLayer
+              (position 10.00 10.00)
+              (bounds 50.00 200.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-40 height=-20)
+          (position 9.00 9.00)
+          (bounds 300.00 300.00)
+          (children 1
+            (GraphicsLayer
+              (position 40.00 20.00)
+              (bounds 100.00 100.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+        (GraphicsLayer
+          (offsetFromRenderer width=-40 height=-260)
+          (position 9.00 9.00)
+          (bounds 300.00 300.00)
+          (children 1
+            (GraphicsLayer
+              (position 40.00 260.00)
+              (bounds 100.00 100.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+      )
+    )
+  )
+)
+
diff --git a/LayoutTests/platform/ios-wk2/compositing/layer-creation/clipping-scope/shared-layers-in-scroller-expected.txt b/LayoutTests/platform/ios-wk2/compositing/layer-creation/clipping-scope/shared-layers-in-scroller-expected.txt
new file mode 100644 (file)
index 0000000..043f903
--- /dev/null
@@ -0,0 +1,38 @@
+(GraphicsLayer
+  (anchor 0.00 0.00)
+  (bounds 800.00 600.00)
+  (children 1
+    (GraphicsLayer
+      (bounds 800.00 600.00)
+      (contentsOpaque 1)
+      (children 2
+        (GraphicsLayer
+          (position 8.00 8.00)
+          (bounds 302.00 302.00)
+          (drawsContent 1)
+          (children 1
+            (GraphicsLayer
+              (offsetFromRenderer width=1 height=1)
+              (position 1.00 1.00)
+              (bounds 300.00 300.00)
+              (children 1
+                (GraphicsLayer
+                  (offsetFromRenderer width=1 height=1)
+                  (anchor 0.00 0.00)
+                  (bounds 300.00 500.00)
+                  (drawsContent 1)
+                )
+              )
+            )
+          )
+        )
+        (GraphicsLayer
+          (position 78.00 300.00)
+          (bounds 100.00 100.00)
+          (contentsOpaque 1)
+        )
+      )
+    )
+  )
+)
+
index 4debed9..b237cb9 100644 (file)
@@ -7,6 +7,7 @@
 
 compositing/shared-backing/overflow-scroll [ Pass ]
 compositing/scrolling/async-overflow-scrolling [ Pass ]
+compositing/layer-creation/clipping-scope [ Pass ]
 editing/find [ Pass ]
 editing/undo-manager [ Pass ]
 fast/forms/select/mac-wk2 [ Pass ]
index d7075b8..4c1401e 100644 (file)
         * platform/graphics/mac/FontCustomPlatformData.cpp:
         (WebCore::FontCustomPlatformData::fontPlatformData):
 
+2019-05-21  Simon Fraser  <simon.fraser@apple.com>
+
+        Layer flashing and poor perf during scrolling of message list on gmail.com and hotmail.com - overlap testing needs to constrained to clipping scopes
+        https://bugs.webkit.org/show_bug.cgi?id=198091
+        <rdar://problem/49403082>
+
+        Reviewed by Antti Koivisto.
+        
+        When overflow:scroll is scrolled asynchronously, we need to have already created compositing layers where necessary
+        for clipped-out layers in the scrolled content so that we have something to reveal. We also have ensure
+        that layers inside the scroller (but scrolled out of view) don't trigger overlap with layers outside the scroller.
+        All this has to work when the containing block hierarchy (clipping/scrolling) doesn't match the paint order hierarchy (structure
+        of the z-order and compositing trees).
+
+        Overlap testing previously simply used a list of rectangles per compositing container (OverlapMapContainer). This is
+        a series of layer bounds, built up as we traver the layer tree in z-order. Layers contribute to container N-2, and test
+        against container N-1.
+        
+        To handle overlap with non-stacking-context scrollers, introduce the concept of a ClippingScope, which encompasses
+        a set of layers sharing the same composited-scrolling containing-block ancestor. Within a ClippingScope, layer bounds
+        are computed unclipped. Between them, bounds are tested clipped.
+        
+        Conceptually, each OverlapMapContainer has a tree of ClippingScopes (reflecting the containing-block order tree of
+        composited overflow scroll), and rects are added to the appropriate ClippingScope. This tree is currently always
+        root-relative; the root node is the RenderView's RenderLayer, and will accumulate the bounds of layers not inside
+        composited overflow scroll (just like the old code).
+        
+        When a OverlapMapContainer is popped, the list of rectangles in its ClippingScope tree is merged with that of the previous
+        container.
+
+        Tests: compositing/layer-creation/clipping-scope/nested-scroller-overlap.html
+               compositing/layer-creation/clipping-scope/overlap-constrained-inside-scroller.html
+               compositing/layer-creation/clipping-scope/overlap-constrained-inside-stacking-context-scroller.html
+               compositing/layer-creation/clipping-scope/scroller-with-negative-z-children.html
+               compositing/layer-creation/clipping-scope/shared-layers-in-scroller.html
+
+        * rendering/LayerOverlapMap.cpp:
+        (WebCore::operator<<):
+        (WebCore::OverlapMapContainer::OverlapMapContainer):
+        (WebCore::OverlapMapContainer::ClippingScope::ClippingScope):
+        (WebCore::OverlapMapContainer::ClippingScope::childWithLayer const):
+        (WebCore::OverlapMapContainer::ClippingScope::addChildWithLayerAndBounds):
+        (WebCore::OverlapMapContainer::ClippingScope::addChild):
+        (WebCore::OverlapMapContainer::ClippingScope::appendRect):
+        (WebCore::OverlapMapContainer::clippingScopeContainingLayerChildRecursive):
+        (WebCore::OverlapMapContainer::scopeContainingLayer const):
+        (WebCore::OverlapMapContainer::rootScope const):
+        (WebCore::OverlapMapContainer::rootScope):
+        (WebCore::OverlapMapContainer::add):
+        (WebCore::OverlapMapContainer::overlapsLayers const):
+        (WebCore::OverlapMapContainer::mergeClippingScopesRecursive):
+        (WebCore::OverlapMapContainer::append):
+        (WebCore::OverlapMapContainer::ensureClippingScopeForLayers):
+        (WebCore::OverlapMapContainer::findClippingScopeForLayers const):
+        (WebCore::OverlapMapContainer::recursiveOutputToStream const):
+        (WebCore::OverlapMapContainer::dump const):
+        (WebCore::LayerOverlapMap::LayerOverlapMap):
+        (WebCore::LayerOverlapMap::add):
+        (WebCore::LayerOverlapMap::overlapsLayers const):
+        (WebCore::LayerOverlapMap::pushCompositingContainer):
+        (WebCore::LayerOverlapMap::popCompositingContainer):
+        (WebCore::OverlapMapContainer::unite): Deleted.
+        (WebCore::OverlapMapContainer::rectList const): Deleted.
+        * rendering/LayerOverlapMap.h:
+        * rendering/RenderLayerCompositor.cpp:
+        (WebCore::RenderLayerCompositor::BackingSharingState::appendSharingLayer):
+        (WebCore::RenderLayerCompositor::BackingSharingState::updateBeforeDescendantTraversal):
+        (WebCore::RenderLayerCompositor::updateCompositingLayers):
+        (WebCore::RenderLayerCompositor::computeCompositingRequirements):
+        (WebCore::RenderLayerCompositor::traverseUnchangedSubtree):
+        (WebCore::RenderLayerCompositor::computeExtent const):
+        (WebCore::createsClippingScope):
+        (WebCore::enclosingClippingScopes):
+        (WebCore::RenderLayerCompositor::addToOverlapMap const):
+        (WebCore::RenderLayerCompositor::updateOverlapMap const):
+        (WebCore::RenderLayerCompositor::layerOverlaps const):
+        * rendering/RenderLayerCompositor.h:
+
 2019-05-21  Antoine Quint  <graouts@apple.com>
 
         [macOS] Compatibility mouse events aren't prevented by calling preventDefault() on pointerdown
index 31f9550..794a483 100644 (file)
@@ -25,6 +25,7 @@
 
 #include "config.h"
 #include "LayerOverlapMap.h"
+#include "RenderLayer.h"
 #include <wtf/text/TextStream.h>
 
 namespace WebCore {
@@ -58,31 +59,215 @@ struct RectList {
     }
 };
 
+static TextStream& operator<<(TextStream& ts, const RectList& rectList)
+{
+    ts << "bounds " << rectList.boundingRect << " (" << rectList.rects << " rects)";
+    return ts;
+}
+
+// Used to store overlap rects in a way that takes overflow into account.
+// It stores a tree whose nodes are layers with composited scrolling. The tree is built lazily as layers are added whose containing block
+// chains contain composited scrollers. The tree always starts at the root layer.
+// Checking for overlap involves finding the node for the clipping layer enclosing the given layer (or the root),
+// and comparing against the bounds of earlier siblings.
 class OverlapMapContainer {
 public:
-    void add(const LayoutRect& bounds)
+    OverlapMapContainer(const RenderLayer& rootLayer)
+        : m_rootScope(rootLayer)
     {
-        m_rectList.append(bounds);
     }
 
-    bool overlapsLayers(const LayoutRect& bounds) const
+    // Layers are added in z-order, lazily creating clipping scopes as necessary.
+    void add(const RenderLayer&, const LayoutRect& bounds, const Vector<LayerOverlapMap::LayerAndBounds>& enclosingClippingLayers);
+    bool overlapsLayers(const RenderLayer&, const LayoutRect& bounds, const Vector<LayerOverlapMap::LayerAndBounds>& enclosingClippingLayers) const;
+    void append(std::unique_ptr<OverlapMapContainer>&&);
+    
+    String dump(unsigned) const;
+
+private:
+    struct ClippingScope {
+        ClippingScope(const RenderLayer& inLayer)
+            : layer(inLayer)
+        {
+        }
+
+        ClippingScope(const LayerOverlapMap::LayerAndBounds& layerAndBounds)
+            : layer(layerAndBounds.layer)
+            , bounds(layerAndBounds.bounds)
+        {
+        }
+
+        ClippingScope* childWithLayer(const RenderLayer& layer) const
+        {
+            for (auto& child : children) {
+                if (&child.layer == &layer)
+                    return const_cast<ClippingScope*>(&child);
+            }
+            return nullptr;
+        }
+
+        ClippingScope* addChildWithLayerAndBounds(const LayerOverlapMap::LayerAndBounds& layerAndBounds)
+        {
+            children.append({ layerAndBounds });
+            return &children.last();
+        }
+
+        ClippingScope* addChild(const ClippingScope& child)
+        {
+            children.append(child);
+            return &children.last();
+        }
+
+        void appendRect(const LayoutRect& bounds)
+        {
+            rectList.append(bounds);
+        }
+
+        const RenderLayer& layer;
+        LayoutRect bounds; // Bounds of the composited clip.
+        Vector<ClippingScope> children;
+        RectList rectList;
+    };
+
+    static ClippingScope* clippingScopeContainingLayerChildRecursive(const ClippingScope& currNode, const RenderLayer& layer)
     {
-        return m_rectList.intersects(bounds);
+        for (auto& child : currNode.children) {
+            if (&layer == &child.layer)
+                return const_cast<ClippingScope*>(&currNode);
+
+            if (auto* foundNode = clippingScopeContainingLayerChildRecursive(child, layer))
+                return foundNode;
+        }
+
+        return nullptr;
     }
 
-    void unite(const OverlapMapContainer& otherContainer)
+    ClippingScope* scopeContainingLayer(const RenderLayer& layer) const
     {
-        m_rectList.append(otherContainer.m_rectList);
+        return clippingScopeContainingLayerChildRecursive(m_rootScope, layer);
     }
     
-    const RectList& rectList() const { return m_rectList; }
+    static void mergeClippingScopesRecursive(const ClippingScope& sourceScope, ClippingScope& destScope);
 
-private:
-    RectList m_rectList;
+    ClippingScope* ensureClippingScopeForLayers(const Vector<LayerOverlapMap::LayerAndBounds>& enclosingClippingLayers);
+    ClippingScope* findClippingScopeForLayers(const Vector<LayerOverlapMap::LayerAndBounds>& enclosingClippingLayers) const;
+
+    void recursiveOutputToStream(TextStream&, const ClippingScope&, unsigned depth) const;
+
+    const ClippingScope& rootScope() const { return m_rootScope; }
+    ClippingScope& rootScope() { return m_rootScope; }
+
+    ClippingScope m_rootScope;
 };
 
-LayerOverlapMap::LayerOverlapMap()
+void OverlapMapContainer::add(const RenderLayer&, const LayoutRect& bounds, const Vector<LayerOverlapMap::LayerAndBounds>& enclosingClippingLayers)
+{
+    auto* layerScope = ensureClippingScopeForLayers(enclosingClippingLayers);
+    layerScope->appendRect(bounds);
+}
+
+bool OverlapMapContainer::overlapsLayers(const RenderLayer&, const LayoutRect& bounds, const Vector<LayerOverlapMap::LayerAndBounds>& enclosingClippingLayers) const
+{
+    if (m_rootScope.rectList.intersects(bounds))
+        return true;
+
+    if (m_rootScope.children.isEmpty())
+        return false;
+
+    // Find the ClippingScope for which this layer is a child.
+    auto* clippingScope = findClippingScopeForLayers(enclosingClippingLayers);
+    if (!clippingScope)
+        return false;
+
+    if (clippingScope->rectList.intersects(bounds))
+        return true;
+
+    // FIXME: In some cases do we have to walk up the ancestor clipping scope chain?
+    return false;
+}
+
+void OverlapMapContainer::mergeClippingScopesRecursive(const ClippingScope& sourceScope, ClippingScope& destScope)
+{
+    ASSERT(&sourceScope.layer == &destScope.layer);
+    destScope.rectList.append(sourceScope.rectList);
+
+    for (auto& sourceChildScope : sourceScope.children) {
+        ClippingScope* destChild = destScope.childWithLayer(sourceChildScope.layer);
+        if (destChild) {
+            destChild->rectList.append(sourceChildScope.rectList);
+            mergeClippingScopesRecursive(sourceChildScope, *destChild);
+        } else {
+            // New child, just copy the whole subtree.
+            destScope.addChild(sourceScope);
+        }
+    }
+}
+
+void OverlapMapContainer::append(std::unique_ptr<OverlapMapContainer>&& otherContainer)
+{
+    mergeClippingScopesRecursive(otherContainer->rootScope(), m_rootScope);
+}
+
+OverlapMapContainer::ClippingScope* OverlapMapContainer::ensureClippingScopeForLayers(const Vector<LayerOverlapMap::LayerAndBounds>& enclosingClippingLayers)
+{
+    ASSERT(enclosingClippingLayers.size());
+    ASSERT(enclosingClippingLayers[0].layer.isRenderViewLayer());
+
+    auto* currScope = &m_rootScope;
+    for (unsigned i = 1; i < enclosingClippingLayers.size(); ++i) {
+        auto& scopeLayerAndBounds = enclosingClippingLayers[i];
+        auto* childScope = currScope->childWithLayer(scopeLayerAndBounds.layer);
+        if (!childScope) {
+            currScope = currScope->addChildWithLayerAndBounds(scopeLayerAndBounds);
+            break;
+        }
+
+        currScope = childScope;
+    }
+
+    return const_cast<ClippingScope*>(currScope);
+}
+
+OverlapMapContainer::ClippingScope* OverlapMapContainer::findClippingScopeForLayers(const Vector<LayerOverlapMap::LayerAndBounds>& enclosingClippingLayers) const
+{
+    ASSERT(enclosingClippingLayers.size());
+    ASSERT(enclosingClippingLayers[0].layer.isRenderViewLayer());
+
+    const auto* currScope = &m_rootScope;
+    for (unsigned i = 1; i < enclosingClippingLayers.size(); ++i) {
+        auto& scopeLayerAndBounds = enclosingClippingLayers[i];
+        auto* childScope = currScope->childWithLayer(scopeLayerAndBounds.layer);
+        if (!childScope)
+            return nullptr;
+
+        currScope = childScope;
+    }
+
+    return const_cast<ClippingScope*>(currScope);
+}
+
+void OverlapMapContainer::recursiveOutputToStream(TextStream& ts, const ClippingScope& scope, unsigned depth) const
+{
+    ts << "\n" << indent << TextStream::Repeat { 2 * depth, ' ' } << " scope for layer " << &scope.layer << " rects " << scope.rectList;
+    for (auto& childScope : scope.children)
+        recursiveOutputToStream(ts, childScope, depth + 1);
+}
+
+String OverlapMapContainer::dump(unsigned indent) const
+{
+    TextStream multilineStream;
+    multilineStream.increaseIndent(indent);
+    multilineStream << "overlap container - root scope layer " <<  &m_rootScope.layer << " rects " << m_rootScope.rectList;
+
+    for (auto& childScope : m_rootScope.children)
+        recursiveOutputToStream(multilineStream, childScope, 1);
+
+    return multilineStream.release();
+}
+
+LayerOverlapMap::LayerOverlapMap(const RenderLayer& rootLayer)
     : m_geometryMap(UseTransforms)
+    , m_rootLayer(rootLayer)
 {
     // Begin assuming the root layer will be composited so that there is
     // something on the stack. The root layer should also never get an
@@ -92,41 +277,36 @@ LayerOverlapMap::LayerOverlapMap()
 
 LayerOverlapMap::~LayerOverlapMap() = default;
 
-void LayerOverlapMap::add(const LayoutRect& bounds)
+void LayerOverlapMap::add(const RenderLayer& layer, const LayoutRect& bounds, const Vector<LayerAndBounds>& enclosingClippingLayers)
 {
     // Layers do not contribute to overlap immediately--instead, they will
     // contribute to overlap as soon as their composited ancestor has been
     // recursively processed and popped off the stack.
     ASSERT(m_overlapStack.size() >= 2);
-    m_overlapStack[m_overlapStack.size() - 2]->add(bounds);
+    m_overlapStack[m_overlapStack.size() - 2]->add(layer, bounds, enclosingClippingLayers);
+    
     m_isEmpty = false;
 }
 
-bool LayerOverlapMap::overlapsLayers(const LayoutRect& bounds) const
+bool LayerOverlapMap::overlapsLayers(const RenderLayer& layer, const LayoutRect& bounds, const Vector<LayerAndBounds>& enclosingClippingLayers) const
 {
-    return m_overlapStack.last()->overlapsLayers(bounds);
+    return m_overlapStack.last()->overlapsLayers(layer, bounds, enclosingClippingLayers);
 }
 
 void LayerOverlapMap::pushCompositingContainer()
 {
-    m_overlapStack.append(std::make_unique<OverlapMapContainer>());
+    m_overlapStack.append(std::make_unique<OverlapMapContainer>(m_rootLayer));
 }
 
 void LayerOverlapMap::popCompositingContainer()
 {
-    m_overlapStack[m_overlapStack.size() - 2]->unite(*m_overlapStack.last());
+    m_overlapStack[m_overlapStack.size() - 2]->append(WTFMove(m_overlapStack.last()));
     m_overlapStack.removeLast();
 }
 
-static TextStream& operator<<(TextStream& ts, const RectList& rectList)
-{
-    ts << "bounds " << rectList.boundingRect << " (" << rectList.rects << " rects)";
-    return ts;
-}
-
 static TextStream& operator<<(TextStream& ts, const OverlapMapContainer& container)
 {
-    ts << container.rectList();
+    ts << container.dump(ts.indent());
     return ts;
 }
 
@@ -135,13 +315,21 @@ TextStream& operator<<(TextStream& ts, const LayerOverlapMap& overlapMap)
     TextStream multilineStream;
 
     TextStream::GroupScope scope(ts);
-    multilineStream << indent << "LayerOverlapMap\n";
+    multilineStream << "LayerOverlapMap\n";
+    multilineStream.increaseIndent(2);
 
-    for (auto& container : overlapMap.overlapStack())
-        multilineStream << "  " << *container << "\n";
+    bool needNewline = false;
+    for (auto& container : overlapMap.overlapStack()) {
+        if (needNewline)
+            multilineStream << "\n";
+        else
+            needNewline = true;
+        multilineStream << indent << *container;
+    }
 
     ts << multilineStream.release();
     return ts;
 }
 
 } // namespace WebCore
+
index 2b40a43..e3061f0 100644 (file)
@@ -34,16 +34,23 @@ class TextStream;
 
 namespace WebCore {
 
+class OverflowAwareOverlapContainer;
 class OverlapMapContainer;
+class RenderLayer;
 
 class LayerOverlapMap {
     WTF_MAKE_NONCOPYABLE(LayerOverlapMap);
 public:
-    LayerOverlapMap();
+    LayerOverlapMap(const RenderLayer& rootLayer);
     ~LayerOverlapMap();
+    
+    struct LayerAndBounds {
+        RenderLayer& layer;
+        LayoutRect bounds;
+    };
 
-    void add(const LayoutRect& bounds);
-    bool overlapsLayers(const LayoutRect&) const;
+    void add(const RenderLayer&, const LayoutRect&, const Vector<LayerAndBounds>& enclosingClippingLayers);
+    bool overlapsLayers(const RenderLayer&, const LayoutRect&, const Vector<LayerAndBounds>& enclosingClippingLayers) const;
     bool isEmpty() const { return m_isEmpty; }
 
     void pushCompositingContainer();
@@ -57,6 +64,7 @@ public:
 private:
     Vector<std::unique_ptr<OverlapMapContainer>> m_overlapStack;
     RenderGeometryMap m_geometryMap;
+    const RenderLayer& m_rootLayer;
     bool m_isEmpty { true };
 };
 
index f9bf2c5..07d95aa 100644 (file)
@@ -205,7 +205,6 @@ public:
     
     void appendSharingLayer(RenderLayer& layer)
     {
-        LOG_WITH_STREAM(Compositing, stream << &layer << " appendSharingLayer " << &layer << " for backing provider " << m_backingProviderCandidate);
         m_backingSharingLayers.append(makeWeakPtr(layer));
     }
 
@@ -249,7 +248,6 @@ void RenderLayerCompositor::BackingSharingState::updateBeforeDescendantTraversal
     // A layer that composites resets backing-sharing, since subsequent layers need to composite to overlap it.
     if (willBeComposited) {
         m_backingSharingLayers.removeAll(&layer);
-        LOG_WITH_STREAM(Compositing, stream << "Pre-descendant compositing of " << &layer << ", ending sharing sequence for " << m_backingProviderCandidate << " with " << m_backingSharingLayers.size() << " sharing layers");
         endBackingSharingSequence();
     }
 }
@@ -768,12 +766,13 @@ bool RenderLayerCompositor::updateCompositingLayers(CompositingUpdateType update
 
     // FIXME: optimize root-only update.
     if (updateRoot->hasDescendantNeedingCompositingRequirementsTraversal() || updateRoot->needsCompositingRequirementsTraversal()) {
+        auto& rootLayer = rootRenderLayer();
         CompositingState compositingState(updateRoot);
         BackingSharingState backingSharingState;
-        LayerOverlapMap overlapMap;
+        LayerOverlapMap overlapMap(rootLayer);
 
         bool descendantHas3DTransform = false;
-        computeCompositingRequirements(nullptr, rootRenderLayer(), overlapMap, compositingState, backingSharingState, descendantHas3DTransform);
+        computeCompositingRequirements(nullptr, rootLayer, overlapMap, compositingState, backingSharingState, descendantHas3DTransform);
     }
 
     LOG(Compositing, "\nRenderLayerCompositor::updateCompositingLayers - mid");
@@ -883,10 +882,8 @@ void RenderLayerCompositor::computeCompositingRequirements(RenderLayer* ancestor
 
     // If we know for sure the layer is going to be composited, don't bother looking it up in the overlap map
     if (!willBeComposited && !overlapMap.isEmpty() && compositingState.testingOverlap) {
-        computeExtent(overlapMap, layer, layerExtent);
-
         // If we're testing for overlap, we only need to composite if we overlap something that is already composited.
-        if (overlapMap.overlapsLayers(layerExtent.bounds)) {
+        if (layerOverlaps(overlapMap, layer, layerExtent)) {
             if (backingSharingState.backingProviderCandidate() && canBeComposited(layer) && backingProviderLayerCanIncludeLayer(*backingSharingState.backingProviderCandidate(), layer)) {
                 backingSharingState.appendSharingLayer(layer);
                 LOG(Compositing, " layer %p can share with %p", &layer, backingSharingState.backingProviderCandidate());
@@ -1786,7 +1783,6 @@ void RenderLayerCompositor::computeExtent(const LayerOverlapMap& overlapMap, con
     if (extent.bounds.isEmpty())
         extent.bounds.setSize(LayoutSize(1, 1));
 
-
     RenderLayerModelObject& renderer = layer.renderer();
     if (renderer.isFixedPositioned() && renderer.container() == &m_renderView) {
         // Because fixed elements get moved around without re-computing overlap, we have to compute an overlap
@@ -1798,6 +1794,44 @@ void RenderLayerCompositor::computeExtent(const LayerOverlapMap& overlapMap, con
     extent.extentComputed = true;
 }
 
+static bool createsClippingScope(const RenderLayer& layer)
+{
+    return layer.hasCompositedScrollableOverflow();
+}
+
+static Vector<LayerOverlapMap::LayerAndBounds> enclosingClippingScopes(const RenderLayer& layer, const RenderLayer& rootLayer)
+{
+    Vector<LayerOverlapMap::LayerAndBounds> clippingScopes;
+    clippingScopes.append({ const_cast<RenderLayer&>(rootLayer), { } });
+
+    if (!layer.hasCompositedScrollingAncestor())
+        return clippingScopes;
+
+    bool containingBlockCanSkipLayers = layer.renderer().isAbsolutelyPositioned();
+    for (const auto* ancestorLayer = layer.parent(); ancestorLayer; ancestorLayer = ancestorLayer->parent()) {
+        bool inContainingBlockChain = true;
+        if (containingBlockCanSkipLayers) {
+            inContainingBlockChain = ancestorLayer->renderer().canContainAbsolutelyPositionedObjects();
+            if (inContainingBlockChain)
+                containingBlockCanSkipLayers = ancestorLayer->renderer().isAbsolutelyPositioned();
+        }
+        
+        if (inContainingBlockChain && createsClippingScope(*ancestorLayer)) {
+            LayoutRect clipRect;
+            if (is<RenderBox>(ancestorLayer->renderer())) {
+                // FIXME: This is expensive. Broken with transforms.
+                LayoutPoint offsetFromRoot = ancestorLayer->convertToLayerCoords(&rootLayer, { });
+                clipRect = downcast<RenderBox>(ancestorLayer->renderer()).overflowClipRect(offsetFromRoot);
+            }
+
+            LayerOverlapMap::LayerAndBounds layerAndBounds { const_cast<RenderLayer&>(*ancestorLayer), clipRect };
+            clippingScopes.insert(1, layerAndBounds); // Order is roots to leaves.
+        }
+    }
+
+    return clippingScopes;
+}
+
 void RenderLayerCompositor::addToOverlapMap(LayerOverlapMap& overlapMap, const RenderLayer& layer, OverlapExtent& extent) const
 {
     if (layer.isRenderViewLayer())
@@ -1805,13 +1839,29 @@ void RenderLayerCompositor::addToOverlapMap(LayerOverlapMap& overlapMap, const R
 
     computeExtent(overlapMap, layer, extent);
 
-    LayoutRect clipRect = layer.backgroundClipRect(RenderLayer::ClipRectsContext(&rootRenderLayer(), AbsoluteClipRects)).rect(); // FIXME: Incorrect for CSS regions.
+    // FIXME: constrain the scopes (by composited stacking context ancestor I think).
+    auto clippingScopes = enclosingClippingScopes(layer, rootRenderLayer());
+
+    LayoutRect clipRect;
+    if (layer.hasCompositedScrollingAncestor()) {
+        // Compute a clip up to the composited scrolling ancestor, then convert it to absolute coordinates.
+        auto& scrollingScope = clippingScopes.last();
+        clipRect = layer.backgroundClipRect(RenderLayer::ClipRectsContext(&scrollingScope.layer, TemporaryClipRects, IgnoreOverlayScrollbarSize, IgnoreOverflowClip)).rect();
+        if (!clipRect.isInfinite())
+            clipRect.setLocation(layer.convertToLayerCoords(&rootRenderLayer(), clipRect.location()));
+    } else
+        clipRect = layer.backgroundClipRect(RenderLayer::ClipRectsContext(&rootRenderLayer(), AbsoluteClipRects)).rect(); // FIXME: Incorrect for CSS regions.
+
+    auto clippedBounds = extent.bounds;
+    if (!clipRect.isInfinite()) {
+        // On iOS, pageScaleFactor() is not applied by RenderView, so we should not scale here.
+        if (!m_renderView.settings().delegatesPageScaling())
+            clipRect.scale(pageScaleFactor());
+
+        clippedBounds.intersect(clipRect);
+    }
 
-    // On iOS, pageScaleFactor() is not applied by RenderView, so we should not scale here.
-    if (!m_renderView.settings().delegatesPageScaling())
-        clipRect.scale(pageScaleFactor());
-    clipRect.intersect(extent.bounds);
-    overlapMap.add(clipRect);
+    overlapMap.add(layer, clippedBounds, clippingScopes);
 }
 
 void RenderLayerCompositor::addDescendantsToOverlapMapRecursive(LayerOverlapMap& overlapMap, const RenderLayer& layer, const RenderLayer* ancestorLayer) const
@@ -1847,6 +1897,7 @@ void RenderLayerCompositor::addDescendantsToOverlapMapRecursive(LayerOverlapMap&
 void RenderLayerCompositor::updateOverlapMap(LayerOverlapMap& overlapMap, const RenderLayer& layer, OverlapExtent& layerExtent, bool didPushContainer, bool addLayerToOverlap, bool addDescendantsToOverlap) const
 {
     if (addLayerToOverlap) {
+        auto clippingScopes = enclosingClippingScopes(layer, rootRenderLayer());
         addToOverlapMap(overlapMap, layer, layerExtent);
         LOG_WITH_STREAM(CompositingOverlap, stream << "layer " << &layer << " contributes to overlap, added to map " << overlapMap);
     }
@@ -1863,6 +1914,14 @@ void RenderLayerCompositor::updateOverlapMap(LayerOverlapMap& overlapMap, const
     }
 }
 
+bool RenderLayerCompositor::layerOverlaps(const LayerOverlapMap& overlapMap, const RenderLayer& layer, OverlapExtent& layerExtent) const
+{
+    computeExtent(overlapMap, layer, layerExtent);
+
+    auto clippingScopes = enclosingClippingScopes(layer, rootRenderLayer());
+    return overlapMap.overlapsLayers(layer, layerExtent.bounds, clippingScopes);
+}
+
 #if ENABLE(VIDEO)
 bool RenderLayerCompositor::canAccelerateVideoRendering(RenderVideo& video) const
 {
index 7f66d18..12c1353 100644 (file)
@@ -409,6 +409,7 @@ private:
     void addToOverlapMap(LayerOverlapMap&, const RenderLayer&, OverlapExtent&) const;
     void addDescendantsToOverlapMapRecursive(LayerOverlapMap&, const RenderLayer&, const RenderLayer* ancestorLayer = nullptr) const;
     void updateOverlapMap(LayerOverlapMap&, const RenderLayer&, OverlapExtent&, bool didPushContainer, bool addLayerToOverlap, bool addDescendantsToOverlap = false) const;
+    bool layerOverlaps(const LayerOverlapMap&, const RenderLayer&, OverlapExtent&) const;
 
     void updateCompositingLayersTimerFired();