[Async overflow Scrolling] Update positioned node layers when overflows are scrolled
authorsimon.fraser@apple.com <simon.fraser@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 15 Mar 2019 16:26:09 +0000 (16:26 +0000)
committersimon.fraser@apple.com <simon.fraser@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 15 Mar 2019 16:26:09 +0000 (16:26 +0000)
https://bugs.webkit.org/show_bug.cgi?id=195733
rdar://problem/11642295

Reviewed by Antti Koivisto.

Source/WebCore:

Make ScrollingTree positioned nodes in the two cases where we need them, as
detected by RenderLayerCompositor::computeCoordinatedPositioningForLayer().

For "Moves" layers we know that the overflow is not in the z-order ancestor chain,
so ScrollingTree needs a map of overflow node -> affected positioned nodes which
notifyRelatedNodesAfterScrollPositionChange() uses to find nodes to update after
a scroll. Computing these dependent nodes in RenderLayerCompositor() would require
correct dependency analysis between an overflow layers and "positioned" layers which
is hard. It's easier to have "positioned" layers figure out which overflow nodes
affect them, then compute the inverse relationship when the scrolling tree is updated
which happens in ScrollingTreePositionedNode::commitStateBeforeChildren().

Tests: scrollingcoordinator/ios/absolute-layer-should-not-move-with-scroll.html
       scrollingcoordinator/ios/relative-layer-should-move-with-scroll.html

* page/scrolling/ScrollingTree.cpp:
(WebCore::ScrollingTree::commitTreeState):
(WebCore::ScrollingTree::applyLayerPositions):
(WebCore::ScrollingTree::notifyRelatedNodesAfterScrollPositionChange):
(WebCore::ScrollingTree::scrollingTreeAsText):
* page/scrolling/ScrollingTree.h:
(WebCore::ScrollingTree::overflowRelatedNodes):
* page/scrolling/ScrollingTreeOverflowScrollingNode.cpp:
(WebCore::ScrollingTreeOverflowScrollingNode::dumpProperties const):
* page/scrolling/ScrollingTreeOverflowScrollingNode.h:
* page/scrolling/cocoa/ScrollingTreePositionedNode.mm:
(WebCore::ScrollingTreePositionedNode::commitStateBeforeChildren):
(WebCore::ScrollingTreePositionedNode::applyLayerPositions):
(WebCore::ScrollingTreePositionedNode::relatedNodeScrollPositionDidChange):
* rendering/RenderLayerCompositor.cpp:
(WebCore::layerContainingBlockCrossesCoordinatedScrollingBoundary):
(WebCore::layerParentedAcrossCoordinatedScrollingBoundary):
(WebCore::RenderLayerCompositor::computeCoordinatedPositioningForLayer const):
(WebCore::collectRelatedCoordinatedScrollingNodes):
(WebCore::RenderLayerCompositor::updateScrollingNodeForPositioningRole):

LayoutTests:

New tests that use uiController.scrollUpdatesDisabled, and are the two move/stationary
cases.

* platform/ios-wk2/scrollingcoordinator/scrolling-tree/positioned-nodes-expected.txt:
* scrollingcoordinator/ios/absolute-layer-should-not-move-with-scroll-expected.html: Added.
* scrollingcoordinator/ios/absolute-layer-should-not-move-with-scroll.html: Added.
* scrollingcoordinator/ios/relative-layer-should-move-with-scroll-expected.html: Added.
* scrollingcoordinator/ios/relative-layer-should-move-with-scroll.html: Added.
* scrollingcoordinator/ios/ui-scrolling-tree-expected.txt: Fixed the last paren showing up.
* scrollingcoordinator/scrolling-tree/positioned-nodes-expected.txt: We make positioned nodes now.

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

16 files changed:
LayoutTests/ChangeLog
LayoutTests/platform/ios-wk2/scrollingcoordinator/scrolling-tree/positioned-nodes-expected.txt
LayoutTests/scrollingcoordinator/ios/absolute-layer-should-not-move-with-scroll-expected.html [new file with mode: 0644]
LayoutTests/scrollingcoordinator/ios/absolute-layer-should-not-move-with-scroll.html [new file with mode: 0644]
LayoutTests/scrollingcoordinator/ios/relative-layer-should-move-with-scroll-expected.html [new file with mode: 0644]
LayoutTests/scrollingcoordinator/ios/relative-layer-should-move-with-scroll.html [new file with mode: 0644]
LayoutTests/scrollingcoordinator/ios/ui-scrolling-tree-expected.txt
LayoutTests/scrollingcoordinator/scrolling-tree/positioned-nodes-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/page/scrolling/ScrollingTree.cpp
Source/WebCore/page/scrolling/ScrollingTree.h
Source/WebCore/page/scrolling/ScrollingTreeOverflowScrollingNode.cpp
Source/WebCore/page/scrolling/ScrollingTreeOverflowScrollingNode.h
Source/WebCore/page/scrolling/cocoa/ScrollingTreePositionedNode.h
Source/WebCore/page/scrolling/cocoa/ScrollingTreePositionedNode.mm
Source/WebCore/rendering/RenderLayerCompositor.cpp

index 8357973..823b8c4 100644 (file)
@@ -1,3 +1,22 @@
+2019-03-15  Simon Fraser  <simon.fraser@apple.com>
+
+        [Async overflow Scrolling] Update positioned node layers when overflows are scrolled
+        https://bugs.webkit.org/show_bug.cgi?id=195733
+        rdar://problem/11642295
+
+        Reviewed by Antti Koivisto.
+
+        New tests that use uiController.scrollUpdatesDisabled, and are the two move/stationary
+        cases.
+
+        * platform/ios-wk2/scrollingcoordinator/scrolling-tree/positioned-nodes-expected.txt:
+        * scrollingcoordinator/ios/absolute-layer-should-not-move-with-scroll-expected.html: Added.
+        * scrollingcoordinator/ios/absolute-layer-should-not-move-with-scroll.html: Added.
+        * scrollingcoordinator/ios/relative-layer-should-move-with-scroll-expected.html: Added.
+        * scrollingcoordinator/ios/relative-layer-should-move-with-scroll.html: Added.
+        * scrollingcoordinator/ios/ui-scrolling-tree-expected.txt: Fixed the last paren showing up.
+        * scrollingcoordinator/scrolling-tree/positioned-nodes-expected.txt: We make positioned nodes now.
+
 2019-03-15  Fujii Hironori  <Hironori.Fujii@sony.com>
 
         Unreviewed test gardening for WinCairo port
index 48e84aa..7b9db80 100644 (file)
@@ -17,7 +17,7 @@ Scrolling content
   (min layout viewport origin (0,0))
   (max layout viewport origin (0,141))
   (behavior for fixed 0)
-  (children 4
+  (children 5
     (Overflow scrolling node
       (scrollable area size 220 170)
       (contents size 220 1020)
@@ -54,6 +54,12 @@ Scrolling content
         )
       )
     )
+    (Positioned node
+      (layout constraints 
+        (layer-position-at-last-layout (10,30))
+        (positioning-behavior moves))
+      (related overflow nodes 1)
+    )
     (Overflow scrolling node
       (scrollable area size 220 170)
       (contents size 220 1020)
diff --git a/LayoutTests/scrollingcoordinator/ios/absolute-layer-should-not-move-with-scroll-expected.html b/LayoutTests/scrollingcoordinator/ios/absolute-layer-should-not-move-with-scroll-expected.html
new file mode 100644 (file)
index 0000000..227df9b
--- /dev/null
@@ -0,0 +1,61 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true ] -->
+<html>
+<head>
+    <meta name="viewport" content="initial-scale=1.0">
+    <style>
+        #scroller {
+            margin: 10px;
+            height: 300px;
+            width: 300px;
+            border: 1px solid black;
+            overflow: scroll;
+            opacity: 0.999; /* Make this a stacking context */
+        }
+        
+        .box {
+            width: 200px;
+            height: 200px;
+            background-color: gray;
+        }
+        
+        .absolute {
+            position: absolute;
+            top: 200px;
+            background-color: blue;
+            transform: translateZ(0); /* Currently need to trigger compositing */
+        }
+        
+        .scroll-content {
+            height: 2000px;
+        }
+    </style>
+    <script src="../../resources/ui-helper.js"></script>
+    <script>
+        if (window.testRunner)
+            testRunner.waitUntilDone();
+
+        if (window.internals)
+            window.internals.settings.setAsyncOverflowScrollingEnabled(true);
+
+        async function doTest()
+        {
+            await UIHelper.ensurePresentationUpdate(); // Not sure why this is necessary, but it is.
+            scroller.scrollTo(0, 100);
+            await UIHelper.ensurePresentationUpdate();
+
+            if (window.testRunner)
+                testRunner.notifyDone();
+        }
+        
+        window.addEventListener('load', doTest, false);
+    </script>
+</head>
+<body>
+    <div id="scroller">
+        <div class="scroll-content">
+            <div class="box"></div>
+            <div class="absolute box"></div>
+        </div>
+    </div>
+</body>
+</html>
diff --git a/LayoutTests/scrollingcoordinator/ios/absolute-layer-should-not-move-with-scroll.html b/LayoutTests/scrollingcoordinator/ios/absolute-layer-should-not-move-with-scroll.html
new file mode 100644 (file)
index 0000000..f26c757
--- /dev/null
@@ -0,0 +1,71 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true ] -->
+<html>
+<head>
+    <meta name="viewport" content="initial-scale=1.0">
+    <style>
+        #scroller {
+            margin: 10px;
+            height: 300px;
+            width: 300px;
+            border: 1px solid black;
+            overflow: scroll;
+            opacity: 0.999; /* Make this a stacking context */
+        }
+        
+        .box {
+            width: 200px;
+            height: 200px;
+            background-color: gray;
+        }
+        
+        .absolute {
+            position: absolute;
+            top: 200px;
+            background-color: blue;
+            transform: translateZ(0); /* Currently need to trigger compositing */
+        }
+        
+        .scroll-content {
+            height: 2000px;
+        }
+    </style>
+    <script>
+        if (window.testRunner)
+            testRunner.waitUntilDone();
+
+        if (window.internals)
+            window.internals.settings.setAsyncOverflowScrollingEnabled(true);
+
+        function getUnstableScrollScript(x, y, scrollX, scrollY)
+        {
+            return `(function() {
+                uiController.scrollUpdatesDisabled = true;
+                uiController.immediateScrollElementAtContentPointToOffset(${x}, ${y}, ${scrollX}, ${scrollY});
+                uiController.doAfterPresentationUpdate(function() {
+                    uiController.uiScriptComplete();
+                });
+            })();`;
+        }
+
+        function doTest()
+        {
+            if (!testRunner.runUIScript)
+                return
+
+            testRunner.runUIScript(getUnstableScrollScript(50, 50, 0, 100), () => {
+                testRunner.notifyDone();
+            });
+        }
+        
+        window.addEventListener('load', doTest, false);
+    </script>
+</head>
+<body>
+    <div id="scroller">
+        <div class="scroll-content">
+            <div class="box"></div>
+            <div class="absolute box"></div>
+        </div>
+    </div>
+</body>
+</html>
diff --git a/LayoutTests/scrollingcoordinator/ios/relative-layer-should-move-with-scroll-expected.html b/LayoutTests/scrollingcoordinator/ios/relative-layer-should-move-with-scroll-expected.html
new file mode 100644 (file)
index 0000000..4798df1
--- /dev/null
@@ -0,0 +1,55 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true ] -->
+<html>
+<head>
+    <meta name="viewport" content="initial-scale=1.0">
+    <style>
+        #scroller {
+            margin: 10px;
+            height: 300px;
+            width: 300px;
+            border: 1px solid black;
+            overflow: scroll;
+        }
+        
+        .box {
+            position: relative;
+            z-index: 0; /* stacking context ancestor is the root */
+            top: 250px;
+            width: 200px;
+            height: 200px;
+            background-color: blue;
+        }
+        
+        .scroll-content {
+            height: 2000px;
+        }
+    </style>
+    <script src="../../resources/ui-helper.js"></script>
+    <script>
+        if (window.testRunner)
+            testRunner.waitUntilDone();
+
+        if (window.internals)
+            window.internals.settings.setAsyncOverflowScrollingEnabled(true);
+
+        async function doTest()
+        {
+            await UIHelper.ensurePresentationUpdate(); // Not sure why this is necessary, but it is.
+            scroller.scrollTo(0, 250);
+            await UIHelper.ensurePresentationUpdate();
+
+            if (window.testRunner)
+                testRunner.notifyDone();
+        }
+        
+        window.addEventListener('load', doTest, false);
+    </script>
+</head>
+<body>
+    <div id="scroller">
+        <div class="scroll-content">
+            <div class="box"></div>
+        </div>
+    </div>
+</body>
+</html>
diff --git a/LayoutTests/scrollingcoordinator/ios/relative-layer-should-move-with-scroll.html b/LayoutTests/scrollingcoordinator/ios/relative-layer-should-move-with-scroll.html
new file mode 100644 (file)
index 0000000..420466d
--- /dev/null
@@ -0,0 +1,65 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true ] -->
+<html>
+<head>
+    <meta name="viewport" content="initial-scale=1.0">
+    <style>
+        #scroller {
+            margin: 10px;
+            height: 300px;
+            width: 300px;
+            border: 1px solid black;
+            overflow: scroll;
+        }
+        
+        .box {
+            position: relative;
+            z-index: 0; /* stacking context ancestor is the root */
+            top: 250px;
+            width: 200px;
+            height: 200px;
+            background-color: blue;
+        }
+        
+        .scroll-content {
+            height: 2000px;
+        }
+    </style>
+    <script>
+        if (window.testRunner)
+            testRunner.waitUntilDone();
+
+        if (window.internals)
+            window.internals.settings.setAsyncOverflowScrollingEnabled(true);
+
+        function getUnstableScrollScript(x, y, scrollX, scrollY)
+        {
+            return `(function() {
+                uiController.scrollUpdatesDisabled = true;
+                uiController.immediateScrollElementAtContentPointToOffset(${x}, ${y}, ${scrollX}, ${scrollY});
+                uiController.doAfterPresentationUpdate(function() {
+                    uiController.uiScriptComplete();
+                });
+            })();`;
+        }
+
+        function doTest()
+        {
+            if (!testRunner.runUIScript)
+                return
+
+            testRunner.runUIScript(getUnstableScrollScript(50, 50, 0, 250), () => {
+                testRunner.notifyDone();
+            });
+        }
+        
+        window.addEventListener('load', doTest, false);
+    </script>
+</head>
+<body>
+    <div id="scroller">
+        <div class="scroll-content">
+            <div class="box"></div>
+        </div>
+    </div>
+</body>
+</html>
index 06be85a..27cfb7e 100644 (file)
@@ -17,4 +17,4 @@
       (fixed constraints 
         (viewport-rect-at-last-layout (0,0) width=320 height=548)
         (layer-position-at-last-layout (12,10)))
-      (layer top left (12,10))))
+      (layer top left (12,10)))))
index 3252823..61b9ad9 100644 (file)
@@ -18,7 +18,7 @@ Scrolling content
   (min layout viewport origin (0,0))
   (max layout viewport origin (0,141))
   (behavior for fixed 0)
-  (children 4
+  (children 5
     (Overflow scrolling node
       (scrollable area size 205 155)
       (contents size 205 1020)
@@ -55,6 +55,12 @@ Scrolling content
         )
       )
     )
+    (Positioned node
+      (layout constraints 
+        (layer-position-at-last-layout (10,28))
+        (positioning-behavior moves))
+      (related overflow nodes 1)
+    )
     (Overflow scrolling node
       (scrollable area size 205 155)
       (contents size 205 1020)
index 7837566..fb3d092 100644 (file)
@@ -1,3 +1,47 @@
+2019-03-15  Simon Fraser  <simon.fraser@apple.com>
+
+        [Async overflow Scrolling] Update positioned node layers when overflows are scrolled
+        https://bugs.webkit.org/show_bug.cgi?id=195733
+        rdar://problem/11642295
+
+        Reviewed by Antti Koivisto.
+
+        Make ScrollingTree positioned nodes in the two cases where we need them, as
+        detected by RenderLayerCompositor::computeCoordinatedPositioningForLayer().
+
+        For "Moves" layers we know that the overflow is not in the z-order ancestor chain,
+        so ScrollingTree needs a map of overflow node -> affected positioned nodes which
+        notifyRelatedNodesAfterScrollPositionChange() uses to find nodes to update after
+        a scroll. Computing these dependent nodes in RenderLayerCompositor() would require
+        correct dependency analysis between an overflow layers and "positioned" layers which
+        is hard. It's easier to have "positioned" layers figure out which overflow nodes
+        affect them, then compute the inverse relationship when the scrolling tree is updated
+        which happens in ScrollingTreePositionedNode::commitStateBeforeChildren().
+
+        Tests: scrollingcoordinator/ios/absolute-layer-should-not-move-with-scroll.html
+               scrollingcoordinator/ios/relative-layer-should-move-with-scroll.html
+
+        * page/scrolling/ScrollingTree.cpp:
+        (WebCore::ScrollingTree::commitTreeState):
+        (WebCore::ScrollingTree::applyLayerPositions):
+        (WebCore::ScrollingTree::notifyRelatedNodesAfterScrollPositionChange):
+        (WebCore::ScrollingTree::scrollingTreeAsText):
+        * page/scrolling/ScrollingTree.h:
+        (WebCore::ScrollingTree::overflowRelatedNodes):
+        * page/scrolling/ScrollingTreeOverflowScrollingNode.cpp:
+        (WebCore::ScrollingTreeOverflowScrollingNode::dumpProperties const):
+        * page/scrolling/ScrollingTreeOverflowScrollingNode.h:
+        * page/scrolling/cocoa/ScrollingTreePositionedNode.mm:
+        (WebCore::ScrollingTreePositionedNode::commitStateBeforeChildren):
+        (WebCore::ScrollingTreePositionedNode::applyLayerPositions):
+        (WebCore::ScrollingTreePositionedNode::relatedNodeScrollPositionDidChange):
+        * rendering/RenderLayerCompositor.cpp:
+        (WebCore::layerContainingBlockCrossesCoordinatedScrollingBoundary):
+        (WebCore::layerParentedAcrossCoordinatedScrollingBoundary):
+        (WebCore::RenderLayerCompositor::computeCoordinatedPositioningForLayer const):
+        (WebCore::collectRelatedCoordinatedScrollingNodes):
+        (WebCore::RenderLayerCompositor::updateScrollingNodeForPositioningRole):
+
 2019-03-15  Antti Koivisto  <antti@apple.com>
 
         Optimize Region for single rectangle case
index a57251f..45e4640 100644 (file)
@@ -174,6 +174,8 @@ void ScrollingTree::commitTreeState(std::unique_ptr<ScrollingStateTree> scrollin
     for (auto nodeID : m_nodeMap.keys())
         unvisitedNodes.add(nodeID);
 
+    m_overflowRelatedNodesMap.clear();
+
     // orphanNodes keeps child nodes alive while we rebuild child lists.
     OrphanScrollingNodeMap orphanNodes;
     updateTreeFromStateNode(rootNode, orphanNodes, unvisitedNodes);
@@ -185,6 +187,8 @@ void ScrollingTree::commitTreeState(std::unique_ptr<ScrollingStateTree> scrollin
         LOG(Scrolling, "ScrollingTree::commitTreeState - removing unvisited node %" PRIu64, nodeID);
         m_nodeMap.remove(nodeID);
     }
+
+    LOG(Scrolling, "committed ScrollingTree\n%s", scrollingTreeAsText(ScrollingStateTreeAsTextBehaviorDebug).utf8().data());
 }
 
 void ScrollingTree::updateTreeFromStateNode(const ScrollingStateNode* stateNode, OrphanScrollingNodeMap& orphanNodes, HashSet<ScrollingNodeID>& unvisitedNodes)
@@ -263,7 +267,11 @@ void ScrollingTree::applyLayerPositions()
     if (!m_rootNode)
         return;
 
+    LOG(Scrolling, "\nScrollingTree %p applyLayerPositions", this);
+
     applyLayerPositionsRecursive(*m_rootNode, { }, { });
+
+    LOG(Scrolling, "ScrollingTree %p applyLayerPositions - done\n", this);
 }
 
 void ScrollingTree::applyLayerPositionsRecursive(ScrollingTreeNode& currNode, FloatRect layoutViewport, FloatSize cumulativeDelta)
@@ -291,6 +299,8 @@ ScrollingTreeNode* ScrollingTree::nodeForID(ScrollingNodeID nodeID) const
 
 void ScrollingTree::notifyRelatedNodesAfterScrollPositionChange(ScrollingTreeScrollingNode& changedNode)
 {
+    Vector<ScrollingNodeID> additionalUpdateRoots;
+    
     FloatSize deltaFromLastCommittedScrollPosition;
     FloatRect currentFrameLayoutViewport;
     if (is<ScrollingTreeFrameScrollingNode>(changedNode))
@@ -300,9 +310,17 @@ void ScrollingTree::notifyRelatedNodesAfterScrollPositionChange(ScrollingTreeScr
 
         if (auto* frameScrollingNode = changedNode.enclosingFrameNodeIncludingSelf())
             currentFrameLayoutViewport = frameScrollingNode->layoutViewport();
+        
+        additionalUpdateRoots = overflowRelatedNodes().get(changedNode.scrollingNodeID());
     }
 
     notifyRelatedNodesRecursive(changedNode, changedNode, currentFrameLayoutViewport, deltaFromLastCommittedScrollPosition);
+    
+    for (auto positionedNodeID : additionalUpdateRoots) {
+        auto* positionedNode = nodeForID(positionedNodeID);
+        if (positionedNode)
+            notifyRelatedNodesRecursive(changedNode, *positionedNode, currentFrameLayoutViewport, deltaFromLastCommittedScrollPosition);
+    }
 }
 
 void ScrollingTree::notifyRelatedNodesRecursive(ScrollingTreeScrollingNode& changedNode, ScrollingTreeNode& currNode, const FloatRect& layoutViewport, FloatSize cumulativeDelta)
@@ -451,26 +469,37 @@ void ScrollingTree::clearLatchedNode()
     m_treeState.latchedNodeID = 0;
 }
 
-String ScrollingTree::scrollingTreeAsText()
+String ScrollingTree::scrollingTreeAsText(ScrollingStateTreeAsTextBehavior behavior)
 {
     TextStream ts(TextStream::LineMode::MultipleLine);
 
-    TextStream::GroupScope scope(ts);
-    ts << "scrolling tree";
+    {
+        TextStream::GroupScope scope(ts);
+        ts << "scrolling tree";
 
-    LockHolder locker(m_treeStateMutex);
+        LockHolder locker(m_treeStateMutex);
 
-    if (m_treeState.latchedNodeID)
-        ts.dumpProperty("latched node", m_treeState.latchedNodeID);
+        if (m_treeState.latchedNodeID)
+            ts.dumpProperty("latched node", m_treeState.latchedNodeID);
 
-    if (!m_treeState.mainFrameScrollPosition.isZero())
-        ts.dumpProperty("main frame scroll position", m_treeState.mainFrameScrollPosition);
-    
-    if (m_rootNode) {
-        TextStream::GroupScope scope(ts);
-        m_rootNode->dump(ts, ScrollingStateTreeAsTextBehaviorIncludeLayerPositions);
+        if (!m_treeState.mainFrameScrollPosition.isZero())
+            ts.dumpProperty("main frame scroll position", m_treeState.mainFrameScrollPosition);
+        
+        if (m_rootNode) {
+            TextStream::GroupScope scope(ts);
+            m_rootNode->dump(ts, behavior | ScrollingStateTreeAsTextBehaviorIncludeLayerPositions);
+        }
+        
+        if (behavior & ScrollingStateTreeAsTextBehaviorIncludeNodeIDs && !m_overflowRelatedNodesMap.isEmpty()) {
+            TextStream::GroupScope scope(ts);
+            ts << "overflow related nodes";
+            {
+                TextStream::IndentScope indentScope(ts);
+                for (auto& it : m_overflowRelatedNodesMap)
+                    ts << "\n" << indent << it.key << " -> " << it.value;
+            }
+        }
     }
-
     return ts.release();
 }
 
index 01e2218..e7202f6 100644 (file)
@@ -144,8 +144,13 @@ public:
         ASSERT(m_fixedOrStickyNodeCount);
         --m_fixedOrStickyNodeCount;
     }
-    
-    WEBCORE_EXPORT String scrollingTreeAsText();
+
+    // A map of overflow scrolling nodes to positioned nodes which need to be updated
+    // when the scroller changes, but are not descendants.
+    using RelatedNodesMap = HashMap<ScrollingNodeID, Vector<ScrollingNodeID>>;
+    RelatedNodesMap& overflowRelatedNodes() { return m_overflowRelatedNodesMap; }
+
+    WEBCORE_EXPORT String scrollingTreeAsText(ScrollingStateTreeAsTextBehavior = ScrollingStateTreeAsTextBehaviorNormal);
     
 protected:
     void setMainFrameScrollPosition(FloatPoint);
@@ -167,6 +172,8 @@ private:
     using ScrollingTreeNodeMap = HashMap<ScrollingNodeID, ScrollingTreeNode*>;
     ScrollingTreeNodeMap m_nodeMap;
 
+    RelatedNodesMap m_overflowRelatedNodesMap;
+
     struct TreeState {
         ScrollingNodeID latchedNodeID { 0 };
         EventTrackingRegions eventTrackingRegions;
index dda0489..23688b3 100644 (file)
@@ -40,6 +40,12 @@ ScrollingTreeOverflowScrollingNode::ScrollingTreeOverflowScrollingNode(Scrolling
 
 ScrollingTreeOverflowScrollingNode::~ScrollingTreeOverflowScrollingNode() = default;
 
+void ScrollingTreeOverflowScrollingNode::dumpProperties(TextStream& ts, ScrollingStateTreeAsTextBehavior behavior) const
+{
+    ts << "overflow scrolling node";
+    ScrollingTreeNode::dumpProperties(ts, behavior);
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(ASYNC_SCROLLING)
index 97fb95c..9c17aa6 100644 (file)
@@ -37,6 +37,8 @@ public:
 
 protected:
     WEBCORE_EXPORT ScrollingTreeOverflowScrollingNode(ScrollingTree&, ScrollingNodeID);
+    
+    WEBCORE_EXPORT void dumpProperties(TextStream&, ScrollingStateTreeAsTextBehavior) const;
 };
 
 } // namespace WebCore
index 710cdc7..2f77b7c 100644 (file)
@@ -49,7 +49,7 @@ private:
 
     void applyLayerPositions(const FloatRect& layoutViewport, FloatSize& cumulativeDelta) override;
 
-    void dumpProperties(WTF::TextStream&, ScrollingStateTreeAsTextBehavior) const override;
+    WEBCORE_EXPORT void dumpProperties(WTF::TextStream&, ScrollingStateTreeAsTextBehavior) const override;
 
     Vector<ScrollingNodeID> m_relatedOverflowScrollingNodes;
     LayoutConstraints m_constraints;
index 6f48bff..3317753 100644 (file)
@@ -31,6 +31,7 @@
 #import "Logging.h"
 #import "ScrollingStatePositionedNode.h"
 #import "ScrollingTree.h"
+#import "ScrollingTreeOverflowScrollingNode.h"
 #import "ScrollingTreeScrollingNode.h"
 #import <QuartzCore/CALayer.h>
 #import <wtf/text/TextStream.h>
@@ -61,30 +62,51 @@ void ScrollingTreePositionedNode::commitStateBeforeChildren(const ScrollingState
 
     if (positionedStateNode.hasChangedProperty(ScrollingStatePositionedNode::LayoutConstraintData))
         m_constraints = positionedStateNode.layoutConstraints();
+
+    // Tell the ScrollingTree about non-ancestor overflow nodes which affect this node.
+    if (m_constraints.scrollPositioningBehavior() == ScrollPositioningBehavior::Moves) {
+        auto& relatedNodes = scrollingTree().overflowRelatedNodes();
+        for (auto overflowNodeID : m_relatedOverflowScrollingNodes) {
+            relatedNodes.ensure(overflowNodeID, [] {
+                return Vector<ScrollingNodeID>();
+            }).iterator->value.append(scrollingNodeID());
+        }
+    }
 }
 
 void ScrollingTreePositionedNode::applyLayerPositions(const FloatRect&, FloatSize& cumulativeDelta)
 {
-    FloatSize layerOffset; // FIXME: layerOffset needs to be computed by looking at scrolling tree deltas
-    // in the overflow nodes that affect this node. Some of that may come in via cumulativeDelta.
-
-    LOG_WITH_STREAM(Scrolling, stream << "ScrollingTreePositionedNode " << scrollingNodeID() << " applyLayerPositions: total overflow delta " << layerOffset);
+    // Note that we ignore cumulativeDelta because it will contain the delta for ancestor scrollers,
+    // but not non-ancestor ones, so it's simpler to just recompute from the scrollers we know about here.
+    FloatSize scrollOffsetSinceLastCommit;
+    for (auto nodeID : m_relatedOverflowScrollingNodes) {
+        if (auto* node = scrollingTree().nodeForID(nodeID)) {
+            if (is<ScrollingTreeOverflowScrollingNode>(node)) {
+                auto& overflowNode = downcast<ScrollingTreeOverflowScrollingNode>(*node);
+                scrollOffsetSinceLastCommit += overflowNode.lastCommittedScrollPosition() - overflowNode.currentScrollPosition();
+            }
+        }
+    }
+    LOG_WITH_STREAM(Scrolling, stream << "ScrollingTreePositionedNode " << scrollingNodeID() << " applyLayerPositions: overflow delta " << scrollOffsetSinceLastCommit);
 
-    layerOffset += cumulativeDelta;
-    // Stationary nodes move in the opposite direction.
-    if (m_constraints.scrollPositioningBehavior() == ScrollPositioningBehavior::Stationary)
+    auto layerOffset = -scrollOffsetSinceLastCommit;
+    if (m_constraints.scrollPositioningBehavior() == ScrollPositioningBehavior::Stationary) {
+        // Stationary nodes move in the opposite direction.
         layerOffset = -layerOffset;
+    }
 
     FloatPoint layerPosition = m_constraints.layerPositionAtLastLayout() - layerOffset;
     [m_layer _web_setLayerTopLeftPosition:layerPosition - m_constraints.alignmentOffset()];
 
-    cumulativeDelta += layerPosition - m_constraints.layerPositionAtLastLayout();
+    // FIXME: Should our scroller deltas propagate to descendants?
+    cumulativeDelta = layerPosition - m_constraints.layerPositionAtLastLayout();
 }
 
 void ScrollingTreePositionedNode::relatedNodeScrollPositionDidChange(const ScrollingTreeScrollingNode& changedNode, const FloatRect& layoutViewport, FloatSize& cumulativeDelta)
 {
-    UNUSED_PARAM(changedNode);
-    // FIXME: This will avoid doing work if we can determine that changedNode doesn't affect this positioned node.
+    if (!m_relatedOverflowScrollingNodes.contains(changedNode.scrollingNodeID()))
+        return;
+
     applyLayerPositions(layoutViewport, cumulativeDelta);
 }
 
index a1ec7f5..f880e82 100644 (file)
@@ -2869,17 +2869,117 @@ bool RenderLayerCompositor::useCoordinatedScrollingForLayer(const RenderLayer& l
     return false;
 }
 
+// Is this layer's containingBlock an ancestor of scrollable overflow, and is the layer's compositing ancestor inside that overflow?
+static bool layerContainingBlockCrossesCoordinatedScrollingBoundary(const RenderLayer& layer, const RenderLayer& compositedAncestor)
+{
+    ASSERT(layer.isComposited());
+    ASSERT(layer.renderer().style().position() == PositionType::Absolute);
+
+    bool sawCompositingAncestor = false;
+    for (const auto* currLayer = layer.parent(); currLayer; currLayer = currLayer->parent()) {
+        if (currLayer->renderer().canContainAbsolutelyPositionedObjects())
+            return false;
+
+        if (currLayer == &compositedAncestor)
+            sawCompositingAncestor = true;
+
+        if (currLayer->hasCompositedScrollableOverflow())
+            return sawCompositingAncestor;
+    }
+
+    return false;
+}
+
+// Is there scrollable overflow between this layer and its composited ancestor?
+static bool layerParentedAcrossCoordinatedScrollingBoundary(const RenderLayer& layer, const RenderLayer& compositedAncestor)
+{
+    ASSERT(layer.isComposited());
+
+    for (const auto* currLayer = layer.parent(); currLayer != &compositedAncestor; currLayer = currLayer->parent()) {
+        if (currLayer->hasCompositedScrollableOverflow())
+            return true;
+    }
+
+    return false;
+}
+
 ScrollPositioningBehavior RenderLayerCompositor::computeCoordinatedPositioningForLayer(const RenderLayer& layer) const
 {
     if (layer.isRenderViewLayer())
         return ScrollPositioningBehavior::None;
 
-    // FIXME: This will look at the containing block and stacking context ancestor chains and determine
-    // whether this layer needs to be repositioned when a composited overflow scroll scrolls.
+    auto* scrollingCoordinator = this->scrollingCoordinator();
+    if (!scrollingCoordinator)
+        return ScrollPositioningBehavior::None;
+
+    // There are two cases we have to deal with here:
+    // 1. There's a composited overflow:scroll in the parent chain between the renderer and its containing block, and the layer's
+    //    composited (z-order) ancestor is inside the scroller or is the scroller. In this case, we have to compensate for scroll position
+    //    changes to make the positioned layer stay in the same place. This only applies to position:absolute (since we handle fixed elsewhere).
+    auto* compositedAncestor = layer.ancestorCompositingLayer();
+
+    auto& renderer = layer.renderer();
+    if (renderer.isOutOfFlowPositioned() && renderer.style().position() == PositionType::Absolute) {
+        if (layerContainingBlockCrossesCoordinatedScrollingBoundary(layer, *compositedAncestor))
+            return ScrollPositioningBehavior::Stationary;
+
+        return ScrollPositioningBehavior::None;
+    }
+
+    // 2. The layer's containing block is the overflow or inside the overflow:scroll, but its z-order ancestor is
+    //    outside the overflow:scroll. In that case, we have to move the layer via the scrolling tree to make
+    //    it move along with the overflow scrolling.
+    if (layerParentedAcrossCoordinatedScrollingBoundary(layer, *compositedAncestor))
+        return ScrollPositioningBehavior::Moves;
 
     return ScrollPositioningBehavior::None;
 }
 
+static Vector<ScrollingNodeID> collectRelatedCoordinatedScrollingNodes(const RenderLayer& layer, ScrollPositioningBehavior positioningBehavior)
+{
+    Vector<ScrollingNodeID> overflowNodeData;
+
+    switch (positioningBehavior) {
+    case ScrollPositioningBehavior::Moves: {
+        // Collect all the composited scrollers between this layer and its composited ancestor.
+        auto* compositedAncestor = layer.ancestorCompositingLayer();
+        for (const auto* currLayer = layer.parent(); currLayer != compositedAncestor; currLayer = currLayer->parent()) {
+            if (currLayer->hasCompositedScrollableOverflow()) {
+                auto scrollingNodeID = currLayer->backing()->scrollingNodeIDForRole(ScrollCoordinationRole::Scrolling);
+                if (scrollingNodeID)
+                    overflowNodeData.append(scrollingNodeID);
+                else
+                    LOG(Scrolling, "Layer %p doesn't have scrolling node ID yet", &layer);
+            }
+        }
+        break;
+    }
+    case ScrollPositioningBehavior::Stationary: {
+        // Collect all the composited scrollers between this layer and its containing block.
+        ASSERT(layer.renderer().style().position() == PositionType::Absolute);
+        for (const auto* currLayer = layer.parent(); currLayer; currLayer = currLayer->parent()) {
+            if (currLayer->renderer().canContainAbsolutelyPositionedObjects())
+                break;
+
+            if (currLayer->hasCompositedScrollableOverflow()) {
+                auto scrollingNodeID = currLayer->backing()->scrollingNodeIDForRole(ScrollCoordinationRole::Scrolling);
+                if (scrollingNodeID)
+                    overflowNodeData.append(scrollingNodeID);
+                else
+                    LOG(Scrolling, "Layer %p doesn't have scrolling node ID yet", &layer);
+            }
+        }
+        // Don't need to do anything because the layer is a descendant of the overflow in stacking.
+        break;
+    }
+    case ScrollPositioningBehavior::None:
+        ASSERT_NOT_REACHED();
+        break;
+    }
+
+    return overflowNodeData;
+}
+
 bool RenderLayerCompositor::isLayerForIFrameWithScrollCoordinatedContents(const RenderLayer& layer) const
 {
     if (!is<RenderWidget>(layer.renderer()))
@@ -4078,15 +4178,16 @@ ScrollingNodeID RenderLayerCompositor::updateScrollingNodeForPositioningRole(Ren
     }
 
     if (changes & ScrollingNodeChangeFlags::LayerGeometry && treeState.parentNodeID) {
-        Vector<ScrollingNodeID> relatedNodeIDs; // FIXME: This will do a tree walk to figure out which composited overflows affect this positioned node.
+        // Would be nice to avoid calling computeCoordinatedPositioningForLayer() again.
+        auto positioningBehavior = computeCoordinatedPositioningForLayer(layer);
+        auto relatedNodeIDs = collectRelatedCoordinatedScrollingNodes(layer, positioningBehavior);
         scrollingCoordinator->setRelatedOverflowScrollingNodes(newNodeID, WTFMove(relatedNodeIDs));
 
         auto* graphicsLayer = layer.backing()->graphicsLayer();
         LayoutConstraints constraints;
         constraints.setAlignmentOffset(graphicsLayer->pixelAlignmentOffset());
         constraints.setLayerPositionAtLastLayout(graphicsLayer->position());
-        // Would be nice to avoid calling computeCoordinatedPositioningForLayer() again.
-        constraints.setScrollPositioningBehavior(computeCoordinatedPositioningForLayer(layer));
+        constraints.setScrollPositioningBehavior(positioningBehavior);
         scrollingCoordinator->setPositionedNodeGeometry(newNodeID, constraints);
     }