2 * Copyright (C) 2014 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
28 #if ENABLE(ASYNC_SCROLLING)
29 #include "AsyncScrollingCoordinator.h"
31 #include "EditorClient.h"
33 #include "FrameView.h"
34 #include "GraphicsLayer.h"
35 #include "MainFrame.h"
37 #include "ScrollingConstraints.h"
38 #include "ScrollingStateFixedNode.h"
39 #include "ScrollingStateFrameScrollingNode.h"
40 #include "ScrollingStateOverflowScrollingNode.h"
41 #include "ScrollingStateStickyNode.h"
42 #include "ScrollingStateTree.h"
46 AsyncScrollingCoordinator::AsyncScrollingCoordinator(Page* page)
47 : ScrollingCoordinator(page)
48 , m_updateNodeScrollPositionTimer(*this, &AsyncScrollingCoordinator::updateScrollPositionAfterAsyncScrollTimerFired)
49 , m_scrollingStateTree(std::make_unique<ScrollingStateTree>(this))
53 AsyncScrollingCoordinator::~AsyncScrollingCoordinator()
57 void AsyncScrollingCoordinator::scrollingStateTreePropertiesChanged()
59 scheduleTreeStateCommit();
62 static inline void setStateScrollingNodeSnapOffsetsAsFloat(ScrollingStateScrollingNode& node, ScrollEventAxis axis, const Vector<LayoutUnit>& snapOffsets, float deviceScaleFactor)
64 // FIXME: Incorporate current page scale factor in snapping to device pixel. Perhaps we should just convert to float here and let UI process do the pixel snapping?
65 Vector<float> snapOffsetsAsFloat;
66 snapOffsetsAsFloat.reserveInitialCapacity(snapOffsets.size());
67 for (size_t i = 0; i < snapOffsets.size(); ++i)
68 snapOffsetsAsFloat.append(roundToDevicePixel(snapOffsets[i], deviceScaleFactor, false));
70 if (axis == ScrollEventAxis::Horizontal)
71 node.setHorizontalSnapOffsets(snapOffsetsAsFloat);
73 node.setVerticalSnapOffsets(snapOffsetsAsFloat);
76 void AsyncScrollingCoordinator::setNonFastScrollableRegionDirty()
78 m_nonFastScrollableRegionDirty = true;
79 // We have to schedule a commit, but the computed non-fast region may not have actually changed.
80 scheduleTreeStateCommit();
83 void AsyncScrollingCoordinator::willCommitTree()
85 updateNonFastScrollableRegion();
88 void AsyncScrollingCoordinator::updateNonFastScrollableRegion()
90 if (!m_nonFastScrollableRegionDirty)
93 if (!m_scrollingStateTree->rootStateNode())
96 m_scrollingStateTree->rootStateNode()->setNonFastScrollableRegion(computeNonFastScrollableRegion(m_page->mainFrame(), IntPoint()));
97 m_nonFastScrollableRegionDirty = false;
100 void AsyncScrollingCoordinator::frameViewLayoutUpdated(FrameView& frameView)
102 ASSERT(isMainThread());
105 // If there isn't a root node yet, don't do anything. We'll be called again after creating one.
106 if (!m_scrollingStateTree->rootStateNode())
109 // Compute the region of the page that we can't do fast scrolling for. This currently includes
110 // all scrollable areas, such as subframes, overflow divs and list boxes. We need to do this even if the
111 // frame view whose layout was updated is not the main frame.
112 // In the future, we may want to have the ability to set non-fast scrolling regions for more than
113 // just the root node. But right now, this concept only applies to the root.
114 m_scrollingStateTree->rootStateNode()->setNonFastScrollableRegion(computeNonFastScrollableRegion(m_page->mainFrame(), IntPoint()));
115 m_nonFastScrollableRegionDirty = false;
117 if (!coordinatesScrollingForFrameView(frameView))
120 ScrollingStateFrameScrollingNode* node = downcast<ScrollingStateFrameScrollingNode>(m_scrollingStateTree->stateNodeForID(frameView.scrollLayerID()));
124 Scrollbar* verticalScrollbar = frameView.verticalScrollbar();
125 Scrollbar* horizontalScrollbar = frameView.horizontalScrollbar();
126 node->setScrollbarPaintersFromScrollbars(verticalScrollbar, horizontalScrollbar);
128 node->setFrameScaleFactor(frameView.frame().frameScaleFactor());
129 node->setHeaderHeight(frameView.headerHeight());
130 node->setFooterHeight(frameView.footerHeight());
131 node->setTopContentInset(frameView.topContentInset());
133 node->setScrollOrigin(frameView.scrollOrigin());
134 node->setScrollableAreaSize(frameView.visibleContentRect().size());
135 node->setTotalContentsSize(frameView.totalContentsSize());
136 node->setReachableContentsSize(frameView.totalContentsSize());
138 #if ENABLE(CSS_SCROLL_SNAP)
139 frameView.updateSnapOffsets();
140 if (const Vector<LayoutUnit>* horizontalSnapOffsets = frameView.horizontalSnapOffsets())
141 setStateScrollingNodeSnapOffsetsAsFloat(*node, ScrollEventAxis::Horizontal, *horizontalSnapOffsets, m_page->deviceScaleFactor());
143 if (const Vector<LayoutUnit>* verticalSnapOffsets = frameView.verticalSnapOffsets())
144 setStateScrollingNodeSnapOffsetsAsFloat(*node, ScrollEventAxis::Vertical, *verticalSnapOffsets, m_page->deviceScaleFactor());
147 ScrollableAreaParameters scrollParameters;
148 scrollParameters.horizontalScrollElasticity = frameView.horizontalScrollElasticity();
149 scrollParameters.verticalScrollElasticity = frameView.verticalScrollElasticity();
150 scrollParameters.hasEnabledHorizontalScrollbar = horizontalScrollbar && horizontalScrollbar->enabled();
151 scrollParameters.hasEnabledVerticalScrollbar = verticalScrollbar && verticalScrollbar->enabled();
152 scrollParameters.horizontalScrollbarMode = frameView.horizontalScrollbarMode();
153 scrollParameters.verticalScrollbarMode = frameView.verticalScrollbarMode();
155 node->setScrollableAreaParameters(scrollParameters);
158 void AsyncScrollingCoordinator::frameViewNonFastScrollableRegionChanged(FrameView&)
160 if (!m_scrollingStateTree->rootStateNode())
163 setNonFastScrollableRegionDirty();
166 void AsyncScrollingCoordinator::frameViewRootLayerDidChange(FrameView& frameView)
168 ASSERT(isMainThread());
171 if (!coordinatesScrollingForFrameView(frameView))
174 // FIXME: In some navigation scenarios, the FrameView has no RenderView or that RenderView has not been composited.
175 // This needs cleaning up: https://bugs.webkit.org/show_bug.cgi?id=132724
176 if (!frameView.scrollLayerID())
179 // If the root layer does not have a ScrollingStateNode, then we should create one.
180 ensureRootStateNodeForFrameView(frameView);
181 ASSERT(m_scrollingStateTree->rootStateNode());
183 ScrollingCoordinator::frameViewRootLayerDidChange(frameView);
185 ScrollingStateFrameScrollingNode* node = downcast<ScrollingStateFrameScrollingNode>(m_scrollingStateTree->stateNodeForID(frameView.scrollLayerID()));
186 node->setLayer(scrollLayerForFrameView(frameView));
187 node->setScrolledContentsLayer(rootContentLayerForFrameView(frameView));
188 node->setCounterScrollingLayer(counterScrollingLayerForFrameView(frameView));
189 node->setInsetClipLayer(insetClipLayerForFrameView(frameView));
190 node->setContentShadowLayer(contentShadowLayerForFrameView(frameView));
191 node->setHeaderLayer(headerLayerForFrameView(frameView));
192 node->setFooterLayer(footerLayerForFrameView(frameView));
193 node->setScrollBehaviorForFixedElements(frameView.scrollBehaviorForFixedElements());
196 bool AsyncScrollingCoordinator::requestScrollPositionUpdate(FrameView& frameView, const IntPoint& scrollPosition)
198 ASSERT(isMainThread());
201 if (!coordinatesScrollingForFrameView(frameView))
204 bool isProgrammaticScroll = frameView.inProgrammaticScroll();
205 if (isProgrammaticScroll || frameView.frame().document()->inPageCache())
206 updateScrollPositionAfterAsyncScroll(frameView.scrollLayerID(), scrollPosition, isProgrammaticScroll, SetScrollingLayerPosition);
208 // If this frame view's document is being put into the page cache, we don't want to update our
209 // main frame scroll position. Just let the FrameView think that we did.
210 if (frameView.frame().document()->inPageCache())
213 ScrollingStateScrollingNode* stateNode = downcast<ScrollingStateScrollingNode>(m_scrollingStateTree->stateNodeForID(frameView.scrollLayerID()));
217 stateNode->setRequestedScrollPosition(scrollPosition, isProgrammaticScroll);
221 void AsyncScrollingCoordinator::scheduleUpdateScrollPositionAfterAsyncScroll(ScrollingNodeID nodeID, const FloatPoint& scrollPosition, bool programmaticScroll, SetOrSyncScrollingLayerPosition scrollingLayerPositionAction)
223 ScheduledScrollUpdate scrollUpdate(nodeID, scrollPosition, programmaticScroll, scrollingLayerPositionAction);
225 // For programmatic scrolls, requestScrollPositionUpdate() has already called updateScrollPositionAfterAsyncScroll().
226 if (programmaticScroll)
229 if (m_updateNodeScrollPositionTimer.isActive()) {
230 if (m_scheduledScrollUpdate.matchesUpdateType(scrollUpdate)) {
231 m_scheduledScrollUpdate.scrollPosition = scrollPosition;
235 // If the parameters don't match what was previously scheduled, dispatch immediately.
236 m_updateNodeScrollPositionTimer.stop();
237 updateScrollPositionAfterAsyncScroll(m_scheduledScrollUpdate.nodeID, m_scheduledScrollUpdate.scrollPosition, m_scheduledScrollUpdate.isProgrammaticScroll, m_scheduledScrollUpdate.updateLayerPositionAction);
238 updateScrollPositionAfterAsyncScroll(nodeID, scrollPosition, programmaticScroll, scrollingLayerPositionAction);
242 m_scheduledScrollUpdate = scrollUpdate;
243 m_updateNodeScrollPositionTimer.startOneShot(0);
246 void AsyncScrollingCoordinator::updateScrollPositionAfterAsyncScrollTimerFired()
248 updateScrollPositionAfterAsyncScroll(m_scheduledScrollUpdate.nodeID, m_scheduledScrollUpdate.scrollPosition, m_scheduledScrollUpdate.isProgrammaticScroll, m_scheduledScrollUpdate.updateLayerPositionAction);
251 FrameView* AsyncScrollingCoordinator::frameViewForScrollingNode(ScrollingNodeID scrollingNodeID) const
253 if (!m_scrollingStateTree->rootStateNode())
256 if (scrollingNodeID == m_scrollingStateTree->rootStateNode()->scrollingNodeID())
257 return m_page->mainFrame().view();
259 ScrollingStateNode* stateNode = m_scrollingStateTree->stateNodeForID(scrollingNodeID);
263 // Find the enclosing frame scrolling node.
264 ScrollingStateNode* parentNode = stateNode;
265 while (parentNode && parentNode->nodeType() != FrameScrollingNode)
266 parentNode = parentNode->parent();
271 // Walk the frame tree to find the matching FrameView. This is not ideal, but avoids back pointers to FrameViews
272 // from ScrollingTreeStateNodes.
273 for (Frame* frame = &m_page->mainFrame(); frame; frame = frame->tree().traverseNext()) {
274 if (FrameView* view = frame->view()) {
275 if (view->scrollLayerID() == parentNode->scrollingNodeID())
283 void AsyncScrollingCoordinator::updateScrollPositionAfterAsyncScroll(ScrollingNodeID scrollingNodeID, const FloatPoint& scrollPosition, bool programmaticScroll, SetOrSyncScrollingLayerPosition scrollingLayerPositionAction)
285 ASSERT(isMainThread());
290 FrameView* frameViewPtr = frameViewForScrollingNode(scrollingNodeID);
294 FrameView& frameView = *frameViewPtr;
296 if (scrollingNodeID == frameView.scrollLayerID()) {
297 bool oldProgrammaticScroll = frameView.inProgrammaticScroll();
298 frameView.setInProgrammaticScroll(programmaticScroll);
300 frameView.setConstrainsScrollingToContentEdge(false);
301 frameView.notifyScrollPositionChanged(roundedIntPoint(scrollPosition));
302 frameView.setConstrainsScrollingToContentEdge(true);
304 frameView.setInProgrammaticScroll(oldProgrammaticScroll);
306 if (GraphicsLayer* scrollLayer = scrollLayerForFrameView(frameView)) {
307 GraphicsLayer* counterScrollingLayer = counterScrollingLayerForFrameView(frameView);
308 GraphicsLayer* insetClipLayer = insetClipLayerForFrameView(frameView);
309 GraphicsLayer* contentShadowLayer = contentShadowLayerForFrameView(frameView);
310 GraphicsLayer* scrolledContentsLayer = rootContentLayerForFrameView(frameView);
311 GraphicsLayer* headerLayer = headerLayerForFrameView(frameView);
312 GraphicsLayer* footerLayer = footerLayerForFrameView(frameView);
313 LayoutSize scrollOffsetForFixed = frameView.scrollOffsetForFixedPosition();
315 float topContentInset = frameView.topContentInset();
316 FloatPoint positionForInsetClipLayer = FloatPoint(0, FrameView::yPositionForInsetClipLayer(scrollPosition, topContentInset));
317 FloatPoint positionForContentsLayer = FloatPoint(scrolledContentsLayer->position().x(),
318 FrameView::yPositionForRootContentLayer(scrollPosition, topContentInset, frameView.headerHeight()));
319 FloatPoint positionForHeaderLayer = FloatPoint(scrollOffsetForFixed.width(), FrameView::yPositionForHeaderLayer(scrollPosition, topContentInset));
320 FloatPoint positionForFooterLayer = FloatPoint(scrollOffsetForFixed.width(),
321 FrameView::yPositionForFooterLayer(scrollPosition, topContentInset, frameView.totalContentsSize().height(), frameView.footerHeight()));
323 if (programmaticScroll || scrollingLayerPositionAction == SetScrollingLayerPosition) {
324 scrollLayer->setPosition(-frameView.scrollPosition());
325 if (counterScrollingLayer)
326 counterScrollingLayer->setPosition(toLayoutPoint(scrollOffsetForFixed));
328 insetClipLayer->setPosition(positionForInsetClipLayer);
329 if (contentShadowLayer)
330 contentShadowLayer->setPosition(positionForContentsLayer);
331 if (scrolledContentsLayer)
332 scrolledContentsLayer->setPosition(positionForContentsLayer);
334 headerLayer->setPosition(positionForHeaderLayer);
336 footerLayer->setPosition(positionForFooterLayer);
338 scrollLayer->syncPosition(-frameView.scrollPosition());
339 if (counterScrollingLayer)
340 counterScrollingLayer->syncPosition(toLayoutPoint(scrollOffsetForFixed));
342 insetClipLayer->syncPosition(positionForInsetClipLayer);
343 if (contentShadowLayer)
344 contentShadowLayer->syncPosition(positionForContentsLayer);
345 if (scrolledContentsLayer)
346 scrolledContentsLayer->syncPosition(positionForContentsLayer);
348 headerLayer->syncPosition(positionForHeaderLayer);
350 footerLayer->syncPosition(positionForFooterLayer);
352 LayoutRect viewportRect = frameView.viewportConstrainedVisibleContentRect();
353 syncChildPositions(viewportRect);
360 // Overflow-scroll area.
361 if (ScrollableArea* scrollableArea = frameView.scrollableAreaForScrollLayerID(scrollingNodeID)) {
362 scrollableArea->setIsUserScroll(scrollingLayerPositionAction == SyncScrollingLayerPosition);
363 scrollableArea->scrollToOffsetWithoutAnimation(scrollPosition);
364 scrollableArea->setIsUserScroll(false);
365 if (scrollingLayerPositionAction == SetScrollingLayerPosition)
366 m_page->editorClient().overflowScrollPositionChanged();
370 void AsyncScrollingCoordinator::scrollableAreaScrollbarLayerDidChange(ScrollableArea& scrollableArea, ScrollbarOrientation orientation)
372 ASSERT(isMainThread());
375 if (&scrollableArea != static_cast<ScrollableArea*>(m_page->mainFrame().view()))
378 if (orientation == VerticalScrollbar)
379 scrollableArea.verticalScrollbarLayerDidChange();
381 scrollableArea.horizontalScrollbarLayerDidChange();
384 ScrollingNodeID AsyncScrollingCoordinator::attachToStateTree(ScrollingNodeType nodeType, ScrollingNodeID newNodeID, ScrollingNodeID parentID)
386 return m_scrollingStateTree->attachNode(nodeType, newNodeID, parentID);
389 void AsyncScrollingCoordinator::detachFromStateTree(ScrollingNodeID nodeID)
391 m_scrollingStateTree->detachNode(nodeID);
394 void AsyncScrollingCoordinator::clearStateTree()
396 m_scrollingStateTree->clear();
399 void AsyncScrollingCoordinator::syncChildPositions(const LayoutRect& viewportRect)
401 if (!m_scrollingStateTree->rootStateNode())
404 auto children = m_scrollingStateTree->rootStateNode()->children();
408 // FIXME: We'll have to traverse deeper into the tree at some point.
409 for (auto& child : *children)
410 child->syncLayerPositionForViewportRect(viewportRect);
413 void AsyncScrollingCoordinator::ensureRootStateNodeForFrameView(FrameView& frameView)
415 ASSERT(frameView.scrollLayerID());
416 attachToStateTree(FrameScrollingNode, frameView.scrollLayerID(), 0);
419 void AsyncScrollingCoordinator::updateFrameScrollingNode(ScrollingNodeID nodeID, GraphicsLayer* layer, GraphicsLayer* scrolledContentsLayer, GraphicsLayer* counterScrollingLayer, GraphicsLayer* insetClipLayer, const ScrollingGeometry* scrollingGeometry)
421 ScrollingStateFrameScrollingNode* node = downcast<ScrollingStateFrameScrollingNode>(m_scrollingStateTree->stateNodeForID(nodeID));
426 node->setLayer(layer);
427 node->setInsetClipLayer(insetClipLayer);
428 node->setScrolledContentsLayer(scrolledContentsLayer);
429 node->setCounterScrollingLayer(counterScrollingLayer);
431 if (scrollingGeometry) {
432 node->setScrollOrigin(scrollingGeometry->scrollOrigin);
433 node->setScrollPosition(scrollingGeometry->scrollPosition);
434 node->setTotalContentsSize(scrollingGeometry->contentSize);
435 node->setReachableContentsSize(scrollingGeometry->reachableContentSize);
436 node->setScrollableAreaSize(scrollingGeometry->scrollableAreaSize);
440 void AsyncScrollingCoordinator::updateOverflowScrollingNode(ScrollingNodeID nodeID, GraphicsLayer* layer, GraphicsLayer* scrolledContentsLayer, const ScrollingGeometry* scrollingGeometry)
442 ScrollingStateOverflowScrollingNode* node = downcast<ScrollingStateOverflowScrollingNode>(m_scrollingStateTree->stateNodeForID(nodeID));
447 node->setLayer(layer);
448 node->setScrolledContentsLayer(scrolledContentsLayer);
450 if (scrollingGeometry) {
451 node->setScrollOrigin(scrollingGeometry->scrollOrigin);
452 node->setScrollPosition(scrollingGeometry->scrollPosition);
453 node->setTotalContentsSize(scrollingGeometry->contentSize);
454 node->setReachableContentsSize(scrollingGeometry->reachableContentSize);
455 node->setScrollableAreaSize(scrollingGeometry->scrollableAreaSize);
456 #if ENABLE(CSS_SCROLL_SNAP)
457 setStateScrollingNodeSnapOffsetsAsFloat(*node, ScrollEventAxis::Horizontal, scrollingGeometry->horizontalSnapOffsets, m_page->deviceScaleFactor());
458 setStateScrollingNodeSnapOffsetsAsFloat(*node, ScrollEventAxis::Vertical, scrollingGeometry->verticalSnapOffsets, m_page->deviceScaleFactor());
463 void AsyncScrollingCoordinator::updateViewportConstrainedNode(ScrollingNodeID nodeID, const ViewportConstraints& constraints, GraphicsLayer* graphicsLayer)
465 ASSERT(supportsFixedPositionLayers());
467 ScrollingStateNode* node = m_scrollingStateTree->stateNodeForID(nodeID);
471 switch (constraints.constraintType()) {
472 case ViewportConstraints::FixedPositionConstraint: {
473 ScrollingStateFixedNode& fixedNode = downcast<ScrollingStateFixedNode>(*node);
474 fixedNode.setLayer(graphicsLayer);
475 fixedNode.updateConstraints((const FixedPositionViewportConstraints&)constraints);
478 case ViewportConstraints::StickyPositionConstraint: {
479 ScrollingStateStickyNode& stickyNode = downcast<ScrollingStateStickyNode>(*node);
480 stickyNode.setLayer(graphicsLayer);
481 stickyNode.updateConstraints((const StickyPositionViewportConstraints&)constraints);
487 void AsyncScrollingCoordinator::setSynchronousScrollingReasons(SynchronousScrollingReasons reasons)
489 if (!m_scrollingStateTree->rootStateNode())
492 // The FrameView's GraphicsLayer is likely to be out-of-synch with the PlatformLayer
493 // at this point. So we'll update it before we switch back to main thread scrolling
494 // in order to avoid layer positioning bugs.
496 updateMainFrameScrollLayerPosition();
497 m_scrollingStateTree->rootStateNode()->setSynchronousScrollingReasons(reasons);
500 void AsyncScrollingCoordinator::updateMainFrameScrollLayerPosition()
502 ASSERT(isMainThread());
507 FrameView* frameView = m_page->mainFrame().view();
511 if (GraphicsLayer* scrollLayer = scrollLayerForFrameView(*frameView))
512 scrollLayer->setPosition(-frameView->scrollPosition());
515 bool AsyncScrollingCoordinator::isRubberBandInProgress() const
517 return scrollingTree()->isRubberBandInProgress();
520 void AsyncScrollingCoordinator::setScrollPinningBehavior(ScrollPinningBehavior pinning)
522 scrollingTree()->setScrollPinningBehavior(pinning);
525 String AsyncScrollingCoordinator::scrollingStateTreeAsText() const
527 if (m_scrollingStateTree->rootStateNode()) {
528 if (m_nonFastScrollableRegionDirty)
529 m_scrollingStateTree->rootStateNode()->setNonFastScrollableRegion(computeNonFastScrollableRegion(m_page->mainFrame(), IntPoint()));
530 return m_scrollingStateTree->rootStateNode()->scrollingStateTreeAsText();
536 } // namespace WebCore
538 #endif // ENABLE(ASYNC_SCROLLING)