diff options
Diffstat (limited to 'Source/WebCore/page/scrolling')
44 files changed, 5572 insertions, 610 deletions
diff --git a/Source/WebCore/page/scrolling/AsyncScrollingCoordinator.cpp b/Source/WebCore/page/scrolling/AsyncScrollingCoordinator.cpp new file mode 100644 index 000000000..fce95a0a3 --- /dev/null +++ b/Source/WebCore/page/scrolling/AsyncScrollingCoordinator.cpp @@ -0,0 +1,707 @@ +/* + * Copyright (C) 2014-2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if ENABLE(ASYNC_SCROLLING) +#include "AsyncScrollingCoordinator.h" + +#include "Document.h" +#include "EditorClient.h" +#include "Frame.h" +#include "FrameView.h" +#include "GraphicsLayer.h" +#include "Logging.h" +#include "MainFrame.h" +#include "Page.h" +#include "ScrollAnimator.h" +#include "ScrollingConstraints.h" +#include "ScrollingStateFixedNode.h" +#include "ScrollingStateFrameScrollingNode.h" +#include "ScrollingStateOverflowScrollingNode.h" +#include "ScrollingStateStickyNode.h" +#include "ScrollingStateTree.h" +#include "Settings.h" +#include "TextStream.h" +#include "WheelEventTestTrigger.h" + +namespace WebCore { + +AsyncScrollingCoordinator::AsyncScrollingCoordinator(Page* page) + : ScrollingCoordinator(page) + , m_updateNodeScrollPositionTimer(*this, &AsyncScrollingCoordinator::updateScrollPositionAfterAsyncScrollTimerFired) + , m_scrollingStateTree(std::make_unique<ScrollingStateTree>(this)) +{ +} + +AsyncScrollingCoordinator::~AsyncScrollingCoordinator() +{ +} + +void AsyncScrollingCoordinator::scrollingStateTreePropertiesChanged() +{ + scheduleTreeStateCommit(); +} + +static inline void setStateScrollingNodeSnapOffsetsAsFloat(ScrollingStateScrollingNode& node, ScrollEventAxis axis, const Vector<LayoutUnit>* snapOffsets, const Vector<ScrollOffsetRange<LayoutUnit>>* snapOffsetRanges, float deviceScaleFactor) +{ + // 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? + Vector<float> snapOffsetsAsFloat; + if (snapOffsets) { + snapOffsetsAsFloat.reserveInitialCapacity(snapOffsets->size()); + for (auto& offset : *snapOffsets) + snapOffsetsAsFloat.uncheckedAppend(roundToDevicePixel(offset, deviceScaleFactor, false)); + } + + Vector<ScrollOffsetRange<float>> snapOffsetRangesAsFloat; + if (snapOffsetRanges) { + snapOffsetRangesAsFloat.reserveInitialCapacity(snapOffsetRanges->size()); + for (auto& range : *snapOffsetRanges) + snapOffsetRangesAsFloat.uncheckedAppend({ roundToDevicePixel(range.start, deviceScaleFactor, false), roundToDevicePixel(range.end, deviceScaleFactor, false) }); + } + if (axis == ScrollEventAxis::Horizontal) { + node.setHorizontalSnapOffsets(snapOffsetsAsFloat); + node.setHorizontalSnapOffsetRanges(snapOffsetRangesAsFloat); + } else { + node.setVerticalSnapOffsets(snapOffsetsAsFloat); + node.setVerticalSnapOffsetRanges(snapOffsetRangesAsFloat); + } +} + +void AsyncScrollingCoordinator::setEventTrackingRegionsDirty() +{ + m_eventTrackingRegionsDirty = true; + // We have to schedule a commit, but the computed non-fast region may not have actually changed. + scheduleTreeStateCommit(); +} + +void AsyncScrollingCoordinator::willCommitTree() +{ + updateEventTrackingRegions(); +} + +void AsyncScrollingCoordinator::updateEventTrackingRegions() +{ + if (!m_eventTrackingRegionsDirty) + return; + + if (!m_scrollingStateTree->rootStateNode()) + return; + + m_scrollingStateTree->rootStateNode()->setEventTrackingRegions(absoluteEventTrackingRegions()); + m_eventTrackingRegionsDirty = false; +} + +void AsyncScrollingCoordinator::frameViewLayoutUpdated(FrameView& frameView) +{ + ASSERT(isMainThread()); + ASSERT(m_page); + + // If there isn't a root node yet, don't do anything. We'll be called again after creating one. + if (!m_scrollingStateTree->rootStateNode()) + return; + + // Compute the region of the page that we can't do fast scrolling for. This currently includes + // all scrollable areas, such as subframes, overflow divs and list boxes. We need to do this even if the + // frame view whose layout was updated is not the main frame. + // In the future, we may want to have the ability to set non-fast scrolling regions for more than + // just the root node. But right now, this concept only applies to the root. + m_scrollingStateTree->rootStateNode()->setEventTrackingRegions(absoluteEventTrackingRegions()); + m_eventTrackingRegionsDirty = false; + + if (!coordinatesScrollingForFrameView(frameView)) + return; + + ScrollingStateFrameScrollingNode* node = downcast<ScrollingStateFrameScrollingNode>(m_scrollingStateTree->stateNodeForID(frameView.scrollLayerID())); + if (!node) + return; + + Scrollbar* verticalScrollbar = frameView.verticalScrollbar(); + Scrollbar* horizontalScrollbar = frameView.horizontalScrollbar(); + node->setScrollerImpsFromScrollbars(verticalScrollbar, horizontalScrollbar); + + node->setFrameScaleFactor(frameView.frame().frameScaleFactor()); + node->setHeaderHeight(frameView.headerHeight()); + node->setFooterHeight(frameView.footerHeight()); + node->setTopContentInset(frameView.topContentInset()); + + node->setVisualViewportEnabled(visualViewportEnabled()); + node->setLayoutViewport(frameView.layoutViewportRect()); + node->setMinLayoutViewportOrigin(frameView.minStableLayoutViewportOrigin()); + node->setMaxLayoutViewportOrigin(frameView.maxStableLayoutViewportOrigin()); + + node->setScrollOrigin(frameView.scrollOrigin()); + node->setScrollableAreaSize(frameView.visibleContentRect().size()); + node->setTotalContentsSize(frameView.totalContentsSize()); + node->setReachableContentsSize(frameView.totalContentsSize()); + node->setFixedElementsLayoutRelativeToFrame(frameView.fixedElementsLayoutRelativeToFrame()); + node->setScrollBehaviorForFixedElements(frameView.scrollBehaviorForFixedElements()); + +#if ENABLE(CSS_SCROLL_SNAP) + frameView.updateSnapOffsets(); + updateScrollSnapPropertiesWithFrameView(frameView); +#endif + +#if PLATFORM(COCOA) + Page* page = frameView.frame().page(); + if (page && page->expectsWheelEventTriggers()) { + LOG(WheelEventTestTriggers, " AsyncScrollingCoordinator::frameViewLayoutUpdated: Expects wheel event test trigger=%d", page->expectsWheelEventTriggers()); + node->setExpectsWheelEventTestTrigger(page->expectsWheelEventTriggers()); + } +#endif + + ScrollableAreaParameters scrollParameters; + scrollParameters.horizontalScrollElasticity = frameView.horizontalScrollElasticity(); + scrollParameters.verticalScrollElasticity = frameView.verticalScrollElasticity(); + scrollParameters.hasEnabledHorizontalScrollbar = horizontalScrollbar && horizontalScrollbar->enabled(); + scrollParameters.hasEnabledVerticalScrollbar = verticalScrollbar && verticalScrollbar->enabled(); + scrollParameters.horizontalScrollbarMode = frameView.horizontalScrollbarMode(); + scrollParameters.verticalScrollbarMode = frameView.verticalScrollbarMode(); + + node->setScrollableAreaParameters(scrollParameters); +} + +void AsyncScrollingCoordinator::updateExpectsWheelEventTestTriggerWithFrameView(const FrameView& frameView) +{ + auto* page = frameView.frame().page(); + if (!page) + return; + + auto* node = downcast<ScrollingStateFrameScrollingNode>(m_scrollingStateTree->stateNodeForID(frameView.scrollLayerID())); + if (!node) + return; + + node->setExpectsWheelEventTestTrigger(page->expectsWheelEventTriggers()); +} + +void AsyncScrollingCoordinator::frameViewEventTrackingRegionsChanged(FrameView&) +{ + if (!m_scrollingStateTree->rootStateNode()) + return; + + setEventTrackingRegionsDirty(); +} + +void AsyncScrollingCoordinator::frameViewRootLayerDidChange(FrameView& frameView) +{ + ASSERT(isMainThread()); + ASSERT(m_page); + + if (!coordinatesScrollingForFrameView(frameView)) + return; + + // FIXME: In some navigation scenarios, the FrameView has no RenderView or that RenderView has not been composited. + // This needs cleaning up: https://bugs.webkit.org/show_bug.cgi?id=132724 + if (!frameView.scrollLayerID()) + return; + + // If the root layer does not have a ScrollingStateNode, then we should create one. + ensureRootStateNodeForFrameView(frameView); + ASSERT(m_scrollingStateTree->rootStateNode()); + + ScrollingCoordinator::frameViewRootLayerDidChange(frameView); + + ScrollingStateFrameScrollingNode* node = downcast<ScrollingStateFrameScrollingNode>(m_scrollingStateTree->stateNodeForID(frameView.scrollLayerID())); + node->setLayer(scrollLayerForFrameView(frameView)); + node->setScrolledContentsLayer(rootContentLayerForFrameView(frameView)); + node->setCounterScrollingLayer(counterScrollingLayerForFrameView(frameView)); + node->setInsetClipLayer(insetClipLayerForFrameView(frameView)); + node->setContentShadowLayer(contentShadowLayerForFrameView(frameView)); + node->setHeaderLayer(headerLayerForFrameView(frameView)); + node->setFooterLayer(footerLayerForFrameView(frameView)); + node->setScrollBehaviorForFixedElements(frameView.scrollBehaviorForFixedElements()); +} + +bool AsyncScrollingCoordinator::requestScrollPositionUpdate(FrameView& frameView, const IntPoint& scrollPosition) +{ + ASSERT(isMainThread()); + ASSERT(m_page); + + if (!coordinatesScrollingForFrameView(frameView)) + return false; + + bool isProgrammaticScroll = frameView.inProgrammaticScroll(); + if (isProgrammaticScroll || frameView.frame().document()->pageCacheState() != Document::NotInPageCache) + updateScrollPositionAfterAsyncScroll(frameView.scrollLayerID(), scrollPosition, std::nullopt, isProgrammaticScroll, ScrollingLayerPositionAction::Set); + + // If this frame view's document is being put into the page cache, we don't want to update our + // main frame scroll position. Just let the FrameView think that we did. + if (frameView.frame().document()->pageCacheState() != Document::NotInPageCache) + return true; + + ScrollingStateScrollingNode* stateNode = downcast<ScrollingStateScrollingNode>(m_scrollingStateTree->stateNodeForID(frameView.scrollLayerID())); + if (!stateNode) + return false; + + stateNode->setRequestedScrollPosition(scrollPosition, isProgrammaticScroll); + return true; +} + +void AsyncScrollingCoordinator::scheduleUpdateScrollPositionAfterAsyncScroll(ScrollingNodeID nodeID, const FloatPoint& scrollPosition, const std::optional<FloatPoint>& layoutViewportOrigin, bool programmaticScroll, ScrollingLayerPositionAction scrollingLayerPositionAction) +{ + ScheduledScrollUpdate scrollUpdate(nodeID, scrollPosition, layoutViewportOrigin, programmaticScroll, scrollingLayerPositionAction); + + // For programmatic scrolls, requestScrollPositionUpdate() has already called updateScrollPositionAfterAsyncScroll(). + if (programmaticScroll) + return; + + if (m_updateNodeScrollPositionTimer.isActive()) { + if (m_scheduledScrollUpdate.matchesUpdateType(scrollUpdate)) { + m_scheduledScrollUpdate.scrollPosition = scrollPosition; + m_scheduledScrollUpdate.layoutViewportOrigin = layoutViewportOrigin; + return; + } + + // If the parameters don't match what was previously scheduled, dispatch immediately. + m_updateNodeScrollPositionTimer.stop(); + updateScrollPositionAfterAsyncScroll(m_scheduledScrollUpdate.nodeID, m_scheduledScrollUpdate.scrollPosition, m_scheduledScrollUpdate.layoutViewportOrigin, m_scheduledScrollUpdate.isProgrammaticScroll, m_scheduledScrollUpdate.updateLayerPositionAction); + updateScrollPositionAfterAsyncScroll(nodeID, scrollPosition, layoutViewportOrigin, programmaticScroll, scrollingLayerPositionAction); + return; + } + + m_scheduledScrollUpdate = scrollUpdate; + m_updateNodeScrollPositionTimer.startOneShot(0); +} + +void AsyncScrollingCoordinator::updateScrollPositionAfterAsyncScrollTimerFired() +{ + updateScrollPositionAfterAsyncScroll(m_scheduledScrollUpdate.nodeID, m_scheduledScrollUpdate.scrollPosition, m_scheduledScrollUpdate.layoutViewportOrigin, m_scheduledScrollUpdate.isProgrammaticScroll, m_scheduledScrollUpdate.updateLayerPositionAction); +} + +FrameView* AsyncScrollingCoordinator::frameViewForScrollingNode(ScrollingNodeID scrollingNodeID) const +{ + if (!m_scrollingStateTree->rootStateNode()) + return nullptr; + + if (scrollingNodeID == m_scrollingStateTree->rootStateNode()->scrollingNodeID()) + return m_page->mainFrame().view(); + + ScrollingStateNode* stateNode = m_scrollingStateTree->stateNodeForID(scrollingNodeID); + if (!stateNode) + return nullptr; + + // Find the enclosing frame scrolling node. + ScrollingStateNode* parentNode = stateNode; + while (parentNode && parentNode->nodeType() != FrameScrollingNode) + parentNode = parentNode->parent(); + + if (!parentNode) + return nullptr; + + // Walk the frame tree to find the matching FrameView. This is not ideal, but avoids back pointers to FrameViews + // from ScrollingTreeStateNodes. + for (Frame* frame = &m_page->mainFrame(); frame; frame = frame->tree().traverseNext()) { + if (FrameView* view = frame->view()) { + if (view->scrollLayerID() == parentNode->scrollingNodeID()) + return view; + } + } + + return nullptr; +} + +void AsyncScrollingCoordinator::updateScrollPositionAfterAsyncScroll(ScrollingNodeID scrollingNodeID, const FloatPoint& scrollPosition, std::optional<FloatPoint> layoutViewportOrigin, bool programmaticScroll, ScrollingLayerPositionAction scrollingLayerPositionAction) +{ + ASSERT(isMainThread()); + + if (!m_page) + return; + + FrameView* frameViewPtr = frameViewForScrollingNode(scrollingNodeID); + if (!frameViewPtr) + return; + + LOG_WITH_STREAM(Scrolling, stream << "AsyncScrollingCoordinator::updateScrollPositionAfterAsyncScroll node " << scrollingNodeID << " scrollPosition " << scrollPosition << " action " << scrollingLayerPositionAction); + + FrameView& frameView = *frameViewPtr; + + if (scrollingNodeID == frameView.scrollLayerID()) { + reconcileScrollingState(frameView, scrollPosition, layoutViewportOrigin, programmaticScroll, true, scrollingLayerPositionAction); + +#if PLATFORM(COCOA) + if (m_page->expectsWheelEventTriggers()) { + frameView.scrollAnimator().setWheelEventTestTrigger(m_page->testTrigger()); + if (const auto& trigger = m_page->testTrigger()) + trigger->removeTestDeferralForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(scrollingNodeID), WheelEventTestTrigger::ScrollingThreadSyncNeeded); + } +#endif + + return; + } + + // Overflow-scroll area. + if (ScrollableArea* scrollableArea = frameView.scrollableAreaForScrollLayerID(scrollingNodeID)) { + scrollableArea->setIsUserScroll(scrollingLayerPositionAction == ScrollingLayerPositionAction::Sync); + scrollableArea->scrollToOffsetWithoutAnimation(scrollPosition); + scrollableArea->setIsUserScroll(false); + if (scrollingLayerPositionAction == ScrollingLayerPositionAction::Set) + m_page->editorClient().overflowScrollPositionChanged(); + +#if PLATFORM(COCOA) + if (m_page->expectsWheelEventTriggers()) { + frameView.scrollAnimator().setWheelEventTestTrigger(m_page->testTrigger()); + if (const auto& trigger = m_page->testTrigger()) + trigger->removeTestDeferralForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(scrollingNodeID), WheelEventTestTrigger::ScrollingThreadSyncNeeded); + } +#endif + } +} + +void AsyncScrollingCoordinator::reconcileScrollingState(FrameView& frameView, const FloatPoint& scrollPosition, const LayoutViewportOriginOrOverrideRect& layoutViewportOriginOrOverrideRect, bool programmaticScroll, bool inStableState, ScrollingLayerPositionAction scrollingLayerPositionAction) +{ + bool oldProgrammaticScroll = frameView.inProgrammaticScroll(); + frameView.setInProgrammaticScroll(programmaticScroll); + + std::optional<FloatRect> layoutViewportRect; + + WTF::switchOn(layoutViewportOriginOrOverrideRect, + [&frameView](std::optional<FloatPoint> origin) { + if (origin) + frameView.setBaseLayoutViewportOrigin(LayoutPoint(origin.value()), FrameView::TriggerLayoutOrNot::No); + }, [&frameView, &layoutViewportRect, inStableState, visualViewportEnabled = visualViewportEnabled()](std::optional<FloatRect> overrideRect) { + layoutViewportRect = overrideRect; + if (overrideRect && inStableState) { + if (visualViewportEnabled) + frameView.setLayoutViewportOverrideRect(LayoutRect(overrideRect.value())); +#if PLATFORM(IOS) + else + frameView.setCustomFixedPositionLayoutRect(enclosingIntRect(overrideRect.value())); +#endif + } + } + ); + + frameView.setConstrainsScrollingToContentEdge(false); + frameView.notifyScrollPositionChanged(roundedIntPoint(scrollPosition)); + frameView.setConstrainsScrollingToContentEdge(true); + frameView.setInProgrammaticScroll(oldProgrammaticScroll); + + if (!programmaticScroll && scrollingLayerPositionAction != ScrollingLayerPositionAction::Set) { + if (inStableState) + reconcileViewportConstrainedLayerPositions(frameView.rectForFixedPositionLayout(), scrollingLayerPositionAction); + else if (layoutViewportRect) + reconcileViewportConstrainedLayerPositions(LayoutRect(layoutViewportRect.value()), scrollingLayerPositionAction); + } + + GraphicsLayer* scrollLayer = scrollLayerForFrameView(frameView); + if (!scrollLayer) + return; + + GraphicsLayer* counterScrollingLayer = counterScrollingLayerForFrameView(frameView); + GraphicsLayer* insetClipLayer = insetClipLayerForFrameView(frameView); + GraphicsLayer* contentShadowLayer = contentShadowLayerForFrameView(frameView); + GraphicsLayer* scrolledContentsLayer = rootContentLayerForFrameView(frameView); + GraphicsLayer* headerLayer = headerLayerForFrameView(frameView); + GraphicsLayer* footerLayer = footerLayerForFrameView(frameView); + + ASSERT(frameView.scrollPosition() == roundedIntPoint(scrollPosition)); + LayoutPoint scrollPositionForFixed = frameView.scrollPositionForFixedPosition(); + float topContentInset = frameView.topContentInset(); + + FloatPoint positionForInsetClipLayer; + if (insetClipLayer) + positionForInsetClipLayer = FloatPoint(insetClipLayer->position().x(), FrameView::yPositionForInsetClipLayer(scrollPosition, topContentInset)); + FloatPoint positionForContentsLayer = frameView.positionForRootContentLayer(); + + FloatPoint positionForHeaderLayer = FloatPoint(scrollPositionForFixed.x(), FrameView::yPositionForHeaderLayer(scrollPosition, topContentInset)); + FloatPoint positionForFooterLayer = FloatPoint(scrollPositionForFixed.x(), + FrameView::yPositionForFooterLayer(scrollPosition, topContentInset, frameView.totalContentsSize().height(), frameView.footerHeight())); + + if (programmaticScroll || scrollingLayerPositionAction == ScrollingLayerPositionAction::Set) { + scrollLayer->setPosition(-frameView.scrollPosition()); + if (counterScrollingLayer) + counterScrollingLayer->setPosition(scrollPositionForFixed); + if (insetClipLayer) + insetClipLayer->setPosition(positionForInsetClipLayer); + if (contentShadowLayer) + contentShadowLayer->setPosition(positionForContentsLayer); + if (scrolledContentsLayer) + scrolledContentsLayer->setPosition(positionForContentsLayer); + if (headerLayer) + headerLayer->setPosition(positionForHeaderLayer); + if (footerLayer) + footerLayer->setPosition(positionForFooterLayer); + } else { + scrollLayer->syncPosition(-frameView.scrollPosition()); + if (counterScrollingLayer) + counterScrollingLayer->syncPosition(scrollPositionForFixed); + if (insetClipLayer) + insetClipLayer->syncPosition(positionForInsetClipLayer); + if (contentShadowLayer) + contentShadowLayer->syncPosition(positionForContentsLayer); + if (scrolledContentsLayer) + scrolledContentsLayer->syncPosition(positionForContentsLayer); + if (headerLayer) + headerLayer->syncPosition(positionForHeaderLayer); + if (footerLayer) + footerLayer->syncPosition(positionForFooterLayer); + } +} + +void AsyncScrollingCoordinator::scrollableAreaScrollbarLayerDidChange(ScrollableArea& scrollableArea, ScrollbarOrientation orientation) +{ + ASSERT(isMainThread()); + ASSERT(m_page); + + if (&scrollableArea != static_cast<ScrollableArea*>(m_page->mainFrame().view())) + return; + + if (orientation == VerticalScrollbar) + scrollableArea.verticalScrollbarLayerDidChange(); + else + scrollableArea.horizontalScrollbarLayerDidChange(); +} + +ScrollingNodeID AsyncScrollingCoordinator::attachToStateTree(ScrollingNodeType nodeType, ScrollingNodeID newNodeID, ScrollingNodeID parentID) +{ + return m_scrollingStateTree->attachNode(nodeType, newNodeID, parentID); +} + +void AsyncScrollingCoordinator::detachFromStateTree(ScrollingNodeID nodeID) +{ + m_scrollingStateTree->detachNode(nodeID); +} + +void AsyncScrollingCoordinator::clearStateTree() +{ + m_scrollingStateTree->clear(); +} + +void AsyncScrollingCoordinator::reconcileViewportConstrainedLayerPositions(const LayoutRect& viewportRect, ScrollingLayerPositionAction action) +{ + if (!m_scrollingStateTree->rootStateNode()) + return; + + auto children = m_scrollingStateTree->rootStateNode()->children(); + if (!children) + return; + + LOG_WITH_STREAM(Scrolling, stream << "AsyncScrollingCoordinator::reconcileViewportConstrainedLayerPositions for viewport rect " << viewportRect); + + // FIXME: We'll have to traverse deeper into the tree at some point. + for (auto& child : *children) + child->reconcileLayerPositionForViewportRect(viewportRect, action); +} + +void AsyncScrollingCoordinator::ensureRootStateNodeForFrameView(FrameView& frameView) +{ + ASSERT(frameView.scrollLayerID()); + attachToStateTree(FrameScrollingNode, frameView.scrollLayerID(), 0); +} + +void AsyncScrollingCoordinator::updateFrameScrollingNode(ScrollingNodeID nodeID, GraphicsLayer* layer, GraphicsLayer* scrolledContentsLayer, GraphicsLayer* counterScrollingLayer, GraphicsLayer* insetClipLayer, const ScrollingGeometry* scrollingGeometry) +{ + ScrollingStateFrameScrollingNode* node = downcast<ScrollingStateFrameScrollingNode>(m_scrollingStateTree->stateNodeForID(nodeID)); + ASSERT(node); + if (!node) + return; + + node->setLayer(layer); + node->setInsetClipLayer(insetClipLayer); + node->setScrolledContentsLayer(scrolledContentsLayer); + node->setCounterScrollingLayer(counterScrollingLayer); + + if (scrollingGeometry) { + node->setScrollOrigin(scrollingGeometry->scrollOrigin); + node->setScrollPosition(scrollingGeometry->scrollPosition); + node->setTotalContentsSize(scrollingGeometry->contentSize); + node->setReachableContentsSize(scrollingGeometry->reachableContentSize); + node->setScrollableAreaSize(scrollingGeometry->scrollableAreaSize); + } +} + +void AsyncScrollingCoordinator::updateOverflowScrollingNode(ScrollingNodeID nodeID, GraphicsLayer* layer, GraphicsLayer* scrolledContentsLayer, const ScrollingGeometry* scrollingGeometry) +{ + ScrollingStateOverflowScrollingNode* node = downcast<ScrollingStateOverflowScrollingNode>(m_scrollingStateTree->stateNodeForID(nodeID)); + ASSERT(node); + if (!node) + return; + + node->setLayer(layer); + node->setScrolledContentsLayer(scrolledContentsLayer); + + if (scrollingGeometry) { + node->setScrollOrigin(scrollingGeometry->scrollOrigin); + node->setScrollPosition(scrollingGeometry->scrollPosition); + node->setTotalContentsSize(scrollingGeometry->contentSize); + node->setReachableContentsSize(scrollingGeometry->reachableContentSize); + node->setScrollableAreaSize(scrollingGeometry->scrollableAreaSize); +#if ENABLE(CSS_SCROLL_SNAP) + setStateScrollingNodeSnapOffsetsAsFloat(*node, ScrollEventAxis::Horizontal, &scrollingGeometry->horizontalSnapOffsets, &scrollingGeometry->horizontalSnapOffsetRanges, m_page->deviceScaleFactor()); + setStateScrollingNodeSnapOffsetsAsFloat(*node, ScrollEventAxis::Vertical, &scrollingGeometry->verticalSnapOffsets, &scrollingGeometry->verticalSnapOffsetRanges, m_page->deviceScaleFactor()); + node->setCurrentHorizontalSnapPointIndex(scrollingGeometry->currentHorizontalSnapPointIndex); + node->setCurrentVerticalSnapPointIndex(scrollingGeometry->currentVerticalSnapPointIndex); +#endif + } +} + +void AsyncScrollingCoordinator::updateViewportConstrainedNode(ScrollingNodeID nodeID, const ViewportConstraints& constraints, GraphicsLayer* graphicsLayer) +{ + ASSERT(supportsFixedPositionLayers()); + + ScrollingStateNode* node = m_scrollingStateTree->stateNodeForID(nodeID); + if (!node) + return; + + switch (constraints.constraintType()) { + case ViewportConstraints::FixedPositionConstraint: { + ScrollingStateFixedNode& fixedNode = downcast<ScrollingStateFixedNode>(*node); + fixedNode.setLayer(graphicsLayer); + fixedNode.updateConstraints((const FixedPositionViewportConstraints&)constraints); + break; + } + case ViewportConstraints::StickyPositionConstraint: { + ScrollingStateStickyNode& stickyNode = downcast<ScrollingStateStickyNode>(*node); + stickyNode.setLayer(graphicsLayer); + stickyNode.updateConstraints((const StickyPositionViewportConstraints&)constraints); + break; + } + } +} + +void AsyncScrollingCoordinator::setSynchronousScrollingReasons(SynchronousScrollingReasons reasons) +{ + if (!m_scrollingStateTree->rootStateNode()) + return; + + // The FrameView's GraphicsLayer is likely to be out-of-synch with the PlatformLayer + // at this point. So we'll update it before we switch back to main thread scrolling + // in order to avoid layer positioning bugs. + if (reasons) + updateMainFrameScrollLayerPosition(); + m_scrollingStateTree->rootStateNode()->setSynchronousScrollingReasons(reasons); +} + +void AsyncScrollingCoordinator::updateMainFrameScrollLayerPosition() +{ + ASSERT(isMainThread()); + + if (!m_page) + return; + + FrameView* frameView = m_page->mainFrame().view(); + if (!frameView) + return; + + if (GraphicsLayer* scrollLayer = scrollLayerForFrameView(*frameView)) + scrollLayer->setPosition(-frameView->scrollPosition()); +} + +bool AsyncScrollingCoordinator::isRubberBandInProgress() const +{ + return scrollingTree()->isRubberBandInProgress(); +} + +void AsyncScrollingCoordinator::setScrollPinningBehavior(ScrollPinningBehavior pinning) +{ + scrollingTree()->setScrollPinningBehavior(pinning); +} + +bool AsyncScrollingCoordinator::visualViewportEnabled() const +{ + return m_page->mainFrame().settings().visualViewportEnabled(); +} + +String AsyncScrollingCoordinator::scrollingStateTreeAsText() const +{ + if (m_scrollingStateTree->rootStateNode()) { + if (m_eventTrackingRegionsDirty) + m_scrollingStateTree->rootStateNode()->setEventTrackingRegions(absoluteEventTrackingRegions()); + return m_scrollingStateTree->rootStateNode()->scrollingStateTreeAsText(); + } + + return String(); +} + +#if PLATFORM(COCOA) +void AsyncScrollingCoordinator::setActiveScrollSnapIndices(ScrollingNodeID scrollingNodeID, unsigned horizontalIndex, unsigned verticalIndex) +{ + ASSERT(isMainThread()); + + if (!m_page) + return; + + FrameView* frameView = frameViewForScrollingNode(scrollingNodeID); + if (!frameView) + return; + + if (scrollingNodeID == frameView->scrollLayerID()) { + frameView->setCurrentHorizontalSnapPointIndex(horizontalIndex); + frameView->setCurrentVerticalSnapPointIndex(verticalIndex); + return; + } + + // Overflow-scroll area. + if (ScrollableArea* scrollableArea = frameView->scrollableAreaForScrollLayerID(scrollingNodeID)) { + scrollableArea->setCurrentHorizontalSnapPointIndex(horizontalIndex); + scrollableArea->setCurrentVerticalSnapPointIndex(verticalIndex); + } +} + +void AsyncScrollingCoordinator::deferTestsForReason(WheelEventTestTrigger::ScrollableAreaIdentifier identifier, WheelEventTestTrigger::DeferTestTriggerReason reason) const +{ + ASSERT(isMainThread()); + if (!m_page || !m_page->expectsWheelEventTriggers()) + return; + + if (const auto& trigger = m_page->testTrigger()) { + LOG(WheelEventTestTriggers, " (!) AsyncScrollingCoordinator::deferTestsForReason: Deferring %p for reason %d.", identifier, reason); + trigger->deferTestsForReason(identifier, reason); + } +} + +void AsyncScrollingCoordinator::removeTestDeferralForReason(WheelEventTestTrigger::ScrollableAreaIdentifier identifier, WheelEventTestTrigger::DeferTestTriggerReason reason) const +{ + ASSERT(isMainThread()); + if (!m_page || !m_page->expectsWheelEventTriggers()) + return; + + if (const auto& trigger = m_page->testTrigger()) { + LOG(WheelEventTestTriggers, " (!) AsyncScrollingCoordinator::removeTestDeferralForReason: Deferring %p for reason %d.", identifier, reason); + trigger->removeTestDeferralForReason(identifier, reason); + } +} +#endif + +#if ENABLE(CSS_SCROLL_SNAP) +bool AsyncScrollingCoordinator::isScrollSnapInProgress() const +{ + return scrollingTree()->isScrollSnapInProgress(); +} + +void AsyncScrollingCoordinator::updateScrollSnapPropertiesWithFrameView(const FrameView& frameView) +{ + if (auto node = downcast<ScrollingStateFrameScrollingNode>(m_scrollingStateTree->stateNodeForID(frameView.scrollLayerID()))) { + setStateScrollingNodeSnapOffsetsAsFloat(*node, ScrollEventAxis::Horizontal, frameView.horizontalSnapOffsets(), frameView.horizontalSnapOffsetRanges(), m_page->deviceScaleFactor()); + setStateScrollingNodeSnapOffsetsAsFloat(*node, ScrollEventAxis::Vertical, frameView.verticalSnapOffsets(), frameView.verticalSnapOffsetRanges(), m_page->deviceScaleFactor()); + node->setCurrentHorizontalSnapPointIndex(frameView.currentHorizontalSnapPointIndex()); + node->setCurrentVerticalSnapPointIndex(frameView.currentVerticalSnapPointIndex()); + } +} +#endif + +} // namespace WebCore + +#endif // ENABLE(ASYNC_SCROLLING) diff --git a/Source/WebCore/page/scrolling/AsyncScrollingCoordinator.h b/Source/WebCore/page/scrolling/AsyncScrollingCoordinator.h new file mode 100644 index 000000000..011ae0f23 --- /dev/null +++ b/Source/WebCore/page/scrolling/AsyncScrollingCoordinator.h @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2014-2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(ASYNC_SCROLLING) + +#include "ScrollingCoordinator.h" +#include "ScrollingTree.h" +#include "Timer.h" +#include <wtf/RefPtr.h> + +namespace WebCore { + +class Page; +class Scrollbar; +class ScrollingStateNode; +class ScrollingStateScrollingNode; +class ScrollingStateTree; + +// ScrollingCoordinator subclass that maintains a ScrollingStateTree and a ScrollingTree, +// allowing asynchronous scrolling (in another thread or process). +class AsyncScrollingCoordinator : public ScrollingCoordinator { +public: + static Ref<AsyncScrollingCoordinator> create(Page*); + WEBCORE_EXPORT virtual ~AsyncScrollingCoordinator(); + + ScrollingTree* scrollingTree() const { return m_scrollingTree.get(); } + + void scrollingStateTreePropertiesChanged(); + + WEBCORE_EXPORT void scheduleUpdateScrollPositionAfterAsyncScroll(ScrollingNodeID, const FloatPoint&, const std::optional<FloatPoint>& layoutViewportOrigin, bool programmaticScroll, ScrollingLayerPositionAction); + +#if PLATFORM(COCOA) + WEBCORE_EXPORT void setActiveScrollSnapIndices(ScrollingNodeID, unsigned horizontalIndex, unsigned verticalIndex); + void deferTestsForReason(WheelEventTestTrigger::ScrollableAreaIdentifier, WheelEventTestTrigger::DeferTestTriggerReason) const; + void removeTestDeferralForReason(WheelEventTestTrigger::ScrollableAreaIdentifier, WheelEventTestTrigger::DeferTestTriggerReason) const; +#endif + +#if ENABLE(CSS_SCROLL_SNAP) + WEBCORE_EXPORT void updateScrollSnapPropertiesWithFrameView(const FrameView&) override; +#endif + + WEBCORE_EXPORT void updateExpectsWheelEventTestTriggerWithFrameView(const FrameView&) override; + +protected: + WEBCORE_EXPORT AsyncScrollingCoordinator(Page*); + + void setScrollingTree(Ref<ScrollingTree>&& scrollingTree) { m_scrollingTree = WTFMove(scrollingTree); } + + ScrollingStateTree* scrollingStateTree() { return m_scrollingStateTree.get(); } + + RefPtr<ScrollingTree> releaseScrollingTree() { return WTFMove(m_scrollingTree); } + + void updateScrollPositionAfterAsyncScroll(ScrollingNodeID, const FloatPoint&, std::optional<FloatPoint> layoutViewportOrigin, bool programmaticScroll, ScrollingLayerPositionAction); + + WEBCORE_EXPORT String scrollingStateTreeAsText() const override; + WEBCORE_EXPORT void willCommitTree() override; + + bool eventTrackingRegionsDirty() const { return m_eventTrackingRegionsDirty; } + +private: + bool isAsyncScrollingCoordinator() const override { return true; } + + bool supportsFixedPositionLayers() const override { return true; } + bool hasVisibleSlowRepaintViewportConstrainedObjects(const FrameView&) const override { return false; } + + bool visualViewportEnabled() const; + + WEBCORE_EXPORT void frameViewLayoutUpdated(FrameView&) override; + WEBCORE_EXPORT void frameViewRootLayerDidChange(FrameView&) override; + WEBCORE_EXPORT void frameViewEventTrackingRegionsChanged(FrameView&) override; + + WEBCORE_EXPORT bool requestScrollPositionUpdate(FrameView&, const IntPoint&) override; + + WEBCORE_EXPORT ScrollingNodeID attachToStateTree(ScrollingNodeType, ScrollingNodeID newNodeID, ScrollingNodeID parentID) override; + WEBCORE_EXPORT void detachFromStateTree(ScrollingNodeID) override; + WEBCORE_EXPORT void clearStateTree() override; + + WEBCORE_EXPORT void updateViewportConstrainedNode(ScrollingNodeID, const ViewportConstraints&, GraphicsLayer*) override; + + WEBCORE_EXPORT void updateFrameScrollingNode(ScrollingNodeID, GraphicsLayer* scrollLayer, GraphicsLayer* scrolledContentsLayer, GraphicsLayer* counterScrollingLayer, GraphicsLayer* insetClipLayer, const ScrollingGeometry* = nullptr) override; + WEBCORE_EXPORT void updateOverflowScrollingNode(ScrollingNodeID, GraphicsLayer* scrollLayer, GraphicsLayer* scrolledContentsLayer, const ScrollingGeometry* = nullptr) override; + + WEBCORE_EXPORT void reconcileScrollingState(FrameView&, const FloatPoint&, const LayoutViewportOriginOrOverrideRect&, bool programmaticScroll, bool inStableState, ScrollingLayerPositionAction) override; + + bool isRubberBandInProgress() const override; + void setScrollPinningBehavior(ScrollPinningBehavior) override; + +#if ENABLE(CSS_SCROLL_SNAP) + bool isScrollSnapInProgress() const override; +#endif + + WEBCORE_EXPORT void reconcileViewportConstrainedLayerPositions(const LayoutRect& viewportRect, ScrollingLayerPositionAction) override; + WEBCORE_EXPORT void scrollableAreaScrollbarLayerDidChange(ScrollableArea&, ScrollbarOrientation) override; + + WEBCORE_EXPORT void setSynchronousScrollingReasons(SynchronousScrollingReasons) override; + + virtual void scheduleTreeStateCommit() = 0; + + void ensureRootStateNodeForFrameView(FrameView&); + void updateMainFrameScrollLayerPosition(); + + void updateScrollPositionAfterAsyncScrollTimerFired(); + void setEventTrackingRegionsDirty(); + void updateEventTrackingRegions(); + + FrameView* frameViewForScrollingNode(ScrollingNodeID) const; + + Timer m_updateNodeScrollPositionTimer; + + struct ScheduledScrollUpdate { + ScheduledScrollUpdate() = default; + ScheduledScrollUpdate(ScrollingNodeID scrollingNodeID, FloatPoint point, std::optional<FloatPoint> viewportOrigin, bool isProgrammatic, ScrollingLayerPositionAction udpateAction) + : nodeID(scrollingNodeID) + , scrollPosition(point) + , layoutViewportOrigin(viewportOrigin) + , isProgrammaticScroll(isProgrammatic) + , updateLayerPositionAction(udpateAction) + { } + + ScrollingNodeID nodeID { 0 }; + FloatPoint scrollPosition; + std::optional<FloatPoint> layoutViewportOrigin; + bool isProgrammaticScroll { false }; + ScrollingLayerPositionAction updateLayerPositionAction { ScrollingLayerPositionAction::Sync }; + + bool matchesUpdateType(const ScheduledScrollUpdate& other) const + { + return nodeID == other.nodeID + && isProgrammaticScroll == other.isProgrammaticScroll + && updateLayerPositionAction == other.updateLayerPositionAction; + } + }; + + ScheduledScrollUpdate m_scheduledScrollUpdate; + + std::unique_ptr<ScrollingStateTree> m_scrollingStateTree; + RefPtr<ScrollingTree> m_scrollingTree; + + bool m_eventTrackingRegionsDirty { false }; +}; + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_SCROLLING_COORDINATOR(WebCore::AsyncScrollingCoordinator, isAsyncScrollingCoordinator()); + +#endif // ENABLE(ASYNC_SCROLLING) diff --git a/Source/WebCore/page/scrolling/AxisScrollSnapOffsets.cpp b/Source/WebCore/page/scrolling/AxisScrollSnapOffsets.cpp new file mode 100644 index 000000000..020753847 --- /dev/null +++ b/Source/WebCore/page/scrolling/AxisScrollSnapOffsets.cpp @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2014-2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AxisScrollSnapOffsets.h" + +#include "ElementChildIterator.h" +#include "HTMLCollection.h" +#include "HTMLElement.h" +#include "Length.h" +#include "Logging.h" +#include "RenderBox.h" +#include "RenderView.h" +#include "ScrollableArea.h" +#include "StyleScrollSnapPoints.h" + +#if ENABLE(CSS_SCROLL_SNAP) + +namespace WebCore { + +enum class InsetOrOutset { + Inset, + Outset +}; + +static LayoutRect computeScrollSnapPortOrAreaRect(const LayoutRect& rect, const LengthBox& insetOrOutsetBox, InsetOrOutset insetOrOutset) +{ + LayoutBoxExtent extents(valueForLength(insetOrOutsetBox.top(), rect.height()), valueForLength(insetOrOutsetBox.right(), rect.width()), valueForLength(insetOrOutsetBox.bottom(), rect.height()), valueForLength(insetOrOutsetBox.left(), rect.width())); + auto snapPortOrArea(rect); + if (insetOrOutset == InsetOrOutset::Inset) + snapPortOrArea.contract(extents); + else + snapPortOrArea.expand(extents); + return snapPortOrArea; +} + +static LayoutUnit computeScrollSnapAlignOffset(const LayoutUnit& leftOrTop, const LayoutUnit& widthOrHeight, ScrollSnapAxisAlignType alignment) +{ + switch (alignment) { + case ScrollSnapAxisAlignType::Start: + return leftOrTop; + case ScrollSnapAxisAlignType::Center: + return leftOrTop + widthOrHeight / 2; + case ScrollSnapAxisAlignType::End: + return leftOrTop + widthOrHeight; + default: + ASSERT_NOT_REACHED(); + return 0; + } +} + +#if !LOG_DISABLED + +static String snapOffsetsToString(const Vector<LayoutUnit>& snapOffsets) +{ + StringBuilder s; + s.append("[ "); + for (auto offset : snapOffsets) + s.append(String::format("%.1f ", offset.toFloat())); + + s.append("]"); + return s.toString(); +} + +static String snapOffsetRangesToString(const Vector<ScrollOffsetRange<LayoutUnit>>& ranges) +{ + StringBuilder s; + s.append("[ "); + for (auto range : ranges) + s.append(String::format("(%.1f, %.1f) ", range.start.toFloat(), range.end.toFloat())); + s.append("]"); + return s.toString(); +} + +static String snapPortOrAreaToString(const LayoutRect& rect) +{ + return String::format("{{%.1f, %.1f} {%.1f, %.1f}}", rect.x().toFloat(), rect.y().toFloat(), rect.width().toFloat(), rect.height().toFloat()); +} + +#endif + +template <typename LayoutType> +static void indicesOfNearestSnapOffsetRanges(LayoutType offset, const Vector<ScrollOffsetRange<LayoutType>>& snapOffsetRanges, unsigned& lowerIndex, unsigned& upperIndex) +{ + if (snapOffsetRanges.isEmpty()) { + lowerIndex = invalidSnapOffsetIndex; + upperIndex = invalidSnapOffsetIndex; + return; + } + + int lowerIndexAsInt = -1; + int upperIndexAsInt = snapOffsetRanges.size(); + do { + int middleIndex = (lowerIndexAsInt + upperIndexAsInt) / 2; + auto& range = snapOffsetRanges[middleIndex]; + if (range.start < offset && offset < range.end) { + lowerIndexAsInt = middleIndex; + upperIndexAsInt = middleIndex; + break; + } + + if (offset > range.end) + lowerIndexAsInt = middleIndex; + else + upperIndexAsInt = middleIndex; + } while (lowerIndexAsInt < upperIndexAsInt - 1); + + if (offset <= snapOffsetRanges.first().start) + lowerIndex = invalidSnapOffsetIndex; + else + lowerIndex = lowerIndexAsInt; + + if (offset >= snapOffsetRanges.last().end) + upperIndex = invalidSnapOffsetIndex; + else + upperIndex = upperIndexAsInt; +} + +template <typename LayoutType> +static void indicesOfNearestSnapOffsets(LayoutType offset, const Vector<LayoutType>& snapOffsets, unsigned& lowerIndex, unsigned& upperIndex) +{ + lowerIndex = 0; + upperIndex = snapOffsets.size() - 1; + while (lowerIndex < upperIndex - 1) { + int middleIndex = (lowerIndex + upperIndex) / 2; + auto middleOffset = snapOffsets[middleIndex]; + if (offset == middleOffset) { + upperIndex = middleIndex; + lowerIndex = middleIndex; + break; + } + + if (offset > middleOffset) + lowerIndex = middleIndex; + else + upperIndex = middleIndex; + } +} + +static void adjustAxisSnapOffsetsForScrollExtent(Vector<LayoutUnit>& snapOffsets, float maxScrollExtent) +{ + if (snapOffsets.isEmpty()) + return; + + std::sort(snapOffsets.begin(), snapOffsets.end()); + if (snapOffsets.last() != maxScrollExtent) + snapOffsets.append(maxScrollExtent); + if (snapOffsets.first()) + snapOffsets.insert(0, 0); +} + +static void computeAxisProximitySnapOffsetRanges(const Vector<LayoutUnit>& snapOffsets, Vector<ScrollOffsetRange<LayoutUnit>>& offsetRanges, LayoutUnit scrollPortAxisLength) +{ + // This is an arbitrary choice for what it means to be "in proximity" of a snap offset. We should play around with + // this and see what feels best. + static const float ratioOfScrollPortAxisLengthToBeConsideredForProximity = 0.3; + if (snapOffsets.size() < 2) + return; + + // The extra rule accounting for scroll offset ranges in between the scroll destination and a potential snap offset + // handles the corner case where the user scrolls with momentum very lightly away from a snap offset, such that the + // predicted scroll destination is still within proximity of the snap offset. In this case, the regular (mandatory + // scroll snapping) behavior would be to snap to the next offset in the direction of momentum scrolling, but + // instead, it is more intuitive to either return to the original snap position (which we arbitrarily choose here) + // or scroll just outside of the snap offset range. This is another minor behavior tweak that we should play around + // with to see what feels best. + LayoutUnit proximityDistance = ratioOfScrollPortAxisLengthToBeConsideredForProximity * scrollPortAxisLength; + for (size_t index = 1; index < snapOffsets.size(); ++index) { + auto startOffset = snapOffsets[index - 1] + proximityDistance; + auto endOffset = snapOffsets[index] - proximityDistance; + if (startOffset < endOffset) + offsetRanges.append({ startOffset, endOffset }); + } +} + +void updateSnapOffsetsForScrollableArea(ScrollableArea& scrollableArea, HTMLElement& scrollingElement, const RenderBox& scrollingElementBox, const RenderStyle& scrollingElementStyle) +{ + auto* scrollContainer = scrollingElement.renderer(); + auto scrollSnapType = scrollingElementStyle.scrollSnapType(); + if (!scrollContainer || scrollSnapType.strictness == ScrollSnapStrictness::None || scrollContainer->view().boxesWithScrollSnapPositions().isEmpty()) { + scrollableArea.clearHorizontalSnapOffsets(); + scrollableArea.clearVerticalSnapOffsets(); + return; + } + + Vector<LayoutUnit> verticalSnapOffsets; + Vector<LayoutUnit> horizontalSnapOffsets; + Vector<ScrollOffsetRange<LayoutUnit>> verticalSnapOffsetRanges; + Vector<ScrollOffsetRange<LayoutUnit>> horizontalSnapOffsetRanges; + HashSet<float> seenVerticalSnapOffsets; + HashSet<float> seenHorizontalSnapOffsets; + bool hasHorizontalSnapOffsets = scrollSnapType.axis == ScrollSnapAxis::Both || scrollSnapType.axis == ScrollSnapAxis::XAxis || scrollSnapType.axis == ScrollSnapAxis::Inline; + bool hasVerticalSnapOffsets = scrollSnapType.axis == ScrollSnapAxis::Both || scrollSnapType.axis == ScrollSnapAxis::YAxis || scrollSnapType.axis == ScrollSnapAxis::Block; + auto maxScrollLeft = scrollingElementBox.scrollWidth() - scrollingElementBox.contentWidth(); + auto maxScrollTop = scrollingElementBox.scrollHeight() - scrollingElementBox.contentHeight(); + LayoutPoint containerScrollOffset(scrollingElementBox.scrollLeft(), scrollingElementBox.scrollTop()); + + // The bounds of the scrolling container's snap port, where the top left of the scrolling container's border box is the origin. + auto scrollSnapPort = computeScrollSnapPortOrAreaRect(scrollingElementBox.paddingBoxRect(), scrollingElementStyle.scrollPadding(), InsetOrOutset::Inset); +#if !LOG_DISABLED + LOG(Scrolling, "Computing scroll snap offsets in snap port: %s", snapPortOrAreaToString(scrollSnapPort).utf8().data()); +#endif + for (auto* child : scrollContainer->view().boxesWithScrollSnapPositions()) { + if (child->findEnclosingScrollableContainer() != scrollContainer) + continue; + + // The bounds of the child element's snap area, where the top left of the scrolling container's border box is the origin. + // The snap area is the bounding box of the child element's border box, after applying transformations. + auto scrollSnapArea = LayoutRect(child->localToContainerQuad(FloatQuad(child->borderBoundingBox()), scrollingElement.renderBox()).boundingBox()); + scrollSnapArea.moveBy(containerScrollOffset); + scrollSnapArea = computeScrollSnapPortOrAreaRect(scrollSnapArea, child->style().scrollSnapMargin(), InsetOrOutset::Outset); +#if !LOG_DISABLED + LOG(Scrolling, " Considering scroll snap area: %s", snapPortOrAreaToString(scrollSnapArea).utf8().data()); +#endif + auto alignment = child->style().scrollSnapAlign(); + if (hasHorizontalSnapOffsets && alignment.x != ScrollSnapAxisAlignType::None) { + auto absoluteScrollOffset = clampTo<LayoutUnit>(computeScrollSnapAlignOffset(scrollSnapArea.x(), scrollSnapArea.width(), alignment.x) - computeScrollSnapAlignOffset(scrollSnapPort.x(), scrollSnapPort.width(), alignment.x), 0, maxScrollLeft); + if (!seenHorizontalSnapOffsets.contains(absoluteScrollOffset)) { + seenHorizontalSnapOffsets.add(absoluteScrollOffset); + horizontalSnapOffsets.append(absoluteScrollOffset); + } + } + if (hasVerticalSnapOffsets && alignment.y != ScrollSnapAxisAlignType::None) { + auto absoluteScrollOffset = clampTo<LayoutUnit>(computeScrollSnapAlignOffset(scrollSnapArea.y(), scrollSnapArea.height(), alignment.y) - computeScrollSnapAlignOffset(scrollSnapPort.y(), scrollSnapPort.height(), alignment.y), 0, maxScrollTop); + if (!seenVerticalSnapOffsets.contains(absoluteScrollOffset)) { + seenVerticalSnapOffsets.add(absoluteScrollOffset); + verticalSnapOffsets.append(absoluteScrollOffset); + } + } + } + + if (!horizontalSnapOffsets.isEmpty()) { + adjustAxisSnapOffsetsForScrollExtent(horizontalSnapOffsets, maxScrollLeft); +#if !LOG_DISABLED + LOG(Scrolling, " => Computed horizontal scroll snap offsets: %s", snapOffsetsToString(horizontalSnapOffsets).utf8().data()); + LOG(Scrolling, " => Computed horizontal scroll snap offset ranges: %s", snapOffsetRangesToString(horizontalSnapOffsetRanges).utf8().data()); +#endif + if (scrollSnapType.strictness == ScrollSnapStrictness::Proximity) + computeAxisProximitySnapOffsetRanges(horizontalSnapOffsets, horizontalSnapOffsetRanges, scrollSnapPort.width()); + + scrollableArea.setHorizontalSnapOffsets(horizontalSnapOffsets); + scrollableArea.setHorizontalSnapOffsetRanges(horizontalSnapOffsetRanges); + } else + scrollableArea.clearHorizontalSnapOffsets(); + + if (!verticalSnapOffsets.isEmpty()) { + adjustAxisSnapOffsetsForScrollExtent(verticalSnapOffsets, maxScrollTop); +#if !LOG_DISABLED + LOG(Scrolling, " => Computed vertical scroll snap offsets: %s", snapOffsetsToString(verticalSnapOffsets).utf8().data()); + LOG(Scrolling, " => Computed vertical scroll snap offset ranges: %s", snapOffsetRangesToString(verticalSnapOffsetRanges).utf8().data()); +#endif + if (scrollSnapType.strictness == ScrollSnapStrictness::Proximity) + computeAxisProximitySnapOffsetRanges(verticalSnapOffsets, verticalSnapOffsetRanges, scrollSnapPort.height()); + + scrollableArea.setVerticalSnapOffsets(verticalSnapOffsets); + scrollableArea.setVerticalSnapOffsetRanges(verticalSnapOffsetRanges); + } else + scrollableArea.clearVerticalSnapOffsets(); +} + +template <typename LayoutType> +LayoutType closestSnapOffset(const Vector<LayoutType>& snapOffsets, const Vector<ScrollOffsetRange<LayoutType>>& snapOffsetRanges, LayoutType scrollDestination, float velocity, unsigned& activeSnapIndex) +{ + ASSERT(snapOffsets.size()); + activeSnapIndex = 0; + + unsigned lowerSnapOffsetRangeIndex; + unsigned upperSnapOffsetRangeIndex; + indicesOfNearestSnapOffsetRanges<LayoutType>(scrollDestination, snapOffsetRanges, lowerSnapOffsetRangeIndex, upperSnapOffsetRangeIndex); + if (lowerSnapOffsetRangeIndex == upperSnapOffsetRangeIndex && upperSnapOffsetRangeIndex != invalidSnapOffsetIndex) { + activeSnapIndex = invalidSnapOffsetIndex; + return scrollDestination; + } + + if (scrollDestination <= snapOffsets.first()) + return snapOffsets.first(); + + activeSnapIndex = snapOffsets.size() - 1; + if (scrollDestination >= snapOffsets.last()) + return snapOffsets.last(); + + unsigned lowerIndex; + unsigned upperIndex; + indicesOfNearestSnapOffsets<LayoutType>(scrollDestination, snapOffsets, lowerIndex, upperIndex); + LayoutType lowerSnapPosition = snapOffsets[lowerIndex]; + LayoutType upperSnapPosition = snapOffsets[upperIndex]; + if (!std::abs(velocity)) { + bool isCloserToLowerSnapPosition = scrollDestination - lowerSnapPosition <= upperSnapPosition - scrollDestination; + activeSnapIndex = isCloserToLowerSnapPosition ? lowerIndex : upperIndex; + return isCloserToLowerSnapPosition ? lowerSnapPosition : upperSnapPosition; + } + + // Non-zero velocity indicates a flick gesture. Even if another snap point is closer, we should choose the one in the direction of the flick gesture + // as long as a scroll snap offset range does not lie between the scroll destination and the targeted snap offset. + if (velocity < 0) { + if (lowerSnapOffsetRangeIndex != invalidSnapOffsetIndex && lowerSnapPosition < snapOffsetRanges[lowerSnapOffsetRangeIndex].end) { + activeSnapIndex = upperIndex; + return upperSnapPosition; + } + activeSnapIndex = lowerIndex; + return lowerSnapPosition; + } + + if (upperSnapOffsetRangeIndex != invalidSnapOffsetIndex && snapOffsetRanges[upperSnapOffsetRangeIndex].start < upperSnapPosition) { + activeSnapIndex = lowerIndex; + return lowerSnapPosition; + } + activeSnapIndex = upperIndex; + return upperSnapPosition; +} + +LayoutUnit closestSnapOffset(const Vector<LayoutUnit>& snapOffsets, const Vector<ScrollOffsetRange<LayoutUnit>>& snapOffsetRanges, LayoutUnit scrollDestination, float velocity, unsigned& activeSnapIndex) +{ + return closestSnapOffset<LayoutUnit>(snapOffsets, snapOffsetRanges, scrollDestination, velocity, activeSnapIndex); +} + +float closestSnapOffset(const Vector<float>& snapOffsets, const Vector<ScrollOffsetRange<float>>& snapOffsetRanges, float scrollDestination, float velocity, unsigned& activeSnapIndex) +{ + return closestSnapOffset<float>(snapOffsets, snapOffsetRanges, scrollDestination, velocity, activeSnapIndex); +} + +} // namespace WebCore + +#endif // CSS_SCROLL_SNAP diff --git a/Source/WebCore/page/scrolling/AxisScrollSnapOffsets.h b/Source/WebCore/page/scrolling/AxisScrollSnapOffsets.h new file mode 100644 index 000000000..6b6b2bc85 --- /dev/null +++ b/Source/WebCore/page/scrolling/AxisScrollSnapOffsets.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2014-2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(CSS_SCROLL_SNAP) + +#include "LayoutUnit.h" +#include "ScrollSnapOffsetsInfo.h" +#include "ScrollTypes.h" +#include <wtf/Vector.h> + +namespace WebCore { + +class HTMLElement; +class RenderBox; +class RenderStyle; +class ScrollableArea; + +void updateSnapOffsetsForScrollableArea(ScrollableArea&, HTMLElement& scrollingElement, const RenderBox& scrollingElementBox, const RenderStyle& scrollingElementStyle); + +const unsigned invalidSnapOffsetIndex = UINT_MAX; +WEBCORE_EXPORT LayoutUnit closestSnapOffset(const Vector<LayoutUnit>& snapOffsets, const Vector<ScrollOffsetRange<LayoutUnit>>& snapOffsetRanges, LayoutUnit scrollDestination, float velocity, unsigned& activeSnapIndex); +WEBCORE_EXPORT float closestSnapOffset(const Vector<float>& snapOffsets, const Vector<ScrollOffsetRange<float>>& snapOffsetRanges, float scrollDestination, float velocity, unsigned& activeSnapIndex); + +} // namespace WebCore + +#endif // ENABLE(CSS_SCROLL_SNAP) diff --git a/Source/WebCore/page/scrolling/ScrollLatchingState.cpp b/Source/WebCore/page/scrolling/ScrollLatchingState.cpp new file mode 100644 index 000000000..01e6529b0 --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollLatchingState.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ScrollLatchingState.h" + +#include "Element.h" + +namespace WebCore { + +ScrollLatchingState::ScrollLatchingState() +{ +} + +ScrollLatchingState::~ScrollLatchingState() +{ +} + +void ScrollLatchingState::clear() +{ + m_wheelEventElement = nullptr; + m_frame = nullptr; + m_scrollableContainer = nullptr; + m_widgetIsLatched = false; + m_previousWheelScrolledElement = nullptr; +} + +void ScrollLatchingState::setWheelEventElement(Element* element) +{ + m_wheelEventElement = element; +} + +void ScrollLatchingState::setWidgetIsLatched(bool isOverWidget) +{ + m_widgetIsLatched = isOverWidget; +} + +void ScrollLatchingState::setPreviousWheelScrolledElement(Element* element) +{ + m_previousWheelScrolledElement = element; +} + +void ScrollLatchingState::setScrollableContainer(ContainerNode* container) +{ + m_scrollableContainer = container; +} + +} diff --git a/Source/WebCore/page/scrolling/ScrollLatchingState.h b/Source/WebCore/page/scrolling/ScrollLatchingState.h new file mode 100644 index 000000000..11dcbd0db --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollLatchingState.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <wtf/RefPtr.h> + +namespace WebCore { + +class ContainerNode; +class Element; +class Frame; + +class ScrollLatchingState final { +public: + ScrollLatchingState(); + ~ScrollLatchingState(); + + void clear(); + + Element* wheelEventElement() { return m_wheelEventElement.get(); } + void setWheelEventElement(Element*); + Frame* frame() { return m_frame; } + void setFrame(Frame* frame) { m_frame = frame; } + + bool widgetIsLatched() const { return m_widgetIsLatched; } + void setWidgetIsLatched(bool isOverWidget); + + Element* previousWheelScrolledElement() { return m_previousWheelScrolledElement.get(); } + void setPreviousWheelScrolledElement(Element*); + + ContainerNode* scrollableContainer() { return m_scrollableContainer.get(); } + void setScrollableContainer(ContainerNode*); + bool startedGestureAtScrollLimit() const { return m_startedGestureAtScrollLimit; } + void setStartedGestureAtScrollLimit(bool startedAtLimit) { m_startedGestureAtScrollLimit = startedAtLimit; } + +private: + RefPtr<Element> m_wheelEventElement; + RefPtr<Element> m_previousWheelScrolledElement; + RefPtr<ContainerNode> m_scrollableContainer; + + Frame* m_frame { nullptr }; + + bool m_widgetIsLatched { false }; + bool m_startedGestureAtScrollLimit { false }; +}; + +} // namespace WebCore diff --git a/Source/WebCore/page/scrolling/ScrollSnapOffsetsInfo.h b/Source/WebCore/page/scrolling/ScrollSnapOffsetsInfo.h new file mode 100644 index 000000000..63bd9638b --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollSnapOffsetsInfo.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <wtf/Vector.h> + +namespace WebCore { + +template <typename T> +struct ScrollOffsetRange { + T start; + T end; +}; + +template <typename T> +struct ScrollSnapOffsetsInfo { + Vector<T> horizontalSnapOffsets; + Vector<T> verticalSnapOffsets; + + // Snap offset ranges represent non-empty ranges of scroll offsets in which scrolling may rest after scroll snapping. + // These are used in two cases: (1) for proximity scroll snapping, where portions of areas between adjacent snap offsets + // may emit snap offset ranges, and (2) in the case where the snap area is larger than the snap port, in which case areas + // where the snap port fits within the snap area are considered to be valid snap positions. + Vector<ScrollOffsetRange<T>> horizontalSnapOffsetRanges; + Vector<ScrollOffsetRange<T>> verticalSnapOffsetRanges; +}; + +}; // namespace WebCore diff --git a/Source/WebCore/page/scrolling/ScrollingConstraints.cpp b/Source/WebCore/page/scrolling/ScrollingConstraints.cpp index 4b3f4d68c..00f101a34 100644 --- a/Source/WebCore/page/scrolling/ScrollingConstraints.cpp +++ b/Source/WebCore/page/scrolling/ScrollingConstraints.cpp @@ -26,6 +26,8 @@ #include "config.h" #include "ScrollingConstraints.h" +#include "TextStream.h" + namespace WebCore { FloatPoint FixedPositionViewportConstraints::layerPositionForViewportRect(const FloatRect& viewportRect) const @@ -98,4 +100,20 @@ FloatPoint StickyPositionViewportConstraints::layerPositionForConstrainingRect(c return m_layerPositionAtLastLayout + offset - m_stickyOffsetAtLastLayout; } +TextStream& operator<<(TextStream& ts, const FixedPositionViewportConstraints& constraints) +{ + ts.dumpProperty("viewport-rect-at-last-layout", constraints.viewportRectAtLastLayout()); + ts.dumpProperty("layer-position-at-last-layout", constraints.layerPositionAtLastLayout()); + + return ts; +} + +TextStream& operator<<(TextStream& ts, const StickyPositionViewportConstraints& constraints) +{ + ts.dumpProperty("sticky-position-at-last-layout", constraints.stickyOffsetAtLastLayout()); + ts.dumpProperty("layer-position-at-last-layout", constraints.layerPositionAtLastLayout()); + + return ts; +} + } // namespace WebCore diff --git a/Source/WebCore/page/scrolling/ScrollingConstraints.h b/Source/WebCore/page/scrolling/ScrollingConstraints.h index d4c1dd461..9d3860062 100644 --- a/Source/WebCore/page/scrolling/ScrollingConstraints.h +++ b/Source/WebCore/page/scrolling/ScrollingConstraints.h @@ -23,8 +23,7 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef ScrollingConstraints_h -#define ScrollingConstraints_h +#pragma once #include "FloatRect.h" @@ -33,6 +32,7 @@ namespace WebCore { // ViewportConstraints classes encapsulate data and logic required to reposition elements whose layout // depends on the viewport rect (positions fixed and sticky), when scrolling and zooming. class ViewportConstraints { + WTF_MAKE_FAST_ALLOCATED; public: enum ConstraintType { FixedPositionConstraint, @@ -85,7 +85,7 @@ public: , m_layerPositionAtLastLayout(other.m_layerPositionAtLastLayout) { } - FloatPoint layerPositionForViewportRect(const FloatRect& viewportRect) const; + WEBCORE_EXPORT FloatPoint layerPositionForViewportRect(const FloatRect& viewportRect) const; const FloatRect& viewportRectAtLastLayout() const { return m_viewportRectAtLastLayout; } void setViewportRectAtLastLayout(const FloatRect& rect) { m_viewportRectAtLastLayout = rect; } @@ -104,7 +104,7 @@ public: bool operator!=(const FixedPositionViewportConstraints& other) const { return !(*this == other); } private: - virtual ConstraintType constraintType() const override { return FixedPositionConstraint; }; + ConstraintType constraintType() const override { return FixedPositionConstraint; }; FloatRect m_viewportRectAtLastLayout; FloatPoint m_layerPositionAtLastLayout; @@ -137,7 +137,7 @@ public: const FloatSize stickyOffsetAtLastLayout() const { return m_stickyOffsetAtLastLayout; } void setStickyOffsetAtLastLayout(const FloatSize& offset) { m_stickyOffsetAtLastLayout = offset; } - FloatPoint layerPositionForConstrainingRect(const FloatRect& constrainingRect) const; + WEBCORE_EXPORT FloatPoint layerPositionForConstrainingRect(const FloatRect& constrainingRect) const; const FloatPoint& layerPositionAtLastLayout() const { return m_layerPositionAtLastLayout; } void setLayerPositionAtLastLayout(const FloatPoint& point) { m_layerPositionAtLastLayout = point; } @@ -167,7 +167,9 @@ public: bool operator==(const StickyPositionViewportConstraints& other) const { - return m_leftOffset == other.m_leftOffset + return m_alignmentOffset == other.m_alignmentOffset + && m_anchorEdges == other.m_anchorEdges + && m_leftOffset == other.m_leftOffset && m_rightOffset == other.m_rightOffset && m_topOffset == other.m_topOffset && m_bottomOffset == other.m_bottomOffset @@ -180,7 +182,7 @@ public: bool operator!=(const StickyPositionViewportConstraints& other) const { return !(*this == other); } private: - virtual ConstraintType constraintType() const override { return StickyPositionConstraint; }; + ConstraintType constraintType() const override { return StickyPositionConstraint; }; float m_leftOffset; float m_rightOffset; @@ -193,6 +195,7 @@ private: FloatPoint m_layerPositionAtLastLayout; }; -} // namespace WebCore +WEBCORE_EXPORT TextStream& operator<<(TextStream&, const FixedPositionViewportConstraints&); +WEBCORE_EXPORT TextStream& operator<<(TextStream&, const StickyPositionViewportConstraints&); -#endif // ScrollingConstraints_h +} // namespace WebCore diff --git a/Source/WebCore/page/scrolling/ScrollingCoordinator.cpp b/Source/WebCore/page/scrolling/ScrollingCoordinator.cpp index 93dfb0cec..ff34266a7 100644 --- a/Source/WebCore/page/scrolling/ScrollingCoordinator.cpp +++ b/Source/WebCore/page/scrolling/ScrollingCoordinator.cpp @@ -28,6 +28,7 @@ #include "ScrollingCoordinator.h" #include "Document.h" +#include "EventNames.h" #include "FrameView.h" #include "GraphicsLayer.h" #include "IntRect.h" @@ -36,35 +37,38 @@ #include "PlatformWheelEvent.h" #include "PluginViewBase.h" #include "Region.h" +#include "RenderLayerCompositor.h" #include "RenderView.h" #include "ScrollAnimator.h" +#include "Settings.h" +#include "TextStream.h" #include <wtf/MainThread.h> #include <wtf/text/StringBuilder.h> -#if USE(ACCELERATED_COMPOSITING) -#include "RenderLayerCompositor.h" -#endif - #if USE(COORDINATED_GRAPHICS) #include "ScrollingCoordinatorCoordinatedGraphics.h" #endif +#if ENABLE(WEB_REPLAY) +#include "ReplayController.h" +#include <replay/InputCursor.h> +#endif + namespace WebCore { -#if !PLATFORM(MAC) -PassRefPtr<ScrollingCoordinator> ScrollingCoordinator::create(Page* page) +#if !PLATFORM(COCOA) +Ref<ScrollingCoordinator> ScrollingCoordinator::create(Page* page) { #if USE(COORDINATED_GRAPHICS) - return adoptRef(new ScrollingCoordinatorCoordinatedGraphics(page)); + return adoptRef(*new ScrollingCoordinatorCoordinatedGraphics(page)); #endif - return adoptRef(new ScrollingCoordinator(page)); + return adoptRef(*new ScrollingCoordinator(page)); } #endif ScrollingCoordinator::ScrollingCoordinator(Page* page) : m_page(page) - , m_forceSynchronousScrollLayerPositionUpdates(false) { } @@ -76,88 +80,112 @@ ScrollingCoordinator::~ScrollingCoordinator() void ScrollingCoordinator::pageDestroyed() { ASSERT(m_page); - m_page = 0; + m_page = nullptr; } -bool ScrollingCoordinator::coordinatesScrollingForFrameView(FrameView* frameView) const +bool ScrollingCoordinator::coordinatesScrollingForFrameView(const FrameView& frameView) const { ASSERT(isMainThread()); ASSERT(m_page); - // We currently only handle the main frame. - if (!frameView->frame().isMainFrame()) + if (!frameView.frame().isMainFrame() && !m_page->settings().scrollingTreeIncludesFrames()) return false; - // We currently only support composited mode. -#if USE(ACCELERATED_COMPOSITING) RenderView* renderView = m_page->mainFrame().contentRenderer(); if (!renderView) return false; return renderView->usesCompositing(); -#else - return false; -#endif } -Region ScrollingCoordinator::computeNonFastScrollableRegion(const Frame* frame, const IntPoint& frameLocation) const +EventTrackingRegions ScrollingCoordinator::absoluteEventTrackingRegionsForFrame(const Frame& frame) const { - Region nonFastScrollableRegion; - FrameView* frameView = frame->view(); + auto* renderView = frame.contentRenderer(); + if (!renderView || renderView->renderTreeBeingDestroyed()) + return EventTrackingRegions(); + +#if ENABLE(IOS_TOUCH_EVENTS) + // On iOS, we use nonFastScrollableRegion to represent the region covered by elements with touch event handlers. + ASSERT(frame.isMainFrame()); + auto* document = frame.document(); + if (!document) + return EventTrackingRegions(); + return document->eventTrackingRegions(); +#else + auto* frameView = frame.view(); if (!frameView) - return nonFastScrollableRegion; + return EventTrackingRegions(); - IntPoint offset = frameLocation; - offset.moveBy(frameView->frameRect().location()); + Region nonFastScrollableRegion; - if (const FrameView::ScrollableAreaSet* scrollableAreas = frameView->scrollableAreas()) { - for (FrameView::ScrollableAreaSet::const_iterator it = scrollableAreas->begin(), end = scrollableAreas->end(); it != end; ++it) { - ScrollableArea* scrollableArea = *it; -#if USE(ACCELERATED_COMPOSITING) + // FIXME: should ASSERT(!frameView->needsLayout()) here, but need to fix DebugPageOverlays + // to not ask for regions at bad times. + + if (auto* scrollableAreas = frameView->scrollableAreas()) { + for (auto& scrollableArea : *scrollableAreas) { // Composited scrollable areas can be scrolled off the main thread. - if (scrollableArea->usesCompositedScrolling()) + if (scrollableArea->usesAsyncScrolling()) continue; -#endif - IntRect box = scrollableArea->scrollableAreaBoundingBox(); - box.moveBy(offset); + + bool isInsideFixed; + IntRect box = scrollableArea->scrollableAreaBoundingBox(&isInsideFixed); + if (isInsideFixed) + box = IntRect(frameView->fixedScrollableAreaBoundsInflatedForScrolling(LayoutRect(box))); + nonFastScrollableRegion.unite(box); } } - for (auto it = frameView->children().begin(), end = frameView->children().end(); it != end; ++it) { - if (!(*it)->isPluginViewBase()) + for (auto& widget : frameView->widgetsInRenderTree()) { + if (!is<PluginViewBase>(*widget)) + continue; + if (!downcast<PluginViewBase>(*widget).wantsWheelEvents()) + continue; + auto* renderWidget = RenderWidget::find(*widget); + if (!renderWidget) continue; - PluginViewBase* pluginViewBase = toPluginViewBase((*it).get()); - if (pluginViewBase->wantsWheelEvents()) - nonFastScrollableRegion.unite(pluginViewBase->frameRect()); + nonFastScrollableRegion.unite(renderWidget->absoluteBoundingBoxRect()); } + + EventTrackingRegions eventTrackingRegions; - for (Frame* subframe = frame->tree().firstChild(); subframe; subframe = subframe->tree().nextSibling()) - nonFastScrollableRegion.unite(computeNonFastScrollableRegion(subframe, offset)); + // FIXME: if we've already accounted for this subframe as a scrollable area, we can avoid recursing into it here. + for (Frame* subframe = frame.tree().firstChild(); subframe; subframe = subframe->tree().nextSibling()) { + auto* subframeView = subframe->view(); + if (!subframeView) + continue; - return nonFastScrollableRegion; -} + EventTrackingRegions subframeRegion = absoluteEventTrackingRegionsForFrame(*subframe); + // Map from the frame document to our document. + IntPoint offset = subframeView->contentsToContainingViewContents(IntPoint()); -unsigned ScrollingCoordinator::computeCurrentWheelEventHandlerCount() -{ - unsigned wheelEventHandlerCount = 0; + // FIXME: this translation ignores non-trival transforms on the frame. + subframeRegion.translate(toIntSize(offset)); + eventTrackingRegions.unite(subframeRegion); + } - for (Frame* frame = &m_page->mainFrame(); frame; frame = frame->tree().traverseNext()) { - if (frame->document()) - wheelEventHandlerCount += frame->document()->wheelEventHandlerCount(); + auto wheelHandlerRegion = frame.document()->absoluteRegionForEventTargets(frame.document()->wheelEventTargets()); + bool wheelHandlerInFixedContent = wheelHandlerRegion.second; + if (wheelHandlerInFixedContent) { + // FIXME: need to handle position:sticky here too. + LayoutRect inflatedWheelHandlerBounds = frameView->fixedScrollableAreaBoundsInflatedForScrolling(LayoutRect(wheelHandlerRegion.first.bounds())); + wheelHandlerRegion.first.unite(enclosingIntRect(inflatedWheelHandlerBounds)); } + + nonFastScrollableRegion.unite(wheelHandlerRegion.first); + + // FIXME: If this is not the main frame, we could clip the region to the frame's bounds. + eventTrackingRegions.uniteSynchronousRegion(eventNames().wheelEvent, nonFastScrollableRegion); - return wheelEventHandlerCount; + return eventTrackingRegions; +#endif } -void ScrollingCoordinator::frameViewWheelEventHandlerCountChanged(FrameView* frameView) +EventTrackingRegions ScrollingCoordinator::absoluteEventTrackingRegions() const { - ASSERT(isMainThread()); - ASSERT(m_page); - - recomputeWheelEventHandlerCountForFrameView(frameView); + return absoluteEventTrackingRegionsForFrame(m_page->mainFrame()); } -void ScrollingCoordinator::frameViewHasSlowRepaintObjectsDidChange(FrameView* frameView) +void ScrollingCoordinator::frameViewHasSlowRepaintObjectsDidChange(FrameView& frameView) { ASSERT(isMainThread()); ASSERT(m_page); @@ -165,10 +193,10 @@ void ScrollingCoordinator::frameViewHasSlowRepaintObjectsDidChange(FrameView* fr if (!coordinatesScrollingForFrameView(frameView)) return; - updateSynchronousScrollingReasons(); + updateSynchronousScrollingReasons(frameView); } -void ScrollingCoordinator::frameViewFixedObjectsDidChange(FrameView* frameView) +void ScrollingCoordinator::frameViewFixedObjectsDidChange(FrameView& frameView) { ASSERT(isMainThread()); ASSERT(m_page); @@ -176,75 +204,80 @@ void ScrollingCoordinator::frameViewFixedObjectsDidChange(FrameView* frameView) if (!coordinatesScrollingForFrameView(frameView)) return; - updateSynchronousScrollingReasons(); + updateSynchronousScrollingReasons(frameView); } -#if USE(ACCELERATED_COMPOSITING) -GraphicsLayer* ScrollingCoordinator::scrollLayerForScrollableArea(ScrollableArea* scrollableArea) +GraphicsLayer* ScrollingCoordinator::scrollLayerForScrollableArea(ScrollableArea& scrollableArea) { - return scrollableArea->layerForScrolling(); + return scrollableArea.layerForScrolling(); } -GraphicsLayer* ScrollingCoordinator::horizontalScrollbarLayerForScrollableArea(ScrollableArea* scrollableArea) +GraphicsLayer* ScrollingCoordinator::scrollLayerForFrameView(FrameView& frameView) { - return scrollableArea->layerForHorizontalScrollbar(); -} - -GraphicsLayer* ScrollingCoordinator::verticalScrollbarLayerForScrollableArea(ScrollableArea* scrollableArea) -{ - return scrollableArea->layerForVerticalScrollbar(); -} -#endif - -GraphicsLayer* ScrollingCoordinator::scrollLayerForFrameView(FrameView* frameView) -{ -#if USE(ACCELERATED_COMPOSITING) - if (RenderView* renderView = frameView->frame().contentRenderer()) + if (RenderView* renderView = frameView.frame().contentRenderer()) return renderView->compositor().scrollLayer(); - return 0; -#else - UNUSED_PARAM(frameView); - return 0; -#endif + return nullptr; } -GraphicsLayer* ScrollingCoordinator::headerLayerForFrameView(FrameView* frameView) +GraphicsLayer* ScrollingCoordinator::headerLayerForFrameView(FrameView& frameView) { -#if USE(ACCELERATED_COMPOSITING) && ENABLE(RUBBER_BANDING) - if (RenderView* renderView = frameView->frame().contentRenderer()) - renderView->compositor().headerLayer(); - return 0; +#if ENABLE(RUBBER_BANDING) + if (RenderView* renderView = frameView.frame().contentRenderer()) + return renderView->compositor().headerLayer(); + return nullptr; #else UNUSED_PARAM(frameView); - return 0; + return nullptr; #endif } -GraphicsLayer* ScrollingCoordinator::footerLayerForFrameView(FrameView* frameView) +GraphicsLayer* ScrollingCoordinator::footerLayerForFrameView(FrameView& frameView) { -#if USE(ACCELERATED_COMPOSITING) && ENABLE(RUBBER_BANDING) - if (RenderView* renderView = frameView->frame().contentRenderer()) +#if ENABLE(RUBBER_BANDING) + if (RenderView* renderView = frameView.frame().contentRenderer()) return renderView->compositor().footerLayer(); - return 0; + return nullptr; #else UNUSED_PARAM(frameView); - return 0; + return nullptr; #endif } -GraphicsLayer* ScrollingCoordinator::counterScrollingLayerForFrameView(FrameView* frameView) +GraphicsLayer* ScrollingCoordinator::counterScrollingLayerForFrameView(FrameView& frameView) { -#if USE(ACCELERATED_COMPOSITING) - if (RenderView* renderView = frameView->frame().contentRenderer()) + if (RenderView* renderView = frameView.frame().contentRenderer()) return renderView->compositor().fixedRootBackgroundLayer(); - return 0; + return nullptr; +} + +GraphicsLayer* ScrollingCoordinator::insetClipLayerForFrameView(FrameView& frameView) +{ + if (RenderView* renderView = frameView.frame().contentRenderer()) + return renderView->compositor().clipLayer(); + return nullptr; +} + +GraphicsLayer* ScrollingCoordinator::contentShadowLayerForFrameView(FrameView& frameView) +{ +#if ENABLE(RUBBER_BANDING) + if (RenderView* renderView = frameView.frame().contentRenderer()) + return renderView->compositor().layerForContentShadow(); + + return nullptr; #else UNUSED_PARAM(frameView); - return 0; + return nullptr; #endif } -void ScrollingCoordinator::frameViewRootLayerDidChange(FrameView* frameView) +GraphicsLayer* ScrollingCoordinator::rootContentLayerForFrameView(FrameView& frameView) +{ + if (RenderView* renderView = frameView.frame().contentRenderer()) + return renderView->compositor().rootContentLayer(); + return nullptr; +} + +void ScrollingCoordinator::frameViewRootLayerDidChange(FrameView& frameView) { ASSERT(isMainThread()); ASSERT(m_page); @@ -253,11 +286,10 @@ void ScrollingCoordinator::frameViewRootLayerDidChange(FrameView* frameView) return; frameViewLayoutUpdated(frameView); - recomputeWheelEventHandlerCountForFrameView(frameView); - updateSynchronousScrollingReasons(); + updateSynchronousScrollingReasons(frameView); } -#if PLATFORM(MAC) +#if PLATFORM(COCOA) void ScrollingCoordinator::handleWheelEventPhase(PlatformWheelEventPhase phase) { ASSERT(isMainThread()); @@ -269,57 +301,58 @@ void ScrollingCoordinator::handleWheelEventPhase(PlatformWheelEventPhase phase) if (!frameView) return; - frameView->scrollAnimator()->handleWheelEventPhase(phase); + frameView->scrollAnimator().handleWheelEventPhase(phase); } #endif -bool ScrollingCoordinator::hasVisibleSlowRepaintViewportConstrainedObjects(FrameView* frameView) const +bool ScrollingCoordinator::hasVisibleSlowRepaintViewportConstrainedObjects(const FrameView& frameView) const { - const FrameView::ViewportConstrainedObjectSet* viewportConstrainedObjects = frameView->viewportConstrainedObjects(); + const FrameView::ViewportConstrainedObjectSet* viewportConstrainedObjects = frameView.viewportConstrainedObjects(); if (!viewportConstrainedObjects) return false; -#if USE(ACCELERATED_COMPOSITING) - for (FrameView::ViewportConstrainedObjectSet::const_iterator it = viewportConstrainedObjects->begin(), end = viewportConstrainedObjects->end(); it != end; ++it) { - RenderObject* viewportConstrainedObject = *it; - if (!viewportConstrainedObject->isBoxModelObject() || !viewportConstrainedObject->hasLayer()) + for (auto& viewportConstrainedObject : *viewportConstrainedObjects) { + if (!is<RenderBoxModelObject>(*viewportConstrainedObject) || !viewportConstrainedObject->hasLayer()) return true; - RenderLayer* layer = toRenderBoxModelObject(viewportConstrainedObject)->layer(); + RenderLayer& layer = *downcast<RenderBoxModelObject>(*viewportConstrainedObject).layer(); // Any explicit reason that a fixed position element is not composited shouldn't cause slow scrolling. - if (!layer->isComposited() && layer->viewportConstrainedNotCompositedReason() == RenderLayer::NoNotCompositedReason) + if (!layer.isComposited() && layer.viewportConstrainedNotCompositedReason() == RenderLayer::NoNotCompositedReason) return true; } return false; -#else - return viewportConstrainedObjects->size(); -#endif } -SynchronousScrollingReasons ScrollingCoordinator::synchronousScrollingReasons() const +SynchronousScrollingReasons ScrollingCoordinator::synchronousScrollingReasons(const FrameView& frameView) const { - FrameView* frameView = m_page->mainFrame().view(); - if (!frameView) - return static_cast<SynchronousScrollingReasons>(0); - SynchronousScrollingReasons synchronousScrollingReasons = (SynchronousScrollingReasons)0; if (m_forceSynchronousScrollLayerPositionUpdates) synchronousScrollingReasons |= ForcedOnMainThread; - if (frameView->hasSlowRepaintObjects()) +#if ENABLE(WEB_REPLAY) + InputCursor& cursor = m_page->replayController().activeInputCursor(); + if (cursor.isCapturing() || cursor.isReplaying()) + synchronousScrollingReasons |= ForcedOnMainThread; +#endif + if (frameView.hasSlowRepaintObjects()) synchronousScrollingReasons |= HasSlowRepaintObjects; - if (!supportsFixedPositionLayers() && frameView->hasViewportConstrainedObjects()) + if (!supportsFixedPositionLayers() && frameView.hasViewportConstrainedObjects()) synchronousScrollingReasons |= HasViewportConstrainedObjectsWithoutSupportingFixedLayers; if (supportsFixedPositionLayers() && hasVisibleSlowRepaintViewportConstrainedObjects(frameView)) synchronousScrollingReasons |= HasNonLayerViewportConstrainedObjects; - if (m_page->mainFrame().document() && m_page->mainFrame().document()->isImageDocument()) + if (frameView.frame().mainFrame().document() && frameView.frame().document()->isImageDocument()) synchronousScrollingReasons |= IsImageDocument; return synchronousScrollingReasons; } -void ScrollingCoordinator::updateSynchronousScrollingReasons() +void ScrollingCoordinator::updateSynchronousScrollingReasons(const FrameView& frameView) { - setSynchronousScrollingReasons(synchronousScrollingReasons()); + // FIXME: Once we support async scrolling of iframes, we'll have to track the synchronous scrolling + // reasons per frame (maybe on scrolling tree nodes). + if (!frameView.frame().isMainFrame()) + return; + + setSynchronousScrollingReasons(synchronousScrollingReasons(frameView)); } void ScrollingCoordinator::setForceSynchronousScrollLayerPositionUpdates(bool forceSynchronousScrollLayerPositionUpdates) @@ -328,9 +361,27 @@ void ScrollingCoordinator::setForceSynchronousScrollLayerPositionUpdates(bool fo return; m_forceSynchronousScrollLayerPositionUpdates = forceSynchronousScrollLayerPositionUpdates; - updateSynchronousScrollingReasons(); + if (FrameView* frameView = m_page->mainFrame().view()) + updateSynchronousScrollingReasons(*frameView); } +bool ScrollingCoordinator::shouldUpdateScrollLayerPositionSynchronously(const FrameView& frameView) const +{ + if (&frameView == m_page->mainFrame().view()) + return synchronousScrollingReasons(frameView); + + return true; +} + +#if ENABLE(WEB_REPLAY) +void ScrollingCoordinator::replaySessionStateDidChange() +{ + // FIXME: Once we support async scrolling of iframes, this should go through all subframes. + if (FrameView* frameView = m_page->mainFrame().view()) + updateSynchronousScrollingReasons(*frameView); +} +#endif + ScrollingNodeID ScrollingCoordinator::uniqueScrollLayerID() { static ScrollingNodeID uniqueScrollLayerID = 1; @@ -347,15 +398,15 @@ String ScrollingCoordinator::synchronousScrollingReasonsAsText(SynchronousScroll StringBuilder stringBuilder; if (reasons & ScrollingCoordinator::ForcedOnMainThread) - stringBuilder.append("Forced on main thread, "); + stringBuilder.appendLiteral("Forced on main thread, "); if (reasons & ScrollingCoordinator::HasSlowRepaintObjects) - stringBuilder.append("Has slow repaint objects, "); + stringBuilder.appendLiteral("Has slow repaint objects, "); if (reasons & ScrollingCoordinator::HasViewportConstrainedObjectsWithoutSupportingFixedLayers) - stringBuilder.append("Has viewport constrained objects without supporting fixed layers, "); + stringBuilder.appendLiteral("Has viewport constrained objects without supporting fixed layers, "); if (reasons & ScrollingCoordinator::HasNonLayerViewportConstrainedObjects) - stringBuilder.append("Has non-layer viewport-constrained objects, "); + stringBuilder.appendLiteral("Has non-layer viewport-constrained objects, "); if (reasons & ScrollingCoordinator::IsImageDocument) - stringBuilder.append("Is image document, "); + stringBuilder.appendLiteral("Is image document, "); if (stringBuilder.length()) stringBuilder.resize(stringBuilder.length() - 2); @@ -364,7 +415,45 @@ String ScrollingCoordinator::synchronousScrollingReasonsAsText(SynchronousScroll String ScrollingCoordinator::synchronousScrollingReasonsAsText() const { - return synchronousScrollingReasonsAsText(synchronousScrollingReasons()); + if (FrameView* frameView = m_page->mainFrame().view()) + return synchronousScrollingReasonsAsText(synchronousScrollingReasons(*frameView)); + + return String(); +} + +TextStream& operator<<(TextStream& ts, ScrollingNodeType nodeType) +{ + switch (nodeType) { + case FrameScrollingNode: + ts << "frame-scrolling"; + break; + case OverflowScrollingNode: + ts << "overflow-scrolling"; + break; + case FixedNode: + ts << "fixed"; + break; + case StickyNode: + ts << "sticky"; + break; + } + return ts; +} + +TextStream& operator<<(TextStream& ts, ScrollingLayerPositionAction action) +{ + switch (action) { + case ScrollingLayerPositionAction::Set: + ts << "set"; + break; + case ScrollingLayerPositionAction::SetApproximate: + ts << "set approximate"; + break; + case ScrollingLayerPositionAction::Sync: + ts << "sync"; + break; + } + return ts; } } // namespace WebCore diff --git a/Source/WebCore/page/scrolling/ScrollingCoordinator.h b/Source/WebCore/page/scrolling/ScrollingCoordinator.h index 0e05d69fd..eb6dabf97 100644 --- a/Source/WebCore/page/scrolling/ScrollingCoordinator.h +++ b/Source/WebCore/page/scrolling/ScrollingCoordinator.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 Apple Inc. All rights reserved. + * Copyright (C) 2011, 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -23,15 +23,18 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef ScrollingCoordinator_h -#define ScrollingCoordinator_h +#pragma once +#include "EventTrackingRegions.h" #include "IntRect.h" #include "LayoutRect.h" #include "PlatformWheelEvent.h" -#include "RenderObject.h" +#include "ScrollSnapOffsetsInfo.h" #include "ScrollTypes.h" #include <wtf/Forward.h> +#include <wtf/ThreadSafeRefCounted.h> +#include <wtf/TypeCasts.h> +#include <wtf/Variant.h> #if ENABLE(ASYNC_SCROLLING) #include <wtf/HashMap.h> @@ -39,8 +42,8 @@ #include <wtf/Threading.h> #endif -#if PLATFORM(MAC) -#include <wtf/RetainPtr.h> +#if ENABLE(CSS_SCROLL_SNAP) +#include "AxisScrollSnapOffsets.h" #endif namespace WebCore { @@ -48,7 +51,7 @@ namespace WebCore { typedef unsigned SynchronousScrollingReasons; typedef uint64_t ScrollingNodeID; -enum ScrollingNodeType { ScrollingNode, FixedNode, StickyNode }; +enum ScrollingNodeType { FrameScrollingNode, OverflowScrollingNode, FixedNode, StickyNode }; class Document; class Frame; @@ -57,15 +60,17 @@ class GraphicsLayer; class Page; class Region; class ScrollableArea; +class TextStream; class ViewportConstraints; #if ENABLE(ASYNC_SCROLLING) class ScrollingTree; #endif -enum SetOrSyncScrollingLayerPosition { - SetScrollingLayerPosition, - SyncScrollingLayerPosition +enum class ScrollingLayerPositionAction { + Set, + SetApproximate, + Sync }; struct ScrollableAreaParameters { @@ -101,114 +106,145 @@ struct ScrollableAreaParameters { class ScrollingCoordinator : public ThreadSafeRefCounted<ScrollingCoordinator> { public: - static PassRefPtr<ScrollingCoordinator> create(Page*); + static Ref<ScrollingCoordinator> create(Page*); virtual ~ScrollingCoordinator(); - virtual void pageDestroyed(); + WEBCORE_EXPORT virtual void pageDestroyed(); virtual bool isAsyncScrollingCoordinator() const { return false; } virtual bool isRemoteScrollingCoordinator() const { return false; } // Return whether this scrolling coordinator handles scrolling for the given frame view. - bool coordinatesScrollingForFrameView(FrameView*) const; + virtual bool coordinatesScrollingForFrameView(const FrameView&) const; // Should be called whenever the given frame view has been laid out. - virtual void frameViewLayoutUpdated(FrameView*) { } + virtual void frameViewLayoutUpdated(FrameView&) { } - // Should be called whenever a wheel event handler is added or removed in the - // frame view's underlying document. - void frameViewWheelEventHandlerCountChanged(FrameView*); + using LayoutViewportOriginOrOverrideRect = WTF::Variant<std::optional<FloatPoint>, std::optional<FloatRect>>; + virtual void reconcileScrollingState(FrameView&, const FloatPoint&, const LayoutViewportOriginOrOverrideRect&, bool /* programmaticScroll */, bool /* inStableState*/, ScrollingLayerPositionAction) { } // Should be called whenever the slow repaint objects counter changes between zero and one. - void frameViewHasSlowRepaintObjectsDidChange(FrameView*); + void frameViewHasSlowRepaintObjectsDidChange(FrameView&); // Should be called whenever the set of fixed objects changes. - void frameViewFixedObjectsDidChange(FrameView*); + void frameViewFixedObjectsDidChange(FrameView&); + + // Called whenever the non-fast scrollable region changes for reasons other than layout. + virtual void frameViewEventTrackingRegionsChanged(FrameView&) { } // Should be called whenever the root layer for the given frame view changes. - virtual void frameViewRootLayerDidChange(FrameView*); + virtual void frameViewRootLayerDidChange(FrameView&); // Return whether this scrolling coordinator can keep fixed position layers fixed to their // containers while scrolling. virtual bool supportsFixedPositionLayers() const { return false; } -#if PLATFORM(MAC) +#if PLATFORM(COCOA) // Dispatched by the scrolling tree during handleWheelEvent. This is required as long as scrollbars are painted on the main thread. void handleWheelEventPhase(PlatformWheelEventPhase); #endif +#if ENABLE(WEB_REPLAY) + // Called when the page transitions between executing normally and deterministically. + void replaySessionStateDidChange(); +#endif + // Force all scroll layer position updates to happen on the main thread. - void setForceSynchronousScrollLayerPositionUpdates(bool); + WEBCORE_EXPORT void setForceSynchronousScrollLayerPositionUpdates(bool); // These virtual functions are currently unique to the threaded scrolling architecture. // Their meaningful implementations are in ScrollingCoordinatorMac. virtual void commitTreeStateIfNeeded() { } - virtual bool requestScrollPositionUpdate(FrameView*, const IntPoint&) { return false; } - virtual bool handleWheelEvent(FrameView*, const PlatformWheelEvent&) { return true; } + virtual bool requestScrollPositionUpdate(FrameView&, const IntPoint&) { return false; } + virtual bool handleWheelEvent(FrameView&, const PlatformWheelEvent&) { return true; } virtual ScrollingNodeID attachToStateTree(ScrollingNodeType, ScrollingNodeID newNodeID, ScrollingNodeID /*parentID*/) { return newNodeID; } virtual void detachFromStateTree(ScrollingNodeID) { } virtual void clearStateTree() { } virtual void updateViewportConstrainedNode(ScrollingNodeID, const ViewportConstraints&, GraphicsLayer*) { } - virtual void updateScrollingNode(ScrollingNodeID, GraphicsLayer* /*scrollLayer*/, GraphicsLayer* /*counterScrollingLayer*/) { } - virtual void syncChildPositions(const LayoutRect&) { } + + struct ScrollingGeometry { + FloatSize scrollableAreaSize; + FloatSize contentSize; + FloatSize reachableContentSize; // Smaller than contentSize when overflow is hidden on one axis. + FloatPoint scrollPosition; + IntPoint scrollOrigin; +#if ENABLE(CSS_SCROLL_SNAP) + Vector<LayoutUnit> horizontalSnapOffsets; + Vector<LayoutUnit> verticalSnapOffsets; + Vector<ScrollOffsetRange<LayoutUnit>> horizontalSnapOffsetRanges; + Vector<ScrollOffsetRange<LayoutUnit>> verticalSnapOffsetRanges; + unsigned currentHorizontalSnapPointIndex; + unsigned currentVerticalSnapPointIndex; +#endif + }; + + virtual void updateFrameScrollingNode(ScrollingNodeID, GraphicsLayer* /*scrollLayer*/, GraphicsLayer* /*scrolledContentsLayer*/, GraphicsLayer* /*counterScrollingLayer*/, GraphicsLayer* /*insetClipLayer*/, const ScrollingGeometry* = nullptr) { } + virtual void updateOverflowScrollingNode(ScrollingNodeID, GraphicsLayer* /*scrollLayer*/, GraphicsLayer* /*scrolledContentsLayer*/, const ScrollingGeometry* = nullptr) { } + virtual void reconcileViewportConstrainedLayerPositions(const LayoutRect&, ScrollingLayerPositionAction) { } virtual String scrollingStateTreeAsText() const; virtual bool isRubberBandInProgress() const { return false; } + virtual bool isScrollSnapInProgress() const { return false; } + virtual void updateScrollSnapPropertiesWithFrameView(const FrameView&) { } virtual void setScrollPinningBehavior(ScrollPinningBehavior) { } // Generated a unique id for scroll layers. ScrollingNodeID uniqueScrollLayerID(); enum MainThreadScrollingReasonFlags { - ForcedOnMainThread = 1 << 0, - HasSlowRepaintObjects = 1 << 1, - HasViewportConstrainedObjectsWithoutSupportingFixedLayers = 1 << 2, - HasNonLayerViewportConstrainedObjects = 1 << 3, - IsImageDocument = 1 << 4 + ForcedOnMainThread = 1 << 0, + HasSlowRepaintObjects = 1 << 1, + HasViewportConstrainedObjectsWithoutSupportingFixedLayers = 1 << 2, + HasNonLayerViewportConstrainedObjects = 1 << 3, + IsImageDocument = 1 << 4 }; - SynchronousScrollingReasons synchronousScrollingReasons() const; - bool shouldUpdateScrollLayerPositionSynchronously() const { return synchronousScrollingReasons(); } + SynchronousScrollingReasons synchronousScrollingReasons(const FrameView&) const; + bool shouldUpdateScrollLayerPositionSynchronously(const FrameView&) const; - virtual void willDestroyScrollableArea(ScrollableArea*) { } - virtual void scrollableAreaScrollLayerDidChange(ScrollableArea*) { } - virtual void scrollableAreaScrollbarLayerDidChange(ScrollableArea*, ScrollbarOrientation) { } - virtual void setLayerIsContainerForFixedPositionLayers(GraphicsLayer*, bool) { } + virtual void willDestroyScrollableArea(ScrollableArea&) { } + virtual void scrollableAreaScrollLayerDidChange(ScrollableArea&) { } + virtual void scrollableAreaScrollbarLayerDidChange(ScrollableArea&, ScrollbarOrientation) { } static String synchronousScrollingReasonsAsText(SynchronousScrollingReasons); String synchronousScrollingReasonsAsText() const; - Region computeNonFastScrollableRegion(const Frame*, const IntPoint& frameLocation) const; + EventTrackingRegions absoluteEventTrackingRegions() const; + virtual void updateExpectsWheelEventTestTriggerWithFrameView(const FrameView&) { } protected: explicit ScrollingCoordinator(Page*); -#if USE(ACCELERATED_COMPOSITING) - static GraphicsLayer* scrollLayerForScrollableArea(ScrollableArea*); - static GraphicsLayer* horizontalScrollbarLayerForScrollableArea(ScrollableArea*); - static GraphicsLayer* verticalScrollbarLayerForScrollableArea(ScrollableArea*); -#endif + static GraphicsLayer* scrollLayerForScrollableArea(ScrollableArea&); - unsigned computeCurrentWheelEventHandlerCount(); - GraphicsLayer* scrollLayerForFrameView(FrameView*); - GraphicsLayer* counterScrollingLayerForFrameView(FrameView*); - GraphicsLayer* headerLayerForFrameView(FrameView*); - GraphicsLayer* footerLayerForFrameView(FrameView*); + GraphicsLayer* scrollLayerForFrameView(FrameView&); + GraphicsLayer* counterScrollingLayerForFrameView(FrameView&); + GraphicsLayer* insetClipLayerForFrameView(FrameView&); + GraphicsLayer* rootContentLayerForFrameView(FrameView&); + GraphicsLayer* contentShadowLayerForFrameView(FrameView&); + GraphicsLayer* headerLayerForFrameView(FrameView&); + GraphicsLayer* footerLayerForFrameView(FrameView&); + + virtual void willCommitTree() { } Page* m_page; // FIXME: ideally this would be a reference but it gets nulled on async teardown. private: - virtual void recomputeWheelEventHandlerCountForFrameView(FrameView*) { } virtual void setSynchronousScrollingReasons(SynchronousScrollingReasons) { } - virtual bool hasVisibleSlowRepaintViewportConstrainedObjects(FrameView*) const; - void updateSynchronousScrollingReasons(); + virtual bool hasVisibleSlowRepaintViewportConstrainedObjects(const FrameView&) const; + void updateSynchronousScrollingReasons(const FrameView&); + + EventTrackingRegions absoluteEventTrackingRegionsForFrame(const Frame&) const; - bool m_forceSynchronousScrollLayerPositionUpdates; + bool m_forceSynchronousScrollLayerPositionUpdates { false }; }; -#define SCROLLING_COORDINATOR_TYPE_CASTS(ToValueTypeName, predicate) \ - TYPE_CASTS_BASE(ToValueTypeName, WebCore::ScrollingCoordinator, value, value->predicate, value.predicate) +WEBCORE_EXPORT TextStream& operator<<(TextStream&, ScrollingNodeType); +WEBCORE_EXPORT TextStream& operator<<(TextStream&, ScrollingLayerPositionAction); } // namespace WebCore -#endif // ScrollingCoordinator_h +#define SPECIALIZE_TYPE_TRAITS_SCROLLING_COORDINATOR(ToValueTypeName, predicate) \ +SPECIALIZE_TYPE_TRAITS_BEGIN(ToValueTypeName) \ + static bool isType(const WebCore::ScrollingCoordinator& value) { return value.predicate; } \ +SPECIALIZE_TYPE_TRAITS_END() diff --git a/Source/WebCore/page/scrolling/ScrollingMomentumCalculator.cpp b/Source/WebCore/page/scrolling/ScrollingMomentumCalculator.cpp new file mode 100644 index 000000000..1fa5674a2 --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollingMomentumCalculator.cpp @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ScrollingMomentumCalculator.h" + +#include "FloatPoint.h" +#include "FloatSize.h" + +namespace WebCore { + +static const Seconds scrollSnapAnimationDuration = 1_s; +static inline float projectedInertialScrollDistance(float initialWheelDelta) +{ + // On macOS 10.10 and earlier, we don't have a platform scrolling momentum calculator, so we instead approximate the scroll destination + // by multiplying the initial wheel delta by a constant factor. By running a few experiments (i.e. logging scroll destination and initial + // wheel delta for many scroll gestures) we determined that this is a reasonable way to approximate where scrolling will take us without + // using _NSScrollingMomentumCalculator. + const static double inertialScrollPredictionFactor = 16.7; + return inertialScrollPredictionFactor * initialWheelDelta; +} + +ScrollingMomentumCalculator::ScrollingMomentumCalculator(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity) + : m_initialDelta(initialDelta) + , m_initialVelocity(initialVelocity) + , m_initialScrollOffset(initialOffset.x(), initialOffset.y()) + , m_viewportSize(viewportSize) + , m_contentSize(contentSize) +{ +} + +void ScrollingMomentumCalculator::setRetargetedScrollOffset(const FloatSize& target) +{ + if (m_retargetedScrollOffset && m_retargetedScrollOffset == target) + return; + + m_retargetedScrollOffset = target; + retargetedScrollOffsetDidChange(); +} + +FloatSize ScrollingMomentumCalculator::predictedDestinationOffset() +{ + float initialOffsetX = clampTo<float>(m_initialScrollOffset.width() + projectedInertialScrollDistance(m_initialDelta.width()), 0, m_contentSize.width() - m_viewportSize.width()); + float initialOffsetY = clampTo<float>(m_initialScrollOffset.height() + projectedInertialScrollDistance(m_initialDelta.height()), 0, m_contentSize.height() - m_viewportSize.height()); + return { initialOffsetX, initialOffsetY }; +} + +#if !HAVE(NSSCROLLING_FILTERS) + +std::unique_ptr<ScrollingMomentumCalculator> ScrollingMomentumCalculator::create(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity) +{ + return std::make_unique<BasicScrollingMomentumCalculator>(viewportSize, contentSize, initialOffset, initialDelta, initialVelocity); +} + +void ScrollingMomentumCalculator::setPlatformMomentumScrollingPredictionEnabled(bool) +{ +} + +#endif + +BasicScrollingMomentumCalculator::BasicScrollingMomentumCalculator(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity) + : ScrollingMomentumCalculator(viewportSize, contentSize, initialOffset, initialDelta, initialVelocity) +{ +} + +FloatSize BasicScrollingMomentumCalculator::linearlyInterpolatedOffsetAtProgress(float progress) +{ + return m_initialScrollOffset + progress * (retargetedScrollOffset() - m_initialScrollOffset); +} + +FloatSize BasicScrollingMomentumCalculator::cubicallyInterpolatedOffsetAtProgress(float progress) const +{ + ASSERT(!m_forceLinearAnimationCurve); + FloatSize interpolatedPoint; + for (int i = 0; i < 4; ++i) + interpolatedPoint += std::pow(progress, i) * m_snapAnimationCurveCoefficients[i]; + + return interpolatedPoint; +} + +FloatPoint BasicScrollingMomentumCalculator::scrollOffsetAfterElapsedTime(Seconds elapsedTime) +{ + if (m_momentumCalculatorRequiresInitialization) { + initializeSnapProgressCurve(); + initializeInterpolationCoefficientsIfNecessary(); + m_momentumCalculatorRequiresInitialization = false; + } + + float progress = animationProgressAfterElapsedTime(elapsedTime); + auto offsetAsSize = m_forceLinearAnimationCurve ? linearlyInterpolatedOffsetAtProgress(progress) : cubicallyInterpolatedOffsetAtProgress(progress); + return FloatPoint(offsetAsSize.width(), offsetAsSize.height()); +} + +Seconds BasicScrollingMomentumCalculator::animationDuration() +{ + return scrollSnapAnimationDuration; +} + +/** + * Computes and sets coefficients required for interpolated snapping when scrolling in 2 dimensions, given + * initial conditions (the initial and target vectors, along with the initial wheel delta as a vector). The + * path is a cubic Bezier curve of the form p(s) = INITIAL + (C_1 * s) + (C_2 * s^2) + (C_3 * s^3) where each + * C_i is a 2D vector and INITIAL is the vector representing the initial scroll offset. s is a real in the + * interval [0, 1] indicating the "progress" of the curve (i.e. how much of the curve has been traveled). + * + * The curve has 4 control points, the first and last of which are the initial and target points, respectively. + * The distances between adjacent control points are constrained to be the same, making the convex hull an + * isosceles trapezoid with 3 sides of equal length. Additionally, the vector from the first control point to + * the second points in the same direction as the initial scroll delta. These constraints ensure two properties: + * 1. The direction of the snap animation at s=0 will be equal to the direction of the initial scroll delta. + * 2. Points at regular intervals of s will be evenly spread out. + * + * If the initial scroll direction is orthogonal to or points in the opposite direction as the vector from the + * initial point to the target point, initialization returns early and sets the curve to animate directly to the + * snap point without cubic interpolation. + * + * FIXME: This should be refactored to use UnitBezier. + */ +void BasicScrollingMomentumCalculator::initializeInterpolationCoefficientsIfNecessary() +{ + m_forceLinearAnimationCurve = true; + float initialDeltaMagnitude = m_initialDelta.diagonalLength(); + if (initialDeltaMagnitude < 1) { + // The initial wheel delta is so insignificant that we're better off considering this to have the same effect as finishing a scroll gesture with no momentum. + // Thus, cubic interpolation isn't needed here. + return; + } + + FloatSize startToEndVector = retargetedScrollOffset() - m_initialScrollOffset; + float startToEndDistance = startToEndVector.diagonalLength(); + if (!startToEndDistance) { + // The start and end positions are the same, so we shouldn't try to interpolate a path. + return; + } + + float cosTheta = (m_initialDelta.width() * startToEndVector.width() + m_initialDelta.height() * startToEndVector.height()) / (initialDeltaMagnitude * startToEndDistance); + if (cosTheta <= 0) { + // It's possible that the user is not scrolling towards the target snap offset (for instance, scrolling against a corner when 2D scroll snapping). + // In this case, just let the scroll offset animate to the target without computing a cubic curve. + return; + } + + float sideLength = startToEndDistance / (2.0f * cosTheta + 1.0f); + FloatSize controlVector1 = m_initialScrollOffset + sideLength * m_initialDelta / initialDeltaMagnitude; + FloatSize controlVector2 = controlVector1 + (sideLength * startToEndVector / startToEndDistance); + m_snapAnimationCurveCoefficients[0] = m_initialScrollOffset; + m_snapAnimationCurveCoefficients[1] = 3 * (controlVector1 - m_initialScrollOffset); + m_snapAnimationCurveCoefficients[2] = 3 * (m_initialScrollOffset - 2 * controlVector1 + controlVector2); + m_snapAnimationCurveCoefficients[3] = 3 * (controlVector1 - controlVector2) - m_initialScrollOffset + retargetedScrollOffset(); + m_forceLinearAnimationCurve = false; +} + +static const float framesPerSecond = 60.0f; + +/** + * Computes and sets parameters required for tracking the progress of a snap animation curve, interpolated + * or linear. The progress curve s(t) maps time t to progress s; both variables are in the interval [0, 1]. + * The time input t is 0 when the current time is the start of the animation, t = 0, and 1 when the current + * time is at or after the end of the animation, t = m_scrollSnapAnimationDuration. + * + * In this exponential progress model, s(t) = A - A * b^(-kt), where k = 60T is the number of frames in the + * animation (assuming 60 FPS and an animation duration of T) and A, b are reals greater than or equal to 1. + * Also note that we are given the initial progress, a value indicating the portion of the curve which our + * initial scroll delta takes us. This is important when matching the initial speed of the animation to the + * user's initial momentum scrolling speed. Let this initial progress amount equal v_0. I clamp this initial + * progress amount to a minimum or maximum value. + * + * A is referred to as the curve magnitude, while b is referred to as the decay factor. We solve for A and b, + * keeping the following constraints in mind: + * 1. s(0) = 0 + * 2. s(1) = 1 + * 3. s(1/k) = v_0 + * + * First, observe that s(0) = 0 holds for appropriate values of A, b. Solving for the remaining constraints + * yields a nonlinear system of two equations. In lieu of a purely analytical solution, an alternating + * optimization scheme is used to approximate A and b. This technique converges quickly (within 5 iterations + * or so) for appropriate values of v_0. The optimization terminates early when the decay factor changes by + * less than a threshold between one iteration and the next. + */ +void BasicScrollingMomentumCalculator::initializeSnapProgressCurve() +{ + static const int maxNumScrollSnapParameterEstimationIterations = 10; + static const float scrollSnapDecayFactorConvergenceThreshold = 0.001; + static const float initialScrollSnapCurveMagnitude = 1.1; + static const float minScrollSnapInitialProgress = 0.1; + static const float maxScrollSnapInitialProgress = 0.5; + + FloatSize alignmentVector = m_initialDelta * (retargetedScrollOffset() - m_initialScrollOffset); + float initialProgress; + if (alignmentVector.width() + alignmentVector.height() > 0) + initialProgress = clampTo(m_initialDelta.diagonalLength() / (retargetedScrollOffset() - m_initialScrollOffset).diagonalLength(), minScrollSnapInitialProgress, maxScrollSnapInitialProgress); + else + initialProgress = minScrollSnapInitialProgress; + + float previousDecayFactor = 1.0f; + m_snapAnimationCurveMagnitude = initialScrollSnapCurveMagnitude; + for (int i = 0; i < maxNumScrollSnapParameterEstimationIterations; ++i) { + m_snapAnimationDecayFactor = m_snapAnimationCurveMagnitude / (m_snapAnimationCurveMagnitude - initialProgress); + m_snapAnimationCurveMagnitude = 1.0f / (1.0f - std::pow(m_snapAnimationDecayFactor, -framesPerSecond * scrollSnapAnimationDuration.value())); + if (std::abs(m_snapAnimationDecayFactor - previousDecayFactor) < scrollSnapDecayFactorConvergenceThreshold) + break; + + previousDecayFactor = m_snapAnimationDecayFactor; + } +} + +float BasicScrollingMomentumCalculator::animationProgressAfterElapsedTime(Seconds elapsedTime) const +{ + float timeProgress = clampTo<float>(elapsedTime / scrollSnapAnimationDuration, 0, 1); + return std::min(1.0, m_snapAnimationCurveMagnitude * (1.0 - std::pow(m_snapAnimationDecayFactor, -framesPerSecond * scrollSnapAnimationDuration.value() * timeProgress))); +} + +}; // namespace WebCore diff --git a/Source/WebCore/page/scrolling/ScrollingMomentumCalculator.h b/Source/WebCore/page/scrolling/ScrollingMomentumCalculator.h new file mode 100644 index 000000000..a82afaa66 --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollingMomentumCalculator.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(CSS_SCROLL_SNAP) + +#include "AxisScrollSnapOffsets.h" +#include "PlatformWheelEvent.h" +#include "ScrollTypes.h" +#include <wtf/Optional.h> +#include <wtf/Seconds.h> + +namespace WebCore { + +class FloatPoint; +class FloatSize; + +class ScrollingMomentumCalculator { +public: + ScrollingMomentumCalculator(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity); + static std::unique_ptr<ScrollingMomentumCalculator> create(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity); + WEBCORE_EXPORT static void setPlatformMomentumScrollingPredictionEnabled(bool); + virtual ~ScrollingMomentumCalculator() { } + + virtual FloatPoint scrollOffsetAfterElapsedTime(Seconds) = 0; + virtual Seconds animationDuration() = 0; + virtual FloatSize predictedDestinationOffset(); + void setRetargetedScrollOffset(const FloatSize&); + +protected: + const FloatSize& retargetedScrollOffset() const { return m_retargetedScrollOffset.value(); } + virtual void retargetedScrollOffsetDidChange() { } + + FloatSize m_initialDelta; + FloatSize m_initialVelocity; + FloatSize m_initialScrollOffset; + FloatSize m_viewportSize; + FloatSize m_contentSize; + +private: + std::optional<FloatSize> m_retargetedScrollOffset; +}; + +class BasicScrollingMomentumCalculator final : public ScrollingMomentumCalculator { +public: + BasicScrollingMomentumCalculator(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity); + +private: + FloatPoint scrollOffsetAfterElapsedTime(Seconds) final; + Seconds animationDuration() final; + void initializeInterpolationCoefficientsIfNecessary(); + void initializeSnapProgressCurve(); + float animationProgressAfterElapsedTime(Seconds) const; + FloatSize linearlyInterpolatedOffsetAtProgress(float progress); + FloatSize cubicallyInterpolatedOffsetAtProgress(float progress) const; + + float m_snapAnimationCurveMagnitude { 0 }; + float m_snapAnimationDecayFactor { 0 }; + FloatSize m_snapAnimationCurveCoefficients[4] { }; + bool m_forceLinearAnimationCurve { false }; + bool m_momentumCalculatorRequiresInitialization { true }; + std::optional<FloatSize> m_predictedDestinationOffset; +}; + +} // namespace WebCore + +#endif // ENABLE(CSS_SCROLL_SNAP) diff --git a/Source/WebCore/page/scrolling/ScrollingStateFixedNode.cpp b/Source/WebCore/page/scrolling/ScrollingStateFixedNode.cpp index cce42eb7e..17d016597 100644 --- a/Source/WebCore/page/scrolling/ScrollingStateFixedNode.cpp +++ b/Source/WebCore/page/scrolling/ScrollingStateFixedNode.cpp @@ -27,17 +27,17 @@ #include "ScrollingStateFixedNode.h" #include "GraphicsLayer.h" +#include "Logging.h" #include "ScrollingStateTree.h" #include "TextStream.h" -#include <wtf/OwnPtr.h> #if ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) namespace WebCore { -PassOwnPtr<ScrollingStateFixedNode> ScrollingStateFixedNode::create(ScrollingStateTree& stateTree, ScrollingNodeID nodeID) +Ref<ScrollingStateFixedNode> ScrollingStateFixedNode::create(ScrollingStateTree& stateTree, ScrollingNodeID nodeID) { - return adoptPtr(new ScrollingStateFixedNode(stateTree, nodeID)); + return adoptRef(*new ScrollingStateFixedNode(stateTree, nodeID)); } ScrollingStateFixedNode::ScrollingStateFixedNode(ScrollingStateTree& tree, ScrollingNodeID nodeID) @@ -55,9 +55,9 @@ ScrollingStateFixedNode::~ScrollingStateFixedNode() { } -PassOwnPtr<ScrollingStateNode> ScrollingStateFixedNode::clone(ScrollingStateTree& adoptiveTree) +Ref<ScrollingStateNode> ScrollingStateFixedNode::clone(ScrollingStateTree& adoptiveTree) { - return adoptPtr(new ScrollingStateFixedNode(*this, adoptiveTree)); + return adoptRef(*new ScrollingStateFixedNode(*this, adoptiveTree)); } void ScrollingStateFixedNode::updateConstraints(const FixedPositionViewportConstraints& constraints) @@ -69,14 +69,31 @@ void ScrollingStateFixedNode::updateConstraints(const FixedPositionViewportConst setPropertyChanged(ViewportConstraints); } -void ScrollingStateFixedNode::syncLayerPositionForViewportRect(const LayoutRect& viewportRect) +void ScrollingStateFixedNode::reconcileLayerPositionForViewportRect(const LayoutRect& viewportRect, ScrollingLayerPositionAction action) { FloatPoint position = m_constraints.layerPositionForViewportRect(viewportRect); - if (layer().representsGraphicsLayer()) - static_cast<GraphicsLayer*>(layer())->syncPosition(position); + if (layer().representsGraphicsLayer()) { + GraphicsLayer* graphicsLayer = static_cast<GraphicsLayer*>(layer()); + + LOG_WITH_STREAM(Compositing, stream << "ScrollingStateFixedNode::reconcileLayerPositionForViewportRect setting position of layer " << graphicsLayer->primaryLayerID() << " to " << position); + + switch (action) { + case ScrollingLayerPositionAction::Set: + graphicsLayer->setPosition(position); + break; + + case ScrollingLayerPositionAction::SetApproximate: + graphicsLayer->setApproximatePosition(position); + break; + + case ScrollingLayerPositionAction::Sync: + graphicsLayer->syncPosition(position); + break; + } + } } -void ScrollingStateFixedNode::dumpProperties(TextStream& ts, int indent) const +void ScrollingStateFixedNode::dumpProperties(TextStream& ts, int indent, ScrollingStateTreeAsTextBehavior) const { ts << "(" << "Fixed node" << "\n"; diff --git a/Source/WebCore/page/scrolling/ScrollingStateFixedNode.h b/Source/WebCore/page/scrolling/ScrollingStateFixedNode.h index a52d7b6d0..e761b6a73 100644 --- a/Source/WebCore/page/scrolling/ScrollingStateFixedNode.h +++ b/Source/WebCore/page/scrolling/ScrollingStateFixedNode.h @@ -23,8 +23,7 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef ScrollingStateFixedNode_h -#define ScrollingStateFixedNode_h +#pragma once #if ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) @@ -39,9 +38,9 @@ class FixedPositionViewportConstraints; class ScrollingStateFixedNode final : public ScrollingStateNode { public: - static PassOwnPtr<ScrollingStateFixedNode> create(ScrollingStateTree&, ScrollingNodeID); + static Ref<ScrollingStateFixedNode> create(ScrollingStateTree&, ScrollingNodeID); - virtual PassOwnPtr<ScrollingStateNode> clone(ScrollingStateTree&); + Ref<ScrollingStateNode> clone(ScrollingStateTree&) override; virtual ~ScrollingStateFixedNode(); @@ -49,24 +48,22 @@ public: ViewportConstraints = NumStateNodeBits }; - void updateConstraints(const FixedPositionViewportConstraints&); + WEBCORE_EXPORT void updateConstraints(const FixedPositionViewportConstraints&); const FixedPositionViewportConstraints& viewportConstraints() const { return m_constraints; } private: ScrollingStateFixedNode(ScrollingStateTree&, ScrollingNodeID); ScrollingStateFixedNode(const ScrollingStateFixedNode&, ScrollingStateTree&); - virtual void syncLayerPositionForViewportRect(const LayoutRect& viewportRect) override; + void reconcileLayerPositionForViewportRect(const LayoutRect& viewportRect, ScrollingLayerPositionAction) override; - virtual void dumpProperties(TextStream&, int indent) const override; + void dumpProperties(TextStream&, int indent, ScrollingStateTreeAsTextBehavior) const override; FixedPositionViewportConstraints m_constraints; }; -SCROLLING_STATE_NODE_TYPE_CASTS(ScrollingStateFixedNode, nodeType() == FixedNode); - } // namespace WebCore -#endif // ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) +SPECIALIZE_TYPE_TRAITS_SCROLLING_STATE_NODE(ScrollingStateFixedNode, isFixedNode()) -#endif // ScrollingStateFixedNode_h +#endif // ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) diff --git a/Source/WebCore/page/scrolling/ScrollingStateFrameScrollingNode.cpp b/Source/WebCore/page/scrolling/ScrollingStateFrameScrollingNode.cpp new file mode 100644 index 000000000..6313c1385 --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollingStateFrameScrollingNode.cpp @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2014, 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ScrollingStateFrameScrollingNode.h" + +#if ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) + +#include "ScrollingStateTree.h" +#include "TextStream.h" + +namespace WebCore { + +Ref<ScrollingStateFrameScrollingNode> ScrollingStateFrameScrollingNode::create(ScrollingStateTree& stateTree, ScrollingNodeID nodeID) +{ + return adoptRef(*new ScrollingStateFrameScrollingNode(stateTree, nodeID)); +} + +ScrollingStateFrameScrollingNode::ScrollingStateFrameScrollingNode(ScrollingStateTree& stateTree, ScrollingNodeID nodeID) + : ScrollingStateScrollingNode(stateTree, FrameScrollingNode, nodeID) +{ +} + +ScrollingStateFrameScrollingNode::ScrollingStateFrameScrollingNode(const ScrollingStateFrameScrollingNode& stateNode, ScrollingStateTree& adoptiveTree) + : ScrollingStateScrollingNode(stateNode, adoptiveTree) +#if PLATFORM(MAC) + , m_verticalScrollerImp(stateNode.verticalScrollerImp()) + , m_horizontalScrollerImp(stateNode.horizontalScrollerImp()) +#endif + , m_eventTrackingRegions(stateNode.eventTrackingRegions()) + , m_requestedScrollPosition(stateNode.requestedScrollPosition()) + , m_layoutViewport(stateNode.layoutViewport()) + , m_minLayoutViewportOrigin(stateNode.minLayoutViewportOrigin()) + , m_maxLayoutViewportOrigin(stateNode.maxLayoutViewportOrigin()) + , m_frameScaleFactor(stateNode.frameScaleFactor()) + , m_topContentInset(stateNode.topContentInset()) + , m_headerHeight(stateNode.headerHeight()) + , m_footerHeight(stateNode.footerHeight()) + , m_synchronousScrollingReasons(stateNode.synchronousScrollingReasons()) + , m_behaviorForFixed(stateNode.scrollBehaviorForFixedElements()) + , m_requestedScrollPositionRepresentsProgrammaticScroll(stateNode.requestedScrollPositionRepresentsProgrammaticScroll()) + , m_fixedElementsLayoutRelativeToFrame(stateNode.fixedElementsLayoutRelativeToFrame()) + , m_visualViewportEnabled(stateNode.visualViewportEnabled()) +{ + if (hasChangedProperty(ScrolledContentsLayer)) + setScrolledContentsLayer(stateNode.scrolledContentsLayer().toRepresentation(adoptiveTree.preferredLayerRepresentation())); + + if (hasChangedProperty(CounterScrollingLayer)) + setCounterScrollingLayer(stateNode.counterScrollingLayer().toRepresentation(adoptiveTree.preferredLayerRepresentation())); + + if (hasChangedProperty(InsetClipLayer)) + setInsetClipLayer(stateNode.insetClipLayer().toRepresentation(adoptiveTree.preferredLayerRepresentation())); + + if (hasChangedProperty(ContentShadowLayer)) + setContentShadowLayer(stateNode.contentShadowLayer().toRepresentation(adoptiveTree.preferredLayerRepresentation())); + + if (hasChangedProperty(HeaderLayer)) + setHeaderLayer(stateNode.headerLayer().toRepresentation(adoptiveTree.preferredLayerRepresentation())); + + if (hasChangedProperty(FooterLayer)) + setFooterLayer(stateNode.footerLayer().toRepresentation(adoptiveTree.preferredLayerRepresentation())); +} + +ScrollingStateFrameScrollingNode::~ScrollingStateFrameScrollingNode() +{ +} + +Ref<ScrollingStateNode> ScrollingStateFrameScrollingNode::clone(ScrollingStateTree& adoptiveTree) +{ + return adoptRef(*new ScrollingStateFrameScrollingNode(*this, adoptiveTree)); +} + +void ScrollingStateFrameScrollingNode::setFrameScaleFactor(float scaleFactor) +{ + if (m_frameScaleFactor == scaleFactor) + return; + + m_frameScaleFactor = scaleFactor; + + setPropertyChanged(FrameScaleFactor); +} + +void ScrollingStateFrameScrollingNode::setEventTrackingRegions(const EventTrackingRegions& eventTrackingRegions) +{ + if (m_eventTrackingRegions == eventTrackingRegions) + return; + + m_eventTrackingRegions = eventTrackingRegions; + setPropertyChanged(EventTrackingRegion); +} + +void ScrollingStateFrameScrollingNode::setSynchronousScrollingReasons(SynchronousScrollingReasons reasons) +{ + if (m_synchronousScrollingReasons == reasons) + return; + + m_synchronousScrollingReasons = reasons; + setPropertyChanged(ReasonsForSynchronousScrolling); +} + +void ScrollingStateFrameScrollingNode::setScrollBehaviorForFixedElements(ScrollBehaviorForFixedElements behaviorForFixed) +{ + if (m_behaviorForFixed == behaviorForFixed) + return; + + m_behaviorForFixed = behaviorForFixed; + setPropertyChanged(BehaviorForFixedElements); +} + +void ScrollingStateFrameScrollingNode::setLayoutViewport(const FloatRect& r) +{ + if (m_layoutViewport == r) + return; + + m_layoutViewport = r; + setPropertyChanged(LayoutViewport); +} + +void ScrollingStateFrameScrollingNode::setMinLayoutViewportOrigin(const FloatPoint& p) +{ + if (m_minLayoutViewportOrigin == p) + return; + + m_minLayoutViewportOrigin = p; + setPropertyChanged(MinLayoutViewportOrigin); +} + +void ScrollingStateFrameScrollingNode::setMaxLayoutViewportOrigin(const FloatPoint& p) +{ + if (m_maxLayoutViewportOrigin == p) + return; + + m_maxLayoutViewportOrigin = p; + setPropertyChanged(MaxLayoutViewportOrigin); +} + +void ScrollingStateFrameScrollingNode::setHeaderHeight(int headerHeight) +{ + if (m_headerHeight == headerHeight) + return; + + m_headerHeight = headerHeight; + setPropertyChanged(HeaderHeight); +} + +void ScrollingStateFrameScrollingNode::setFooterHeight(int footerHeight) +{ + if (m_footerHeight == footerHeight) + return; + + m_footerHeight = footerHeight; + setPropertyChanged(FooterHeight); +} + +void ScrollingStateFrameScrollingNode::setTopContentInset(float topContentInset) +{ + if (m_topContentInset == topContentInset) + return; + + m_topContentInset = topContentInset; + setPropertyChanged(TopContentInset); +} + +void ScrollingStateFrameScrollingNode::setScrolledContentsLayer(const LayerRepresentation& layerRepresentation) +{ + if (layerRepresentation == m_scrolledContentsLayer) + return; + + m_scrolledContentsLayer = layerRepresentation; + setPropertyChanged(ScrolledContentsLayer); +} + +void ScrollingStateFrameScrollingNode::setCounterScrollingLayer(const LayerRepresentation& layerRepresentation) +{ + if (layerRepresentation == m_counterScrollingLayer) + return; + + m_counterScrollingLayer = layerRepresentation; + setPropertyChanged(CounterScrollingLayer); +} + +void ScrollingStateFrameScrollingNode::setInsetClipLayer(const LayerRepresentation& layerRepresentation) +{ + if (layerRepresentation == m_insetClipLayer) + return; + + m_insetClipLayer = layerRepresentation; + setPropertyChanged(InsetClipLayer); +} + +void ScrollingStateFrameScrollingNode::setContentShadowLayer(const LayerRepresentation& layerRepresentation) +{ + if (layerRepresentation == m_contentShadowLayer) + return; + + m_contentShadowLayer = layerRepresentation; + setPropertyChanged(ContentShadowLayer); +} + +void ScrollingStateFrameScrollingNode::setHeaderLayer(const LayerRepresentation& layerRepresentation) +{ + if (layerRepresentation == m_headerLayer) + return; + + m_headerLayer = layerRepresentation; + setPropertyChanged(HeaderLayer); +} + +void ScrollingStateFrameScrollingNode::setFooterLayer(const LayerRepresentation& layerRepresentation) +{ + if (layerRepresentation == m_footerLayer) + return; + + m_footerLayer = layerRepresentation; + setPropertyChanged(FooterLayer); +} + +void ScrollingStateFrameScrollingNode::setFixedElementsLayoutRelativeToFrame(bool fixedElementsLayoutRelativeToFrame) +{ + if (fixedElementsLayoutRelativeToFrame == m_fixedElementsLayoutRelativeToFrame) + return; + + m_fixedElementsLayoutRelativeToFrame = fixedElementsLayoutRelativeToFrame; + setPropertyChanged(FixedElementsLayoutRelativeToFrame); +} + +// Only needed while visual viewports are runtime-switchable. +void ScrollingStateFrameScrollingNode::setVisualViewportEnabled(bool visualViewportEnabled) +{ + if (visualViewportEnabled == m_visualViewportEnabled) + return; + + m_visualViewportEnabled = visualViewportEnabled; + setPropertyChanged(VisualViewportEnabled); +} + +#if !PLATFORM(MAC) +void ScrollingStateFrameScrollingNode::setScrollerImpsFromScrollbars(Scrollbar*, Scrollbar*) +{ +} +#endif + +void ScrollingStateFrameScrollingNode::dumpProperties(TextStream& ts, int indent, ScrollingStateTreeAsTextBehavior behavior) const +{ + ts << "(Frame scrolling node" << "\n"; + + ScrollingStateScrollingNode::dumpProperties(ts, indent, behavior); + + if (m_frameScaleFactor != 1) { + writeIndent(ts, indent + 1); + ts << "(frame scale factor " << m_frameScaleFactor << ")\n"; + } + + if (m_visualViewportEnabled) { + writeIndent(ts, indent + 1); + ts << "(layout viewport " << m_layoutViewport << ")\n"; + writeIndent(ts, indent + 1); + ts << "(min layout viewport origin " << m_minLayoutViewportOrigin << ")\n"; + writeIndent(ts, indent + 1); + ts << "(max layout viewport origin " << m_maxLayoutViewportOrigin << ")\n"; + } + + if (m_behaviorForFixed == StickToViewportBounds) { + writeIndent(ts, indent + 1); + ts << "(fixed behavior: stick to viewport)\n"; + } + + if (!m_eventTrackingRegions.asynchronousDispatchRegion.isEmpty()) { + ++indent; + writeIndent(ts, indent); + ts << "(asynchronous event dispatch region"; + ++indent; + for (auto rect : m_eventTrackingRegions.asynchronousDispatchRegion.rects()) { + ts << "\n"; + writeIndent(ts, indent); + ts << rect; + } + ts << ")\n"; + indent -= 2; + } + if (!m_eventTrackingRegions.eventSpecificSynchronousDispatchRegions.isEmpty()) { + for (const auto& synchronousEventRegion : m_eventTrackingRegions.eventSpecificSynchronousDispatchRegions) { + ++indent; + writeIndent(ts, indent); + ts << "(synchronous event dispatch region for event " << synchronousEventRegion.key; + ++indent; + for (auto rect : synchronousEventRegion.value.rects()) { + ts << "\n"; + writeIndent(ts, indent); + ts << rect; + } + ts << ")\n"; + indent -= 2; + } + } + + if (m_synchronousScrollingReasons) { + writeIndent(ts, indent + 1); + ts << "(Scrolling on main thread because: " << ScrollingCoordinator::synchronousScrollingReasonsAsText(m_synchronousScrollingReasons) << ")\n"; + } + + // FIXME: dump more properties. +} + +} // namespace WebCore + +#endif // ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) diff --git a/Source/WebCore/page/scrolling/ScrollingStateFrameScrollingNode.h b/Source/WebCore/page/scrolling/ScrollingStateFrameScrollingNode.h new file mode 100644 index 000000000..eeaa71fe3 --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollingStateFrameScrollingNode.h @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2014, 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) + +#include "EventTrackingRegions.h" +#include "ScrollTypes.h" +#include "ScrollbarThemeComposite.h" +#include "ScrollingCoordinator.h" +#include "ScrollingStateScrollingNode.h" + +namespace WebCore { + +class Scrollbar; + +class ScrollingStateFrameScrollingNode final : public ScrollingStateScrollingNode { +public: + static Ref<ScrollingStateFrameScrollingNode> create(ScrollingStateTree&, ScrollingNodeID); + + Ref<ScrollingStateNode> clone(ScrollingStateTree&) override; + + virtual ~ScrollingStateFrameScrollingNode(); + + enum ChangedProperty { + FrameScaleFactor = NumScrollingStateNodeBits, + EventTrackingRegion, + ReasonsForSynchronousScrolling, + ScrolledContentsLayer, + CounterScrollingLayer, + InsetClipLayer, + ContentShadowLayer, + HeaderHeight, + FooterHeight, + HeaderLayer, + FooterLayer, + PainterForScrollbar, + BehaviorForFixedElements, + TopContentInset, + FixedElementsLayoutRelativeToFrame, + VisualViewportEnabled, + LayoutViewport, + MinLayoutViewportOrigin, + MaxLayoutViewportOrigin, + }; + + float frameScaleFactor() const { return m_frameScaleFactor; } + WEBCORE_EXPORT void setFrameScaleFactor(float); + + const EventTrackingRegions& eventTrackingRegions() const { return m_eventTrackingRegions; } + WEBCORE_EXPORT void setEventTrackingRegions(const EventTrackingRegions&); + + SynchronousScrollingReasons synchronousScrollingReasons() const { return m_synchronousScrollingReasons; } + WEBCORE_EXPORT void setSynchronousScrollingReasons(SynchronousScrollingReasons); + + ScrollBehaviorForFixedElements scrollBehaviorForFixedElements() const { return m_behaviorForFixed; } + WEBCORE_EXPORT void setScrollBehaviorForFixedElements(ScrollBehaviorForFixedElements); + + FloatRect layoutViewport() const { return m_layoutViewport; }; + WEBCORE_EXPORT void setLayoutViewport(const FloatRect&); + + FloatPoint minLayoutViewportOrigin() const { return m_minLayoutViewportOrigin; } + WEBCORE_EXPORT void setMinLayoutViewportOrigin(const FloatPoint&); + + FloatPoint maxLayoutViewportOrigin() const { return m_maxLayoutViewportOrigin; } + WEBCORE_EXPORT void setMaxLayoutViewportOrigin(const FloatPoint&); + + int headerHeight() const { return m_headerHeight; } + WEBCORE_EXPORT void setHeaderHeight(int); + + int footerHeight() const { return m_footerHeight; } + WEBCORE_EXPORT void setFooterHeight(int); + + float topContentInset() const { return m_topContentInset; } + WEBCORE_EXPORT void setTopContentInset(float); + + const LayerRepresentation& scrolledContentsLayer() const { return m_scrolledContentsLayer; } + WEBCORE_EXPORT void setScrolledContentsLayer(const LayerRepresentation&); + + // This is a layer moved in the opposite direction to scrolling, for example for background-attachment:fixed + const LayerRepresentation& counterScrollingLayer() const { return m_counterScrollingLayer; } + WEBCORE_EXPORT void setCounterScrollingLayer(const LayerRepresentation&); + + // This is a clipping layer that will scroll with the page for all y-delta scroll values between 0 + // and topContentInset(). Once the y-deltas get beyond the content inset point, this layer no longer + // needs to move. If the topContentInset() is 0, this layer does not need to move at all. This is + // only used on the Mac. + const LayerRepresentation& insetClipLayer() const { return m_insetClipLayer; } + WEBCORE_EXPORT void setInsetClipLayer(const LayerRepresentation&); + + const LayerRepresentation& contentShadowLayer() const { return m_contentShadowLayer; } + WEBCORE_EXPORT void setContentShadowLayer(const LayerRepresentation&); + + // The header and footer layers scroll vertically with the page, they should remain fixed when scrolling horizontally. + const LayerRepresentation& headerLayer() const { return m_headerLayer; } + WEBCORE_EXPORT void setHeaderLayer(const LayerRepresentation&); + + // The header and footer layers scroll vertically with the page, they should remain fixed when scrolling horizontally. + const LayerRepresentation& footerLayer() const { return m_footerLayer; } + WEBCORE_EXPORT void setFooterLayer(const LayerRepresentation&); + + bool fixedElementsLayoutRelativeToFrame() const { return m_fixedElementsLayoutRelativeToFrame; } + WEBCORE_EXPORT void setFixedElementsLayoutRelativeToFrame(bool); + + bool visualViewportEnabled() const { return m_visualViewportEnabled; }; + WEBCORE_EXPORT void setVisualViewportEnabled(bool); + +#if PLATFORM(MAC) + NSScrollerImp *verticalScrollerImp() const { return m_verticalScrollerImp.get(); } + NSScrollerImp *horizontalScrollerImp() const { return m_horizontalScrollerImp.get(); } +#endif + void setScrollerImpsFromScrollbars(Scrollbar* verticalScrollbar, Scrollbar* horizontalScrollbar); + + void dumpProperties(TextStream&, int indent, ScrollingStateTreeAsTextBehavior) const override; + +private: + ScrollingStateFrameScrollingNode(ScrollingStateTree&, ScrollingNodeID); + ScrollingStateFrameScrollingNode(const ScrollingStateFrameScrollingNode&, ScrollingStateTree&); + + LayerRepresentation m_counterScrollingLayer; + LayerRepresentation m_insetClipLayer; + LayerRepresentation m_scrolledContentsLayer; + LayerRepresentation m_contentShadowLayer; + LayerRepresentation m_headerLayer; + LayerRepresentation m_footerLayer; + +#if PLATFORM(MAC) + RetainPtr<NSScrollerImp> m_verticalScrollerImp; + RetainPtr<NSScrollerImp> m_horizontalScrollerImp; +#endif + + EventTrackingRegions m_eventTrackingRegions; + FloatPoint m_requestedScrollPosition; + + FloatRect m_layoutViewport; + FloatPoint m_minLayoutViewportOrigin; + FloatPoint m_maxLayoutViewportOrigin; + + float m_frameScaleFactor { 1 }; + float m_topContentInset { 0 }; + int m_headerHeight { 0 }; + int m_footerHeight { 0 }; + SynchronousScrollingReasons m_synchronousScrollingReasons { 0 }; + ScrollBehaviorForFixedElements m_behaviorForFixed { StickToDocumentBounds }; + bool m_requestedScrollPositionRepresentsProgrammaticScroll { false }; + bool m_fixedElementsLayoutRelativeToFrame { false }; + bool m_visualViewportEnabled { false }; +}; + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_SCROLLING_STATE_NODE(ScrollingStateFrameScrollingNode, isFrameScrollingNode()) + +#endif // ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) diff --git a/Source/WebCore/page/scrolling/ScrollingStateNode.cpp b/Source/WebCore/page/scrolling/ScrollingStateNode.cpp index 0b60c5acb..61da58a9d 100644 --- a/Source/WebCore/page/scrolling/ScrollingStateNode.cpp +++ b/Source/WebCore/page/scrolling/ScrollingStateNode.cpp @@ -41,7 +41,7 @@ ScrollingStateNode::ScrollingStateNode(ScrollingNodeType nodeType, ScrollingStat , m_nodeID(nodeID) , m_changedProperties(0) , m_scrollingStateTree(scrollingStateTree) - , m_parent(0) + , m_parent(nullptr) { } @@ -52,7 +52,7 @@ ScrollingStateNode::ScrollingStateNode(const ScrollingStateNode& stateNode, Scro , m_nodeID(stateNode.scrollingNodeID()) , m_changedProperties(stateNode.changedProperties()) , m_scrollingStateTree(adoptiveTree) - , m_parent(0) + , m_parent(nullptr) { if (hasChangedProperty(ScrollLayer)) setLayer(stateNode.layer().toRepresentation(adoptiveTree.preferredLayerRepresentation())); @@ -72,15 +72,16 @@ void ScrollingStateNode::setPropertyChanged(unsigned propertyBit) m_scrollingStateTree.setHasChangedProperties(); } -PassOwnPtr<ScrollingStateNode> ScrollingStateNode::cloneAndReset(ScrollingStateTree& adoptiveTree) +Ref<ScrollingStateNode> ScrollingStateNode::cloneAndReset(ScrollingStateTree& adoptiveTree) { - OwnPtr<ScrollingStateNode> clone = this->clone(adoptiveTree); + auto clone = this->clone(adoptiveTree); // Now that this node is cloned, reset our change properties. resetChangedProperties(); - cloneAndResetChildren(*clone, adoptiveTree); - return clone.release(); + cloneAndResetChildren(clone.get(), adoptiveTree); + + return clone; } void ScrollingStateNode::cloneAndResetChildren(ScrollingStateNode& clone, ScrollingStateTree& adoptiveTree) @@ -88,51 +89,17 @@ void ScrollingStateNode::cloneAndResetChildren(ScrollingStateNode& clone, Scroll if (!m_children) return; - size_t size = m_children->size(); - for (size_t i = 0; i < size; ++i) - clone.appendChild(m_children->at(i)->cloneAndReset(adoptiveTree)); + for (auto& child : *m_children) + clone.appendChild(child->cloneAndReset(adoptiveTree)); } -void ScrollingStateNode::appendChild(PassOwnPtr<ScrollingStateNode> childNode) +void ScrollingStateNode::appendChild(Ref<ScrollingStateNode>&& childNode) { childNode->setParent(this); if (!m_children) - m_children = adoptPtr(new Vector<OwnPtr<ScrollingStateNode>>); - - m_children->append(childNode); -} - -void ScrollingStateNode::removeChild(ScrollingStateNode* node) -{ - if (!m_children) - return; - - size_t index = m_children->find(node); - - // The index will be notFound if the node to remove is a deeper-than-1-level descendant or - // if node is the root state node. - if (index != notFound) { - node->willBeRemovedFromStateTree(); - m_children->remove(index); - return; - } - - size_t size = m_children->size(); - for (size_t i = 0; i < size; ++i) - m_children->at(i)->removeChild(node); -} - -void ScrollingStateNode::willBeRemovedFromStateTree() -{ - scrollingStateTree().didRemoveNode(scrollingNodeID()); - - if (!m_children) - return; - - size_t size = m_children->size(); - for (size_t i = 0; i < size; ++i) - m_children->at(i)->willBeRemovedFromStateTree(); + m_children = std::make_unique<Vector<RefPtr<ScrollingStateNode>>>(); + m_children->append(WTFMove(childNode)); } void ScrollingStateNode::setLayer(const LayerRepresentation& layerRepresentation) @@ -145,18 +112,17 @@ void ScrollingStateNode::setLayer(const LayerRepresentation& layerRepresentation setPropertyChanged(ScrollLayer); } -void ScrollingStateNode::dump(TextStream& ts, int indent) const +void ScrollingStateNode::dump(TextStream& ts, int indent, ScrollingStateTreeAsTextBehavior behavior) const { writeIndent(ts, indent); - dumpProperties(ts, indent); + dumpProperties(ts, indent, behavior); if (m_children) { writeIndent(ts, indent + 1); - size_t size = children()->size(); - ts << "(children " << size << "\n"; + ts << "(children " << children()->size() << "\n"; - for (size_t i = 0; i < size; i++) - m_children->at(i)->dump(ts, indent + 2); + for (auto& child : *m_children) + child->dump(ts, indent + 2, behavior); writeIndent(ts, indent + 1); ts << ")\n"; } @@ -167,9 +133,9 @@ void ScrollingStateNode::dump(TextStream& ts, int indent) const String ScrollingStateNode::scrollingStateTreeAsText() const { - TextStream ts; + TextStream ts(TextStream::LineMode::MultipleLine, TextStream::Formatting::SVGStyleRect); - dump(ts, 0); + dump(ts, 0, ScrollingStateTreeAsTextBehaviorNormal); return ts.release(); } diff --git a/Source/WebCore/page/scrolling/ScrollingStateNode.h b/Source/WebCore/page/scrolling/ScrollingStateNode.h index ca705f3e2..087d61829 100644 --- a/Source/WebCore/page/scrolling/ScrollingStateNode.h +++ b/Source/WebCore/page/scrolling/ScrollingStateNode.h @@ -23,27 +23,31 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef ScrollingStateNode_h -#define ScrollingStateNode_h +#pragma once #if ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) #include "GraphicsLayer.h" #include "ScrollingCoordinator.h" -#include <wtf/OwnPtr.h> -#include <wtf/PassOwnPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/TypeCasts.h> #include <wtf/Vector.h> -#if PLATFORM(MAC) -#include <wtf/RetainPtr.h> -#endif - namespace WebCore { class GraphicsLayer; class ScrollingStateTree; class TextStream; +enum ScrollingStateTreeAsTextBehaviorFlags { + ScrollingStateTreeAsTextBehaviorNormal = 0, + ScrollingStateTreeAsTextBehaviorIncludeLayerIDs = 1 << 0, + ScrollingStateTreeAsTextBehaviorIncludeNodeIDs = 1 << 1, + ScrollingStateTreeAsTextBehaviorIncludeLayerPositions = 1 << 2, + ScrollingStateTreeAsTextBehaviorDebug = ScrollingStateTreeAsTextBehaviorIncludeLayerIDs | ScrollingStateTreeAsTextBehaviorIncludeNodeIDs | ScrollingStateTreeAsTextBehaviorIncludeLayerPositions +}; +typedef unsigned ScrollingStateTreeAsTextBehavior; + // Used to allow ScrollingStateNodes to refer to layers in various contexts: // a) Async scrolling, main thread: ScrollingStateNode holds onto a GraphicsLayer, and uses m_layerID // to detect whether that GraphicsLayer's underlying PlatformLayer changed. @@ -75,14 +79,32 @@ public: : m_platformLayer(platformLayer) , m_layerID(0) , m_representation(PlatformLayerRepresentation) - { } + { + retainPlatformLayer(platformLayer); + } LayerRepresentation(GraphicsLayer::PlatformLayerID layerID) : m_graphicsLayer(nullptr) , m_layerID(layerID) , m_representation(PlatformLayerIDRepresentation) - { } - + { + } + + LayerRepresentation(const LayerRepresentation& other) + : m_platformLayer(other.m_platformLayer) + , m_layerID(other.m_layerID) + , m_representation(other.m_representation) + { + if (m_representation == PlatformLayerRepresentation) + retainPlatformLayer(m_platformLayer); + } + + ~LayerRepresentation() + { + if (m_representation == PlatformLayerRepresentation) + releasePlatformLayer(m_platformLayer); + } + operator GraphicsLayer*() const { ASSERT(m_representation == GraphicsLayerRepresentation); @@ -94,14 +116,31 @@ public: ASSERT(m_representation == PlatformLayerRepresentation); return m_platformLayer; } + + GraphicsLayer::PlatformLayerID layerID() const + { + return m_layerID; + } operator GraphicsLayer::PlatformLayerID() const { ASSERT(m_representation != PlatformLayerRepresentation); return m_layerID; } - - bool operator ==(const LayerRepresentation& other) const + + LayerRepresentation& operator=(const LayerRepresentation& other) + { + m_platformLayer = other.m_platformLayer; + m_layerID = other.m_layerID; + m_representation = other.m_representation; + + if (m_representation == PlatformLayerRepresentation) + retainPlatformLayer(m_platformLayer); + + return *this; + } + + bool operator==(const LayerRepresentation& other) const { if (m_representation != other.m_representation) return false; @@ -140,6 +179,9 @@ public: bool representsPlatformLayerID() const { return m_representation == PlatformLayerIDRepresentation; } private: + WEBCORE_EXPORT void retainPlatformLayer(PlatformLayer*); + WEBCORE_EXPORT void releasePlatformLayer(PlatformLayer*); + union { GraphicsLayer* m_graphicsLayer; PlatformLayer *m_platformLayer; @@ -149,15 +191,22 @@ private: Type m_representation; }; -class ScrollingStateNode { +class ScrollingStateNode : public RefCounted<ScrollingStateNode> { + WTF_MAKE_FAST_ALLOCATED; public: ScrollingStateNode(ScrollingNodeType, ScrollingStateTree&, ScrollingNodeID); virtual ~ScrollingStateNode(); ScrollingNodeType nodeType() const { return m_nodeType; } - virtual PassOwnPtr<ScrollingStateNode> clone(ScrollingStateTree& adoptiveTree) = 0; - PassOwnPtr<ScrollingStateNode> cloneAndReset(ScrollingStateTree& adoptiveTree); + bool isFixedNode() const { return m_nodeType == FixedNode; } + bool isStickyNode() const { return m_nodeType == StickyNode; } + bool isScrollingNode() const { return m_nodeType == FrameScrollingNode || m_nodeType == OverflowScrollingNode; } + bool isFrameScrollingNode() const { return m_nodeType == FrameScrollingNode; } + bool isOverflowScrollingNode() const { return m_nodeType == OverflowScrollingNode; } + + virtual Ref<ScrollingStateNode> clone(ScrollingStateTree& adoptiveTree) = 0; + Ref<ScrollingStateNode> cloneAndReset(ScrollingStateTree& adoptiveTree); void cloneAndResetChildren(ScrollingStateNode&, ScrollingStateTree& adoptiveTree); enum { @@ -174,10 +223,10 @@ public: ChangedProperties changedProperties() const { return m_changedProperties; } void setChangedProperties(ChangedProperties changedProperties) { m_changedProperties = changedProperties; } - virtual void syncLayerPositionForViewportRect(const LayoutRect& /*viewportRect*/) { } + virtual void reconcileLayerPositionForViewportRect(const LayoutRect& /*viewportRect*/, ScrollingLayerPositionAction) { } const LayerRepresentation& layer() const { return m_layer; } - void setLayer(const LayerRepresentation&); + WEBCORE_EXPORT void setLayer(const LayerRepresentation&); ScrollingStateTree& scrollingStateTree() const { return m_scrollingStateTree; } @@ -187,10 +236,9 @@ public: void setParent(ScrollingStateNode* parent) { m_parent = parent; } ScrollingNodeID parentNodeID() const { return m_parent ? m_parent->scrollingNodeID() : 0; } - Vector<OwnPtr<ScrollingStateNode>>* children() const { return m_children.get(); } + Vector<RefPtr<ScrollingStateNode>>* children() const { return m_children.get(); } - void appendChild(PassOwnPtr<ScrollingStateNode>); - void removeChild(ScrollingStateNode*); + void appendChild(Ref<ScrollingStateNode>&&); String scrollingStateTreeAsText() const; @@ -198,10 +246,9 @@ protected: ScrollingStateNode(const ScrollingStateNode&, ScrollingStateTree&); private: - void dump(TextStream&, int indent) const; + void dump(TextStream&, int indent, ScrollingStateTreeAsTextBehavior) const; - virtual void dumpProperties(TextStream&, int indent) const = 0; - void willBeRemovedFromStateTree(); + virtual void dumpProperties(TextStream&, int indent, ScrollingStateTreeAsTextBehavior) const = 0; const ScrollingNodeType m_nodeType; ScrollingNodeID m_nodeID; @@ -210,16 +257,16 @@ private: ScrollingStateTree& m_scrollingStateTree; ScrollingStateNode* m_parent; - OwnPtr<Vector<OwnPtr<ScrollingStateNode>>> m_children; + std::unique_ptr<Vector<RefPtr<ScrollingStateNode>>> m_children; LayerRepresentation m_layer; }; -#define SCROLLING_STATE_NODE_TYPE_CASTS(ToValueTypeName, predicate) \ - TYPE_CASTS_BASE(ToValueTypeName, ScrollingStateNode, value, value->predicate, value.predicate) - } // namespace WebCore -#endif // ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) +#define SPECIALIZE_TYPE_TRAITS_SCROLLING_STATE_NODE(ToValueTypeName, predicate) \ +SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::ToValueTypeName) \ + static bool isType(const WebCore::ScrollingStateNode& node) { return node.predicate; } \ +SPECIALIZE_TYPE_TRAITS_END() -#endif // ScrollingStateNode_h +#endif // ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) diff --git a/Source/WebCore/page/scrolling/ScrollingStateOverflowScrollingNode.cpp b/Source/WebCore/page/scrolling/ScrollingStateOverflowScrollingNode.cpp new file mode 100644 index 000000000..346459a71 --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollingStateOverflowScrollingNode.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ScrollingStateOverflowScrollingNode.h" + +#if ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) + +#include "ScrollingStateTree.h" +#include "TextStream.h" + +namespace WebCore { + +Ref<ScrollingStateOverflowScrollingNode> ScrollingStateOverflowScrollingNode::create(ScrollingStateTree& stateTree, ScrollingNodeID nodeID) +{ + return adoptRef(*new ScrollingStateOverflowScrollingNode(stateTree, nodeID)); +} + +ScrollingStateOverflowScrollingNode::ScrollingStateOverflowScrollingNode(ScrollingStateTree& stateTree, ScrollingNodeID nodeID) + : ScrollingStateScrollingNode(stateTree, OverflowScrollingNode, nodeID) +{ +} + +ScrollingStateOverflowScrollingNode::ScrollingStateOverflowScrollingNode(const ScrollingStateOverflowScrollingNode& stateNode, ScrollingStateTree& adoptiveTree) + : ScrollingStateScrollingNode(stateNode, adoptiveTree) +{ + if (hasChangedProperty(ScrolledContentsLayer)) + setScrolledContentsLayer(stateNode.scrolledContentsLayer().toRepresentation(adoptiveTree.preferredLayerRepresentation())); +} + +ScrollingStateOverflowScrollingNode::~ScrollingStateOverflowScrollingNode() +{ +} + +Ref<ScrollingStateNode> ScrollingStateOverflowScrollingNode::clone(ScrollingStateTree& adoptiveTree) +{ + return adoptRef(*new ScrollingStateOverflowScrollingNode(*this, adoptiveTree)); +} + +void ScrollingStateOverflowScrollingNode::setScrolledContentsLayer(const LayerRepresentation& layerRepresentation) +{ + if (layerRepresentation == m_scrolledContentsLayer) + return; + + m_scrolledContentsLayer = layerRepresentation; + setPropertyChanged(ScrolledContentsLayer); +} + +void ScrollingStateOverflowScrollingNode::dumpProperties(TextStream& ts, int indent, ScrollingStateTreeAsTextBehavior behavior) const +{ + ts << "(" << "Overflow scrolling node" << "\n"; + + ScrollingStateScrollingNode::dumpProperties(ts, indent, behavior); + + if ((behavior & ScrollingStateTreeAsTextBehaviorIncludeLayerIDs) && m_scrolledContentsLayer.layerID()) { + writeIndent(ts, indent + 1); + ts << "(scrolled contents layer " << m_scrolledContentsLayer.layerID() << ")\n"; + } +} + +} // namespace WebCore + +#endif // ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) diff --git a/Source/WebCore/page/scrolling/ScrollingStateOverflowScrollingNode.h b/Source/WebCore/page/scrolling/ScrollingStateOverflowScrollingNode.h new file mode 100644 index 000000000..042bcf87d --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollingStateOverflowScrollingNode.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) + +#include "ScrollingStateScrollingNode.h" + +namespace WebCore { + +class ScrollingStateOverflowScrollingNode : public ScrollingStateScrollingNode { +public: + static Ref<ScrollingStateOverflowScrollingNode> create(ScrollingStateTree&, ScrollingNodeID); + + Ref<ScrollingStateNode> clone(ScrollingStateTree&) override; + + virtual ~ScrollingStateOverflowScrollingNode(); + + enum ChangedProperty { + ScrolledContentsLayer = NumScrollingStateNodeBits + }; + + // This is a layer with the contents that move. + const LayerRepresentation& scrolledContentsLayer() const { return m_scrolledContentsLayer; } + WEBCORE_EXPORT void setScrolledContentsLayer(const LayerRepresentation&); + + void dumpProperties(TextStream&, int indent, ScrollingStateTreeAsTextBehavior) const override; + +private: + ScrollingStateOverflowScrollingNode(ScrollingStateTree&, ScrollingNodeID); + ScrollingStateOverflowScrollingNode(const ScrollingStateOverflowScrollingNode&, ScrollingStateTree&); + + LayerRepresentation m_scrolledContentsLayer; +}; + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_SCROLLING_STATE_NODE(ScrollingStateOverflowScrollingNode, isOverflowScrollingNode()) + +#endif // ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) diff --git a/Source/WebCore/page/scrolling/ScrollingStateScrollingNode.cpp b/Source/WebCore/page/scrolling/ScrollingStateScrollingNode.cpp index 8ef8ff6a5..b3de26324 100644 --- a/Source/WebCore/page/scrolling/ScrollingStateScrollingNode.cpp +++ b/Source/WebCore/page/scrolling/ScrollingStateScrollingNode.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Apple Inc. All rights reserved. + * Copyright (C) 2012, 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -30,80 +30,45 @@ #include "ScrollingStateTree.h" #include "TextStream.h" -#include <wtf/OwnPtr.h> namespace WebCore { -PassOwnPtr<ScrollingStateScrollingNode> ScrollingStateScrollingNode::create(ScrollingStateTree& stateTree, ScrollingNodeID nodeID) -{ - return adoptPtr(new ScrollingStateScrollingNode(stateTree, nodeID)); -} - -ScrollingStateScrollingNode::ScrollingStateScrollingNode(ScrollingStateTree& stateTree, ScrollingNodeID nodeID) - : ScrollingStateNode(ScrollingNode, stateTree, nodeID) -#if PLATFORM(MAC) && !PLATFORM(IOS) - , m_verticalScrollbarPainter(0) - , m_horizontalScrollbarPainter(0) -#endif - , m_frameScaleFactor(1) - , m_wheelEventHandlerCount(0) - , m_synchronousScrollingReasons(0) - , m_behaviorForFixed(StickToDocumentBounds) - , m_headerHeight(0) - , m_footerHeight(0) - , m_requestedScrollPositionRepresentsProgrammaticScroll(false) +ScrollingStateScrollingNode::ScrollingStateScrollingNode(ScrollingStateTree& stateTree, ScrollingNodeType nodeType, ScrollingNodeID nodeID) + : ScrollingStateNode(nodeType, stateTree, nodeID) { } ScrollingStateScrollingNode::ScrollingStateScrollingNode(const ScrollingStateScrollingNode& stateNode, ScrollingStateTree& adoptiveTree) : ScrollingStateNode(stateNode, adoptiveTree) -#if PLATFORM(MAC) && !PLATFORM(IOS) - , m_verticalScrollbarPainter(stateNode.verticalScrollbarPainter()) - , m_horizontalScrollbarPainter(stateNode.horizontalScrollbarPainter()) -#endif - , m_viewportRect(stateNode.viewportRect()) + , m_scrollableAreaSize(stateNode.scrollableAreaSize()) , m_totalContentsSize(stateNode.totalContentsSize()) + , m_reachableContentsSize(stateNode.reachableContentsSize()) + , m_scrollPosition(stateNode.scrollPosition()) + , m_requestedScrollPosition(stateNode.requestedScrollPosition()) , m_scrollOrigin(stateNode.scrollOrigin()) +#if ENABLE(CSS_SCROLL_SNAP) + , m_snapOffsetsInfo(stateNode.m_snapOffsetsInfo) +#endif , m_scrollableAreaParameters(stateNode.scrollableAreaParameters()) - , m_nonFastScrollableRegion(stateNode.nonFastScrollableRegion()) - , m_frameScaleFactor(stateNode.frameScaleFactor()) - , m_wheelEventHandlerCount(stateNode.wheelEventHandlerCount()) - , m_synchronousScrollingReasons(stateNode.synchronousScrollingReasons()) - , m_behaviorForFixed(stateNode.scrollBehaviorForFixedElements()) - , m_headerHeight(stateNode.headerHeight()) - , m_footerHeight(stateNode.footerHeight()) - , m_requestedScrollPosition(stateNode.requestedScrollPosition()) , m_requestedScrollPositionRepresentsProgrammaticScroll(stateNode.requestedScrollPositionRepresentsProgrammaticScroll()) + , m_expectsWheelEventTestTrigger(stateNode.expectsWheelEventTestTrigger()) { - if (hasChangedProperty(CounterScrollingLayer)) - setCounterScrollingLayer(stateNode.counterScrollingLayer().toRepresentation(adoptiveTree.preferredLayerRepresentation())); - - if (hasChangedProperty(HeaderLayer)) - setHeaderLayer(stateNode.headerLayer().toRepresentation(adoptiveTree.preferredLayerRepresentation())); - - if (hasChangedProperty(FooterLayer)) - setFooterLayer(stateNode.footerLayer().toRepresentation(adoptiveTree.preferredLayerRepresentation())); } ScrollingStateScrollingNode::~ScrollingStateScrollingNode() { } -PassOwnPtr<ScrollingStateNode> ScrollingStateScrollingNode::clone(ScrollingStateTree& adoptiveTree) +void ScrollingStateScrollingNode::setScrollableAreaSize(const FloatSize& size) { - return adoptPtr(new ScrollingStateScrollingNode(*this, adoptiveTree)); -} - -void ScrollingStateScrollingNode::setViewportRect(const IntRect& viewportRect) -{ - if (m_viewportRect == viewportRect) + if (m_scrollableAreaSize == size) return; - m_viewportRect = viewportRect; - setPropertyChanged(ViewportRect); + m_scrollableAreaSize = size; + setPropertyChanged(ScrollableAreaSize); } -void ScrollingStateScrollingNode::setTotalContentsSize(const IntSize& totalContentsSize) +void ScrollingStateScrollingNode::setTotalContentsSize(const FloatSize& totalContentsSize) { if (m_totalContentsSize == totalContentsSize) return; @@ -112,159 +77,142 @@ void ScrollingStateScrollingNode::setTotalContentsSize(const IntSize& totalConte setPropertyChanged(TotalContentsSize); } -void ScrollingStateScrollingNode::setScrollOrigin(const IntPoint& scrollOrigin) +void ScrollingStateScrollingNode::setReachableContentsSize(const FloatSize& reachableContentsSize) { - if (m_scrollOrigin == scrollOrigin) + if (m_reachableContentsSize == reachableContentsSize) return; - m_scrollOrigin = scrollOrigin; - setPropertyChanged(ScrollOrigin); + m_reachableContentsSize = reachableContentsSize; + setPropertyChanged(ReachableContentsSize); } -void ScrollingStateScrollingNode::setScrollableAreaParameters(const ScrollableAreaParameters& parameters) +void ScrollingStateScrollingNode::setScrollPosition(const FloatPoint& scrollPosition) { - if (m_scrollableAreaParameters == parameters) + if (m_scrollPosition == scrollPosition) return; - m_scrollableAreaParameters = parameters; - setPropertyChanged(ScrollableAreaParams); + m_scrollPosition = scrollPosition; + setPropertyChanged(ScrollPosition); } -void ScrollingStateScrollingNode::setFrameScaleFactor(float scaleFactor) +void ScrollingStateScrollingNode::setScrollOrigin(const IntPoint& scrollOrigin) { - if (m_frameScaleFactor == scaleFactor) + if (m_scrollOrigin == scrollOrigin) return; - m_frameScaleFactor = scaleFactor; - - setPropertyChanged(FrameScaleFactor); + m_scrollOrigin = scrollOrigin; + setPropertyChanged(ScrollOrigin); } -void ScrollingStateScrollingNode::setNonFastScrollableRegion(const Region& nonFastScrollableRegion) +#if ENABLE(CSS_SCROLL_SNAP) +void ScrollingStateScrollingNode::setHorizontalSnapOffsets(const Vector<float>& snapOffsets) { - if (m_nonFastScrollableRegion == nonFastScrollableRegion) + if (m_snapOffsetsInfo.horizontalSnapOffsets == snapOffsets) return; - m_nonFastScrollableRegion = nonFastScrollableRegion; - setPropertyChanged(NonFastScrollableRegion); + m_snapOffsetsInfo.horizontalSnapOffsets = snapOffsets; + setPropertyChanged(HorizontalSnapOffsets); } -void ScrollingStateScrollingNode::setWheelEventHandlerCount(unsigned wheelEventHandlerCount) +void ScrollingStateScrollingNode::setVerticalSnapOffsets(const Vector<float>& snapOffsets) { - if (m_wheelEventHandlerCount == wheelEventHandlerCount) + if (m_snapOffsetsInfo.verticalSnapOffsets == snapOffsets) return; - m_wheelEventHandlerCount = wheelEventHandlerCount; - setPropertyChanged(WheelEventHandlerCount); + m_snapOffsetsInfo.verticalSnapOffsets = snapOffsets; + setPropertyChanged(VerticalSnapOffsets); } -void ScrollingStateScrollingNode::setSynchronousScrollingReasons(SynchronousScrollingReasons reasons) +void ScrollingStateScrollingNode::setHorizontalSnapOffsetRanges(const Vector<ScrollOffsetRange<float>>& scrollOffsetRanges) { - if (m_synchronousScrollingReasons == reasons) + if (m_snapOffsetsInfo.horizontalSnapOffsetRanges == scrollOffsetRanges) return; - m_synchronousScrollingReasons = reasons; - setPropertyChanged(ReasonsForSynchronousScrolling); + m_snapOffsetsInfo.horizontalSnapOffsetRanges = scrollOffsetRanges; + setPropertyChanged(HorizontalSnapOffsetRanges); } -void ScrollingStateScrollingNode::setScrollBehaviorForFixedElements(ScrollBehaviorForFixedElements behaviorForFixed) +void ScrollingStateScrollingNode::setVerticalSnapOffsetRanges(const Vector<ScrollOffsetRange<float>>& scrollOffsetRanges) { - if (m_behaviorForFixed == behaviorForFixed) + if (m_snapOffsetsInfo.verticalSnapOffsetRanges == scrollOffsetRanges) return; - m_behaviorForFixed = behaviorForFixed; - setPropertyChanged(BehaviorForFixedElements); -} - -void ScrollingStateScrollingNode::setRequestedScrollPosition(const IntPoint& requestedScrollPosition, bool representsProgrammaticScroll) -{ - m_requestedScrollPosition = requestedScrollPosition; - m_requestedScrollPositionRepresentsProgrammaticScroll = representsProgrammaticScroll; - setPropertyChanged(RequestedScrollPosition); + m_snapOffsetsInfo.verticalSnapOffsetRanges = scrollOffsetRanges; + setPropertyChanged(VerticalSnapOffsetRanges); } -void ScrollingStateScrollingNode::setHeaderHeight(int headerHeight) +void ScrollingStateScrollingNode::setCurrentHorizontalSnapPointIndex(unsigned index) { - if (m_headerHeight == headerHeight) + if (m_currentHorizontalSnapPointIndex == index) return; - - m_headerHeight = headerHeight; - setPropertyChanged(HeaderHeight); + + m_currentHorizontalSnapPointIndex = index; + setPropertyChanged(CurrentHorizontalSnapOffsetIndex); } -void ScrollingStateScrollingNode::setFooterHeight(int footerHeight) +void ScrollingStateScrollingNode::setCurrentVerticalSnapPointIndex(unsigned index) { - if (m_footerHeight == footerHeight) + if (m_currentVerticalSnapPointIndex == index) return; - - m_footerHeight = footerHeight; - setPropertyChanged(FooterHeight); + + m_currentVerticalSnapPointIndex = index; + setPropertyChanged(CurrentVerticalSnapOffsetIndex); } +#endif -void ScrollingStateScrollingNode::setCounterScrollingLayer(const LayerRepresentation& layerRepresentation) +void ScrollingStateScrollingNode::setScrollableAreaParameters(const ScrollableAreaParameters& parameters) { - if (layerRepresentation == m_counterScrollingLayer) + if (m_scrollableAreaParameters == parameters) return; - - m_counterScrollingLayer = layerRepresentation; - setPropertyChanged(CounterScrollingLayer); + m_scrollableAreaParameters = parameters; + setPropertyChanged(ScrollableAreaParams); } -void ScrollingStateScrollingNode::setHeaderLayer(const LayerRepresentation& layerRepresentation) +void ScrollingStateScrollingNode::setRequestedScrollPosition(const FloatPoint& requestedScrollPosition, bool representsProgrammaticScroll) { - if (layerRepresentation == m_headerLayer) - return; - - m_headerLayer = layerRepresentation; - - setPropertyChanged(HeaderLayer); + m_requestedScrollPosition = requestedScrollPosition; + m_requestedScrollPositionRepresentsProgrammaticScroll = representsProgrammaticScroll; + setPropertyChanged(RequestedScrollPosition); } - -void ScrollingStateScrollingNode::setFooterLayer(const LayerRepresentation& layerRepresentation) +void ScrollingStateScrollingNode::setExpectsWheelEventTestTrigger(bool expectsTestTrigger) { - if (layerRepresentation == m_footerLayer) + if (expectsTestTrigger == m_expectsWheelEventTestTrigger) return; - - m_footerLayer = layerRepresentation; - setPropertyChanged(FooterLayer); + m_expectsWheelEventTestTrigger = expectsTestTrigger; + setPropertyChanged(ExpectsWheelEventTestTrigger); } -#if !(PLATFORM(MAC) && !PLATFORM(IOS)) -void ScrollingStateScrollingNode::setScrollbarPaintersFromScrollbars(Scrollbar*, Scrollbar*) +void ScrollingStateScrollingNode::dumpProperties(TextStream& ts, int indent, ScrollingStateTreeAsTextBehavior) const { -} -#endif - -void ScrollingStateScrollingNode::dumpProperties(TextStream& ts, int indent) const -{ - ts << "(" << "Scrolling node" << "\n"; - - if (!m_viewportRect.isEmpty()) { + if (m_scrollPosition != FloatPoint()) { writeIndent(ts, indent + 1); - ts << "(viewport rect " << m_viewportRect.x() << " " << m_viewportRect.y() << " " << m_viewportRect.width() << " " << m_viewportRect.height() << ")\n"; + ts << "(scroll position " + << TextStream::FormatNumberRespectingIntegers(m_scrollPosition.x()) << " " + << TextStream::FormatNumberRespectingIntegers(m_scrollPosition.y()) << ")\n"; } - if (!m_totalContentsSize.isEmpty()) { + if (!m_scrollableAreaSize.isEmpty()) { writeIndent(ts, indent + 1); - ts << "(contents size " << m_totalContentsSize.width() << " " << m_totalContentsSize.height() << ")\n"; + ts << "(scrollable area size " + << TextStream::FormatNumberRespectingIntegers(m_scrollableAreaSize.width()) << " " + << TextStream::FormatNumberRespectingIntegers(m_scrollableAreaSize.height()) << ")\n"; } - if (m_frameScaleFactor != 1) { - writeIndent(ts, indent + 1); - ts << "(frame scale factor " << m_frameScaleFactor << ")\n"; - } - - if (m_synchronousScrollingReasons) { + if (!m_totalContentsSize.isEmpty()) { writeIndent(ts, indent + 1); - ts << "(Scrolling on main thread because: " << ScrollingCoordinator::synchronousScrollingReasonsAsText(m_synchronousScrollingReasons) << ")\n"; + ts << "(contents size " + << TextStream::FormatNumberRespectingIntegers(m_totalContentsSize.width()) << " " + << TextStream::FormatNumberRespectingIntegers(m_totalContentsSize.height()) << ")\n"; } if (m_requestedScrollPosition != IntPoint()) { writeIndent(ts, indent + 1); - ts << "(requested scroll position " << m_requestedScrollPosition.x() << " " << m_requestedScrollPosition.y() << ")\n"; + ts << "(requested scroll position " + << TextStream::FormatNumberRespectingIntegers(m_requestedScrollPosition.x()) << " " + << TextStream::FormatNumberRespectingIntegers(m_requestedScrollPosition.y()) << ")\n"; } if (m_scrollOrigin != IntPoint()) { diff --git a/Source/WebCore/page/scrolling/ScrollingStateScrollingNode.h b/Source/WebCore/page/scrolling/ScrollingStateScrollingNode.h index 14be04b97..628c34f7f 100644 --- a/Source/WebCore/page/scrolling/ScrollingStateScrollingNode.h +++ b/Source/WebCore/page/scrolling/ScrollingStateScrollingNode.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Apple Inc. All rights reserved. + * Copyright (C) 2012, 2014-2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -23,142 +23,111 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef ScrollingStateScrollingNode_h -#define ScrollingStateScrollingNode_h +#pragma once #if ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) -#include "GraphicsLayer.h" -#include "IntRect.h" -#include "Region.h" +#include "ScrollSnapOffsetsInfo.h" #include "ScrollTypes.h" -#include "ScrollbarThemeComposite.h" #include "ScrollingCoordinator.h" #include "ScrollingStateNode.h" -#include <wtf/PassOwnPtr.h> namespace WebCore { -class Scrollbar; - -class ScrollingStateScrollingNode final : public ScrollingStateNode { +class ScrollingStateScrollingNode : public ScrollingStateNode { public: - static PassOwnPtr<ScrollingStateScrollingNode> create(ScrollingStateTree&, ScrollingNodeID); - - virtual PassOwnPtr<ScrollingStateNode> clone(ScrollingStateTree&); - virtual ~ScrollingStateScrollingNode(); enum ChangedProperty { - ViewportRect = NumStateNodeBits, + ScrollableAreaSize = NumStateNodeBits, TotalContentsSize, + ReachableContentsSize, + ScrollPosition, ScrollOrigin, ScrollableAreaParams, - FrameScaleFactor, - NonFastScrollableRegion, - WheelEventHandlerCount, - ReasonsForSynchronousScrolling, RequestedScrollPosition, - CounterScrollingLayer, - HeaderHeight, - FooterHeight, - HeaderLayer, - FooterLayer, - PainterForScrollbar, - BehaviorForFixedElements + NumScrollingStateNodeBits, +#if ENABLE(CSS_SCROLL_SNAP) + HorizontalSnapOffsets, + VerticalSnapOffsets, + HorizontalSnapOffsetRanges, + VerticalSnapOffsetRanges, + CurrentHorizontalSnapOffsetIndex, + CurrentVerticalSnapOffsetIndex, +#endif + ExpectsWheelEventTestTrigger, }; - const IntRect& viewportRect() const { return m_viewportRect; } - void setViewportRect(const IntRect&); - - const IntSize& totalContentsSize() const { return m_totalContentsSize; } - void setTotalContentsSize(const IntSize&); - - const IntPoint& scrollOrigin() const { return m_scrollOrigin; } - void setScrollOrigin(const IntPoint&); + const FloatSize& scrollableAreaSize() const { return m_scrollableAreaSize; } + WEBCORE_EXPORT void setScrollableAreaSize(const FloatSize&); - float frameScaleFactor() const { return m_frameScaleFactor; } - void setFrameScaleFactor(float); + const FloatSize& totalContentsSize() const { return m_totalContentsSize; } + WEBCORE_EXPORT void setTotalContentsSize(const FloatSize&); - const Region& nonFastScrollableRegion() const { return m_nonFastScrollableRegion; } - void setNonFastScrollableRegion(const Region&); + const FloatSize& reachableContentsSize() const { return m_reachableContentsSize; } + WEBCORE_EXPORT void setReachableContentsSize(const FloatSize&); - unsigned wheelEventHandlerCount() const { return m_wheelEventHandlerCount; } - void setWheelEventHandlerCount(unsigned); + const FloatPoint& scrollPosition() const { return m_scrollPosition; } + WEBCORE_EXPORT void setScrollPosition(const FloatPoint&); - SynchronousScrollingReasons synchronousScrollingReasons() const { return m_synchronousScrollingReasons; } - void setSynchronousScrollingReasons(SynchronousScrollingReasons); - - const ScrollableAreaParameters& scrollableAreaParameters() const { return m_scrollableAreaParameters; } - void setScrollableAreaParameters(const ScrollableAreaParameters& params); - - ScrollBehaviorForFixedElements scrollBehaviorForFixedElements() const { return m_behaviorForFixed; } - void setScrollBehaviorForFixedElements(ScrollBehaviorForFixedElements); - - const IntPoint& requestedScrollPosition() const { return m_requestedScrollPosition; } - void setRequestedScrollPosition(const IntPoint&, bool representsProgrammaticScroll); + const IntPoint& scrollOrigin() const { return m_scrollOrigin; } + WEBCORE_EXPORT void setScrollOrigin(const IntPoint&); - int headerHeight() const { return m_headerHeight; } - void setHeaderHeight(int); +#if ENABLE(CSS_SCROLL_SNAP) + const Vector<float>& horizontalSnapOffsets() const { return m_snapOffsetsInfo.horizontalSnapOffsets; } + WEBCORE_EXPORT void setHorizontalSnapOffsets(const Vector<float>&); - int footerHeight() const { return m_footerHeight; } - void setFooterHeight(int); + const Vector<float>& verticalSnapOffsets() const { return m_snapOffsetsInfo.verticalSnapOffsets; } + WEBCORE_EXPORT void setVerticalSnapOffsets(const Vector<float>&); - // This is a layer moved in the opposite direction to scrolling, for example for background-attachment:fixed - const LayerRepresentation& counterScrollingLayer() const { return m_counterScrollingLayer; } - void setCounterScrollingLayer(const LayerRepresentation&); + const Vector<ScrollOffsetRange<float>>& horizontalSnapOffsetRanges() const { return m_snapOffsetsInfo.horizontalSnapOffsetRanges; } + WEBCORE_EXPORT void setHorizontalSnapOffsetRanges(const Vector<ScrollOffsetRange<float>>&); - // The header and footer layers scroll vertically with the page, they should remain fixed when scrolling horizontally. - const LayerRepresentation& headerLayer() const { return m_headerLayer; } - void setHeaderLayer(const LayerRepresentation&); + const Vector<ScrollOffsetRange<float>>& verticalSnapOffsetRanges() const { return m_snapOffsetsInfo.verticalSnapOffsetRanges; } + WEBCORE_EXPORT void setVerticalSnapOffsetRanges(const Vector<ScrollOffsetRange<float>>&); - // The header and footer layers scroll vertically with the page, they should remain fixed when scrolling horizontally. - const LayerRepresentation& footerLayer() const { return m_footerLayer; } - void setFooterLayer(const LayerRepresentation&); + unsigned currentHorizontalSnapPointIndex() const { return m_currentHorizontalSnapPointIndex; } + WEBCORE_EXPORT void setCurrentHorizontalSnapPointIndex(unsigned); -#if PLATFORM(MAC) && !PLATFORM(IOS) - ScrollbarPainter verticalScrollbarPainter() const { return m_verticalScrollbarPainter.get(); } - ScrollbarPainter horizontalScrollbarPainter() const { return m_horizontalScrollbarPainter.get(); } + unsigned currentVerticalSnapPointIndex() const { return m_currentVerticalSnapPointIndex; } + WEBCORE_EXPORT void setCurrentVerticalSnapPointIndex(unsigned); #endif - void setScrollbarPaintersFromScrollbars(Scrollbar* verticalScrollbar, Scrollbar* horizontalScrollbar); + const ScrollableAreaParameters& scrollableAreaParameters() const { return m_scrollableAreaParameters; } + WEBCORE_EXPORT void setScrollableAreaParameters(const ScrollableAreaParameters& params); + + const FloatPoint& requestedScrollPosition() const { return m_requestedScrollPosition; } bool requestedScrollPositionRepresentsProgrammaticScroll() const { return m_requestedScrollPositionRepresentsProgrammaticScroll; } + WEBCORE_EXPORT void setRequestedScrollPosition(const FloatPoint&, bool representsProgrammaticScroll); - virtual void dumpProperties(TextStream&, int indent) const override; + bool expectsWheelEventTestTrigger() const { return m_expectsWheelEventTestTrigger; } + WEBCORE_EXPORT void setExpectsWheelEventTestTrigger(bool); -private: - ScrollingStateScrollingNode(ScrollingStateTree&, ScrollingNodeID); +protected: + ScrollingStateScrollingNode(ScrollingStateTree&, ScrollingNodeType, ScrollingNodeID); ScrollingStateScrollingNode(const ScrollingStateScrollingNode&, ScrollingStateTree&); - LayerRepresentation m_counterScrollingLayer; - LayerRepresentation m_headerLayer; - LayerRepresentation m_footerLayer; - -#if PLATFORM(MAC) && !PLATFORM(IOS) - RetainPtr<ScrollbarPainter> m_verticalScrollbarPainter; - RetainPtr<ScrollbarPainter> m_horizontalScrollbarPainter; -#endif - - IntRect m_viewportRect; - IntSize m_totalContentsSize; - IntPoint m_scrollOrigin; + void dumpProperties(TextStream&, int indent, ScrollingStateTreeAsTextBehavior) const override; +private: + FloatSize m_scrollableAreaSize; + FloatSize m_totalContentsSize; + FloatSize m_reachableContentsSize; + FloatPoint m_scrollPosition; + FloatPoint m_requestedScrollPosition; + IntPoint m_scrollOrigin; +#if ENABLE(CSS_SCROLL_SNAP) + ScrollSnapOffsetsInfo<float> m_snapOffsetsInfo; + unsigned m_currentHorizontalSnapPointIndex { 0 }; + unsigned m_currentVerticalSnapPointIndex { 0 }; +#endif ScrollableAreaParameters m_scrollableAreaParameters; - Region m_nonFastScrollableRegion; - float m_frameScaleFactor; - unsigned m_wheelEventHandlerCount; - SynchronousScrollingReasons m_synchronousScrollingReasons; - ScrollBehaviorForFixedElements m_behaviorForFixed; - int m_headerHeight; - int m_footerHeight; - IntPoint m_requestedScrollPosition; - bool m_requestedScrollPositionRepresentsProgrammaticScroll; + bool m_requestedScrollPositionRepresentsProgrammaticScroll { false }; + bool m_expectsWheelEventTestTrigger { false }; }; -SCROLLING_STATE_NODE_TYPE_CASTS(ScrollingStateScrollingNode, nodeType() == ScrollingNode); - } // namespace WebCore -#endif // ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) +SPECIALIZE_TYPE_TRAITS_SCROLLING_STATE_NODE(ScrollingStateScrollingNode, isScrollingNode()) -#endif // ScrollingStateScrollingNode_h +#endif // ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) diff --git a/Source/WebCore/page/scrolling/ScrollingStateStickyNode.cpp b/Source/WebCore/page/scrolling/ScrollingStateStickyNode.cpp new file mode 100644 index 000000000..912f5b9d7 --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollingStateStickyNode.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2012 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ScrollingStateStickyNode.h" + +#if ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) + +#include "GraphicsLayer.h" +#include "Logging.h" +#include "ScrollingStateTree.h" +#include "TextStream.h" + +namespace WebCore { + +Ref<ScrollingStateStickyNode> ScrollingStateStickyNode::create(ScrollingStateTree& stateTree, ScrollingNodeID nodeID) +{ + return adoptRef(*new ScrollingStateStickyNode(stateTree, nodeID)); +} + +ScrollingStateStickyNode::ScrollingStateStickyNode(ScrollingStateTree& tree, ScrollingNodeID nodeID) + : ScrollingStateNode(StickyNode, tree, nodeID) +{ +} + +ScrollingStateStickyNode::ScrollingStateStickyNode(const ScrollingStateStickyNode& node, ScrollingStateTree& adoptiveTree) + : ScrollingStateNode(node, adoptiveTree) + , m_constraints(StickyPositionViewportConstraints(node.viewportConstraints())) +{ +} + +ScrollingStateStickyNode::~ScrollingStateStickyNode() +{ +} + +Ref<ScrollingStateNode> ScrollingStateStickyNode::clone(ScrollingStateTree& adoptiveTree) +{ + return adoptRef(*new ScrollingStateStickyNode(*this, adoptiveTree)); +} + +void ScrollingStateStickyNode::updateConstraints(const StickyPositionViewportConstraints& constraints) +{ + if (m_constraints == constraints) + return; + + m_constraints = constraints; + setPropertyChanged(ViewportConstraints); +} + +void ScrollingStateStickyNode::reconcileLayerPositionForViewportRect(const LayoutRect& viewportRect, ScrollingLayerPositionAction action) +{ + FloatPoint position = m_constraints.layerPositionForConstrainingRect(viewportRect); + if (layer().representsGraphicsLayer()) { + GraphicsLayer* graphicsLayer = static_cast<GraphicsLayer*>(layer()); + + LOG_WITH_STREAM(Compositing, stream << "ScrollingStateStickyNode::reconcileLayerPositionForViewportRect setting position of layer " << graphicsLayer->primaryLayerID() << " to " << position); + + switch (action) { + case ScrollingLayerPositionAction::Set: + graphicsLayer->setPosition(position); + break; + + case ScrollingLayerPositionAction::SetApproximate: + graphicsLayer->setApproximatePosition(position); + break; + + case ScrollingLayerPositionAction::Sync: + graphicsLayer->syncPosition(position); + break; + } + } +} + +void ScrollingStateStickyNode::dumpProperties(TextStream& ts, int indent, ScrollingStateTreeAsTextBehavior) const +{ + ts << "(" << "Sticky node" << "\n"; + + if (m_constraints.anchorEdges()) { + writeIndent(ts, indent + 1); + ts << "(anchor edges: "; + if (m_constraints.hasAnchorEdge(ViewportConstraints::AnchorEdgeLeft)) + ts << "AnchorEdgeLeft "; + if (m_constraints.hasAnchorEdge(ViewportConstraints::AnchorEdgeRight)) + ts << "AnchorEdgeRight "; + if (m_constraints.hasAnchorEdge(ViewportConstraints::AnchorEdgeTop)) + ts << "AnchorEdgeTop "; + if (m_constraints.hasAnchorEdge(ViewportConstraints::AnchorEdgeBottom)) + ts << "AnchorEdgeBottom"; + ts << ")\n"; + } + + if (m_constraints.hasAnchorEdge(ViewportConstraints::AnchorEdgeLeft)) { + writeIndent(ts, indent + 1); + ts << "(left offset " << m_constraints.leftOffset() << ")\n"; + } + if (m_constraints.hasAnchorEdge(ViewportConstraints::AnchorEdgeRight)) { + writeIndent(ts, indent + 1); + ts << "(right offset " << m_constraints.rightOffset() << ")\n"; + } + if (m_constraints.hasAnchorEdge(ViewportConstraints::AnchorEdgeTop)) { + writeIndent(ts, indent + 1); + ts << "(top offset " << m_constraints.topOffset() << ")\n"; + } + if (m_constraints.hasAnchorEdge(ViewportConstraints::AnchorEdgeBottom)) { + writeIndent(ts, indent + 1); + ts << "(bottom offset " << m_constraints.bottomOffset() << ")\n"; + } + + writeIndent(ts, indent + 1); + FloatRect r = m_constraints.containingBlockRect(); + ts << "(containing block rect " << r.x() << ", " << r.y() << " " << r.width() << " x " << r.height() << ")\n"; + + writeIndent(ts, indent + 1); + r = m_constraints.stickyBoxRect(); + ts << "(sticky box rect " << r.x() << " " << r.y() << " " << r.width() << " " << r.height() << ")\n"; + + writeIndent(ts, indent + 1); + r = m_constraints.constrainingRectAtLastLayout(); + ts << "(constraining rect " << r.x() << " " << r.y() << " " << r.width() << " " << r.height() << ")\n"; + + writeIndent(ts, indent + 1); + ts << "(sticky offset at last layout " << m_constraints.stickyOffsetAtLastLayout().width() << " " << m_constraints.stickyOffsetAtLastLayout().height() << ")\n"; + + writeIndent(ts, indent + 1); + ts << "(layer position at last layout " << m_constraints.layerPositionAtLastLayout().x() << " " << m_constraints.layerPositionAtLastLayout().y() << ")\n"; +} + +} // namespace WebCore + +#endif // ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) diff --git a/Source/WebCore/page/scrolling/ScrollingStateStickyNode.h b/Source/WebCore/page/scrolling/ScrollingStateStickyNode.h new file mode 100644 index 000000000..63f908e76 --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollingStateStickyNode.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2012 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) + +#include "ScrollingConstraints.h" +#include "ScrollingStateNode.h" + +#include <wtf/Forward.h> + +namespace WebCore { + +class StickyPositionViewportConstraints; + +class ScrollingStateStickyNode final : public ScrollingStateNode { +public: + static Ref<ScrollingStateStickyNode> create(ScrollingStateTree&, ScrollingNodeID); + + Ref<ScrollingStateNode> clone(ScrollingStateTree&) override; + + virtual ~ScrollingStateStickyNode(); + + enum { + ViewportConstraints = NumStateNodeBits + }; + + WEBCORE_EXPORT void updateConstraints(const StickyPositionViewportConstraints&); + const StickyPositionViewportConstraints& viewportConstraints() const { return m_constraints; } + +private: + ScrollingStateStickyNode(ScrollingStateTree&, ScrollingNodeID); + ScrollingStateStickyNode(const ScrollingStateStickyNode&, ScrollingStateTree&); + + void reconcileLayerPositionForViewportRect(const LayoutRect& viewportRect, ScrollingLayerPositionAction) override; + + void dumpProperties(TextStream&, int indent, ScrollingStateTreeAsTextBehavior) const override; + + StickyPositionViewportConstraints m_constraints; +}; + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_SCROLLING_STATE_NODE(ScrollingStateStickyNode, isStickyNode()) + +#endif // ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) diff --git a/Source/WebCore/page/scrolling/ScrollingStateTree.cpp b/Source/WebCore/page/scrolling/ScrollingStateTree.cpp index e06f22168..0bbc1b733 100644 --- a/Source/WebCore/page/scrolling/ScrollingStateTree.cpp +++ b/Source/WebCore/page/scrolling/ScrollingStateTree.cpp @@ -30,15 +30,16 @@ #include "AsyncScrollingCoordinator.h" #include "ScrollingStateFixedNode.h" -#include "ScrollingStateScrollingNode.h" +#include "ScrollingStateFrameScrollingNode.h" +#include "ScrollingStateOverflowScrollingNode.h" #include "ScrollingStateStickyNode.h" +#include <wtf/text/CString.h> -namespace WebCore { +#ifndef NDEBUG +#include <stdio.h> +#endif -PassOwnPtr<ScrollingStateTree> ScrollingStateTree::create(AsyncScrollingCoordinator* scrollingCoordinator) -{ - return adoptPtr(new ScrollingStateTree(scrollingCoordinator)); -} +namespace WebCore { ScrollingStateTree::ScrollingStateTree(AsyncScrollingCoordinator* scrollingCoordinator) : m_scrollingCoordinator(scrollingCoordinator) @@ -66,27 +67,58 @@ void ScrollingStateTree::setHasChangedProperties(bool changedProperties) #endif } +Ref<ScrollingStateNode> ScrollingStateTree::createNode(ScrollingNodeType nodeType, ScrollingNodeID nodeID) +{ + switch (nodeType) { + case FixedNode: + return ScrollingStateFixedNode::create(*this, nodeID); + case StickyNode: + return ScrollingStateStickyNode::create(*this, nodeID); + case FrameScrollingNode: + return ScrollingStateFrameScrollingNode::create(*this, nodeID); + case OverflowScrollingNode: + return ScrollingStateOverflowScrollingNode::create(*this, nodeID); + } + ASSERT_NOT_REACHED(); + return ScrollingStateFixedNode::create(*this, nodeID); +} + +bool ScrollingStateTree::nodeTypeAndParentMatch(ScrollingStateNode& node, ScrollingNodeType nodeType, ScrollingNodeID parentID) const +{ + if (node.nodeType() != nodeType) + return false; + + ScrollingStateNode* parent = stateNodeForID(parentID); + if (!parent) + return true; + + return node.parent() == parent; +} + ScrollingNodeID ScrollingStateTree::attachNode(ScrollingNodeType nodeType, ScrollingNodeID newNodeID, ScrollingNodeID parentID) { ASSERT(newNodeID); if (ScrollingStateNode* node = stateNodeForID(newNodeID)) { - ScrollingStateNode* parent = stateNodeForID(parentID); - if (!parent) - return newNodeID; - if (node->parent() == parent) + if (nodeTypeAndParentMatch(*node, nodeType, parentID)) return newNodeID; - // The node is being re-parented. To do that, we'll remove it, and then re-create a new node. - removeNode(node); +#if ENABLE(ASYNC_SCROLLING) + // If the type has changed, we need to destroy and recreate the node with a new ID. + if (nodeType != node->nodeType()) + newNodeID = m_scrollingCoordinator->uniqueScrollLayerID(); +#endif + + // The node is being re-parented. To do that, we'll remove it, and then create a new node. + removeNodeAndAllDescendants(node, SubframeNodeRemoval::Orphan); } - ScrollingStateNode* newNode = 0; + ScrollingStateNode* newNode = nullptr; if (!parentID) { // If we're resetting the root node, we should clear the HashMap and destroy the current children. clear(); - setRootStateNode(ScrollingStateScrollingNode::create(*this, newNodeID)); + setRootStateNode(ScrollingStateFrameScrollingNode::create(*this, newNodeID)); newNode = rootStateNode(); m_hasNewRootStateNode = true; } else { @@ -94,31 +126,22 @@ ScrollingNodeID ScrollingStateTree::attachNode(ScrollingNodeType nodeType, Scrol if (!parent) return 0; - switch (nodeType) { - case FixedNode: { - OwnPtr<ScrollingStateFixedNode> fixedNode = ScrollingStateFixedNode::create(*this, newNodeID); - newNode = fixedNode.get(); - parent->appendChild(fixedNode.release()); - break; - } - case StickyNode: { - OwnPtr<ScrollingStateStickyNode> stickyNode = ScrollingStateStickyNode::create(*this, newNodeID); - newNode = stickyNode.get(); - parent->appendChild(stickyNode.release()); - break; - } - case ScrollingNode: { - // FIXME: We currently only support child nodes that are fixed. - ASSERT_NOT_REACHED(); - OwnPtr<ScrollingStateScrollingNode> scrollingNode = ScrollingStateScrollingNode::create(*this, newNodeID); - newNode = scrollingNode.get(); - parent->appendChild(scrollingNode.release()); - break; + if (nodeType == FrameScrollingNode && parentID) { + if (auto orphanedNode = m_orphanedSubframeNodes.take(newNodeID)) { + newNode = orphanedNode.get(); + parent->appendChild(orphanedNode.releaseNonNull()); + } } + + if (!newNode) { + auto stateNode = createNode(nodeType, newNodeID); + newNode = stateNode.ptr(); + parent->appendChild(WTFMove(stateNode)); } } m_stateNodeMap.set(newNodeID, newNode); + m_nodesRemovedSinceLastCommit.remove(newNodeID); return newNodeID; } @@ -132,35 +155,46 @@ void ScrollingStateTree::detachNode(ScrollingNodeID nodeID) if (!node) return; - removeNode(node); + removeNodeAndAllDescendants(node, SubframeNodeRemoval::Orphan); } void ScrollingStateTree::clear() { - removeNode(rootStateNode()); + if (rootStateNode()) + removeNodeAndAllDescendants(rootStateNode()); + m_stateNodeMap.clear(); + m_orphanedSubframeNodes.clear(); } -PassOwnPtr<ScrollingStateTree> ScrollingStateTree::commit(LayerRepresentation::Type preferredLayerRepresentation) +std::unique_ptr<ScrollingStateTree> ScrollingStateTree::commit(LayerRepresentation::Type preferredLayerRepresentation) { + if (!m_orphanedSubframeNodes.isEmpty()) { + // If we still have orphaned subtrees, remove them from m_stateNodeMap since they will be deleted + // when clearing m_orphanedSubframeNodes. + for (auto& orphanNode : m_orphanedSubframeNodes.values()) + recursiveNodeWillBeRemoved(orphanNode.get(), SubframeNodeRemoval::Delete); + m_orphanedSubframeNodes.clear(); + } + // This function clones and resets the current state tree, but leaves the tree structure intact. - OwnPtr<ScrollingStateTree> treeStateClone = ScrollingStateTree::create(); + std::unique_ptr<ScrollingStateTree> treeStateClone = std::make_unique<ScrollingStateTree>(); treeStateClone->setPreferredLayerRepresentation(preferredLayerRepresentation); if (m_rootStateNode) - treeStateClone->setRootStateNode(static_pointer_cast<ScrollingStateScrollingNode>(m_rootStateNode->cloneAndReset(*treeStateClone))); + treeStateClone->setRootStateNode(static_reference_cast<ScrollingStateFrameScrollingNode>(m_rootStateNode->cloneAndReset(*treeStateClone))); // Copy the IDs of the nodes that have been removed since the last commit into the clone. treeStateClone->m_nodesRemovedSinceLastCommit.swap(m_nodesRemovedSinceLastCommit); // Now the clone tree has changed properties, and the original tree does not. - treeStateClone->m_hasChangedProperties = true; + treeStateClone->m_hasChangedProperties = m_hasChangedProperties; m_hasChangedProperties = false; treeStateClone->m_hasNewRootStateNode = m_hasNewRootStateNode; m_hasNewRootStateNode = false; - return treeStateClone.release(); + return treeStateClone; } void ScrollingStateTree::addNode(ScrollingStateNode* node) @@ -168,39 +202,54 @@ void ScrollingStateTree::addNode(ScrollingStateNode* node) m_stateNodeMap.add(node->scrollingNodeID(), node); } -void ScrollingStateTree::removeNode(ScrollingStateNode* node) +void ScrollingStateTree::removeNodeAndAllDescendants(ScrollingStateNode* node, SubframeNodeRemoval subframeNodeRemoval) { - if (!node) - return; + ScrollingStateNode* parent = node->parent(); - if (node == m_rootStateNode) { - didRemoveNode(node->scrollingNodeID()); + recursiveNodeWillBeRemoved(node, subframeNodeRemoval); + + if (node == m_rootStateNode) m_rootStateNode = nullptr; + else if (parent) { + ASSERT(parent->children()); + ASSERT(parent->children()->find(node) != notFound); + if (auto children = parent->children()) { + size_t index = children->find(node); + if (index != notFound) + children->remove(index); + } + } +} + +void ScrollingStateTree::recursiveNodeWillBeRemoved(ScrollingStateNode* currNode, SubframeNodeRemoval subframeNodeRemoval) +{ + currNode->setParent(nullptr); + if (subframeNodeRemoval == SubframeNodeRemoval::Orphan && currNode != m_rootStateNode && currNode->isFrameScrollingNode()) { + m_orphanedSubframeNodes.add(currNode->scrollingNodeID(), currNode); return; } - ASSERT(m_rootStateNode); - m_rootStateNode->removeChild(node); + willRemoveNode(currNode); - // ScrollingStateTree::removeNode() will destroy children, so we have to make sure we remove those children - // from the HashMap. - size_t size = m_nodesRemovedSinceLastCommit.size(); - for (size_t i = 0; i < size; ++i) - m_stateNodeMap.remove(m_nodesRemovedSinceLastCommit[i]); + if (auto children = currNode->children()) { + for (auto& child : *children) + recursiveNodeWillBeRemoved(child.get(), subframeNodeRemoval); + } } -void ScrollingStateTree::didRemoveNode(ScrollingNodeID nodeID) +void ScrollingStateTree::willRemoveNode(ScrollingStateNode* node) { - m_nodesRemovedSinceLastCommit.append(nodeID); + m_nodesRemovedSinceLastCommit.add(node->scrollingNodeID()); + m_stateNodeMap.remove(node->scrollingNodeID()); setHasChangedProperties(); } -void ScrollingStateTree::setRemovedNodes(Vector<ScrollingNodeID> nodes) +void ScrollingStateTree::setRemovedNodes(HashSet<ScrollingNodeID> nodes) { - m_nodesRemovedSinceLastCommit = std::move(nodes); + m_nodesRemovedSinceLastCommit = WTFMove(nodes); } -ScrollingStateNode* ScrollingStateTree::stateNodeForID(ScrollingNodeID scrollLayerID) +ScrollingStateNode* ScrollingStateTree::stateNodeForID(ScrollingNodeID scrollLayerID) const { if (!scrollLayerID) return 0; @@ -215,4 +264,30 @@ ScrollingStateNode* ScrollingStateTree::stateNodeForID(ScrollingNodeID scrollLay } // namespace WebCore +#ifndef NDEBUG +void showScrollingStateTree(const WebCore::ScrollingStateTree* tree) +{ + if (!tree) + return; + + auto rootNode = tree->rootStateNode(); + if (!rootNode) { + fprintf(stderr, "Scrolling state tree %p with no root node\n", tree); + return; + } + + String output = rootNode->scrollingStateTreeAsText(); + fprintf(stderr, "%s\n", output.utf8().data()); +} + +void showScrollingStateTree(const WebCore::ScrollingStateNode* node) +{ + if (!node) + return; + + showScrollingStateTree(&node->scrollingStateTree()); +} + +#endif + #endif // ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) diff --git a/Source/WebCore/page/scrolling/ScrollingStateTree.h b/Source/WebCore/page/scrolling/ScrollingStateTree.h index 59659e534..3b256151a 100644 --- a/Source/WebCore/page/scrolling/ScrollingStateTree.h +++ b/Source/WebCore/page/scrolling/ScrollingStateTree.h @@ -23,14 +23,11 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef ScrollingStateTree_h -#define ScrollingStateTree_h +#pragma once #if ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) -#include "ScrollingStateScrollingNode.h" -#include <wtf/OwnPtr.h> -#include <wtf/PassOwnPtr.h> +#include "ScrollingStateFrameScrollingNode.h" #include <wtf/RefPtr.h> namespace WebCore { @@ -43,29 +40,30 @@ class AsyncScrollingCoordinator; // the scrolling thread, avoiding locking. class ScrollingStateTree { + WTF_MAKE_FAST_ALLOCATED; friend class ScrollingStateNode; public: - - static PassOwnPtr<ScrollingStateTree> create(AsyncScrollingCoordinator* = 0); - ~ScrollingStateTree(); + WEBCORE_EXPORT ScrollingStateTree(AsyncScrollingCoordinator* = nullptr); + WEBCORE_EXPORT ~ScrollingStateTree(); - ScrollingStateScrollingNode* rootStateNode() const { return m_rootStateNode.get(); } - ScrollingStateNode* stateNodeForID(ScrollingNodeID); + ScrollingStateFrameScrollingNode* rootStateNode() const { return m_rootStateNode.get(); } + WEBCORE_EXPORT ScrollingStateNode* stateNodeForID(ScrollingNodeID) const; - ScrollingNodeID attachNode(ScrollingNodeType, ScrollingNodeID, ScrollingNodeID parentID); + WEBCORE_EXPORT ScrollingNodeID attachNode(ScrollingNodeType, ScrollingNodeID, ScrollingNodeID parentID); void detachNode(ScrollingNodeID); void clear(); - const Vector<ScrollingNodeID>& removedNodes() const { return m_nodesRemovedSinceLastCommit; } - void setRemovedNodes(Vector<ScrollingNodeID>); + const HashSet<ScrollingNodeID>& removedNodes() const { return m_nodesRemovedSinceLastCommit; } + WEBCORE_EXPORT void setRemovedNodes(HashSet<ScrollingNodeID>); // Copies the current tree state and clears the changed properties mask in the original. - PassOwnPtr<ScrollingStateTree> commit(LayerRepresentation::Type preferredLayerRepresentation); + WEBCORE_EXPORT std::unique_ptr<ScrollingStateTree> commit(LayerRepresentation::Type preferredLayerRepresentation); - void setHasChangedProperties(bool = true); + WEBCORE_EXPORT void setHasChangedProperties(bool = true); bool hasChangedProperties() const { return m_hasChangedProperties; } bool hasNewRootStateNode() const { return m_hasNewRootStateNode; } + void setHasNewRootStateNode(bool hasNewRoot) { m_hasNewRootStateNode = hasNewRoot; } int nodeCount() const { return m_stateNodeMap.size(); } @@ -76,17 +74,24 @@ public: void setPreferredLayerRepresentation(LayerRepresentation::Type representation) { m_preferredLayerRepresentation = representation; } private: - ScrollingStateTree(AsyncScrollingCoordinator*); - - void setRootStateNode(PassOwnPtr<ScrollingStateScrollingNode> rootStateNode) { m_rootStateNode = rootStateNode; } + void setRootStateNode(Ref<ScrollingStateFrameScrollingNode>&& rootStateNode) { m_rootStateNode = WTFMove(rootStateNode); } void addNode(ScrollingStateNode*); - void removeNode(ScrollingStateNode*); - void didRemoveNode(ScrollingNodeID); + + Ref<ScrollingStateNode> createNode(ScrollingNodeType, ScrollingNodeID); + + bool nodeTypeAndParentMatch(ScrollingStateNode&, ScrollingNodeType, ScrollingNodeID parentID) const; + + enum class SubframeNodeRemoval { Delete, Orphan }; + void removeNodeAndAllDescendants(ScrollingStateNode*, SubframeNodeRemoval = SubframeNodeRemoval::Delete); + + void recursiveNodeWillBeRemoved(ScrollingStateNode* currNode, SubframeNodeRemoval); + void willRemoveNode(ScrollingStateNode*); AsyncScrollingCoordinator* m_scrollingCoordinator; StateNodeMap m_stateNodeMap; - OwnPtr<ScrollingStateScrollingNode> m_rootStateNode; - Vector<ScrollingNodeID> m_nodesRemovedSinceLastCommit; + RefPtr<ScrollingStateFrameScrollingNode> m_rootStateNode; + HashSet<ScrollingNodeID> m_nodesRemovedSinceLastCommit; + HashMap<ScrollingNodeID, RefPtr<ScrollingStateNode>> m_orphanedSubframeNodes; bool m_hasChangedProperties; bool m_hasNewRootStateNode; LayerRepresentation::Type m_preferredLayerRepresentation; @@ -94,6 +99,9 @@ private: } // namespace WebCore -#endif // ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) +#ifndef NDEBUG +void showScrollingStateTree(const WebCore::ScrollingStateTree*); +void showScrollingStateTree(const WebCore::ScrollingStateNode*); +#endif -#endif // ScrollingStateTree_h +#endif // ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) diff --git a/Source/WebCore/page/scrolling/ScrollingThread.cpp b/Source/WebCore/page/scrolling/ScrollingThread.cpp new file mode 100644 index 000000000..730c0a6bd --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollingThread.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2012, 2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ScrollingThread.h" + +#if ENABLE(ASYNC_SCROLLING) + +#include <mutex> +#include <wtf/MainThread.h> +#include <wtf/NeverDestroyed.h> + +namespace WebCore { + +ScrollingThread::ScrollingThread() + : m_threadIdentifier(0) +{ +} + +bool ScrollingThread::isCurrentThread() +{ + auto threadIdentifier = ScrollingThread::singleton().m_threadIdentifier; + return threadIdentifier && currentThread() == threadIdentifier; +} + +void ScrollingThread::dispatch(Function<void ()>&& function) +{ + auto& scrollingThread = ScrollingThread::singleton(); + scrollingThread.createThreadIfNeeded(); + + { + std::lock_guard<Lock> lock(scrollingThread.m_functionsMutex); + scrollingThread.m_functions.append(WTFMove(function)); + } + + scrollingThread.wakeUpRunLoop(); +} + +void ScrollingThread::dispatchBarrier(Function<void ()>&& function) +{ + dispatch([function = WTFMove(function)]() mutable { + callOnMainThread(WTFMove(function)); + }); +} + +ScrollingThread& ScrollingThread::singleton() +{ + static NeverDestroyed<ScrollingThread> scrollingThread; + + return scrollingThread; +} + +void ScrollingThread::createThreadIfNeeded() +{ + if (m_threadIdentifier) + return; + + // Wait for the thread to initialize the run loop. + { + std::unique_lock<Lock> lock(m_initializeRunLoopMutex); + + m_threadIdentifier = createThread(threadCallback, this, "WebCore: Scrolling"); + +#if PLATFORM(COCOA) + m_initializeRunLoopConditionVariable.wait(lock, [this]{ return m_threadRunLoop; }); +#endif + } +} + +void ScrollingThread::threadCallback(void* scrollingThread) +{ + WTF::setCurrentThreadIsUserInteractive(); + static_cast<ScrollingThread*>(scrollingThread)->threadBody(); +} + +void ScrollingThread::threadBody() +{ + initializeRunLoop(); +} + +void ScrollingThread::dispatchFunctionsFromScrollingThread() +{ + ASSERT(isCurrentThread()); + + Vector<Function<void ()>> functions; + + { + std::lock_guard<Lock> lock(m_functionsMutex); + functions = WTFMove(m_functions); + } + + for (auto& function : functions) + function(); +} + +} // namespace WebCore + +#endif // ENABLE(ASYNC_SCROLLING) diff --git a/Source/WebCore/page/scrolling/ScrollingThread.h b/Source/WebCore/page/scrolling/ScrollingThread.h new file mode 100644 index 000000000..de062d77a --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollingThread.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2012, 2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(ASYNC_SCROLLING) + +#include <functional> +#include <wtf/Condition.h> +#include <wtf/Forward.h> +#include <wtf/Function.h> +#include <wtf/Lock.h> +#include <wtf/Noncopyable.h> +#include <wtf/Threading.h> +#include <wtf/Vector.h> + +#if PLATFORM(COCOA) +#include <wtf/RetainPtr.h> +#endif + +namespace WebCore { + +class ScrollingThread { + WTF_MAKE_NONCOPYABLE(ScrollingThread); + +public: + static bool isCurrentThread(); + WEBCORE_EXPORT static void dispatch(Function<void ()>&&); + + // Will dispatch the given function on the main thread once all pending functions + // on the scrolling thread have finished executing. Used for synchronization purposes. + WEBCORE_EXPORT static void dispatchBarrier(Function<void ()>&&); + +private: + friend NeverDestroyed<ScrollingThread>; + + ScrollingThread(); + + static ScrollingThread& singleton(); + + void createThreadIfNeeded(); + static void threadCallback(void* scrollingThread); + void threadBody(); + void dispatchFunctionsFromScrollingThread(); + + void initializeRunLoop(); + void wakeUpRunLoop(); + +#if PLATFORM(COCOA) + static void threadRunLoopSourceCallback(void* scrollingThread); + void threadRunLoopSourceCallback(); +#endif + + ThreadIdentifier m_threadIdentifier; + + Condition m_initializeRunLoopConditionVariable; + Lock m_initializeRunLoopMutex; + + Lock m_functionsMutex; + Vector<Function<void ()>> m_functions; + +#if PLATFORM(COCOA) + // FIXME: We should use WebCore::RunLoop here. + RetainPtr<CFRunLoopRef> m_threadRunLoop; + RetainPtr<CFRunLoopSourceRef> m_threadRunLoopSource; +#endif +}; + +} // namespace WebCore + +#endif // ENABLE(ASYNC_SCROLLING) diff --git a/Source/WebCore/page/scrolling/ScrollingTree.cpp b/Source/WebCore/page/scrolling/ScrollingTree.cpp new file mode 100644 index 000000000..40bcc63ff --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollingTree.cpp @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2012-2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ScrollingTree.h" + +#if ENABLE(ASYNC_SCROLLING) + +#include "EventNames.h" +#include "Logging.h" +#include "PlatformWheelEvent.h" +#include "ScrollingStateTree.h" +#include "ScrollingTreeFrameScrollingNode.h" +#include "ScrollingTreeNode.h" +#include "ScrollingTreeOverflowScrollingNode.h" +#include "ScrollingTreeScrollingNode.h" +#include "TextStream.h" +#include <wtf/SetForScope.h> + +namespace WebCore { + +ScrollingTree::ScrollingTree() +{ +} + +ScrollingTree::~ScrollingTree() +{ +} + +bool ScrollingTree::shouldHandleWheelEventSynchronously(const PlatformWheelEvent& wheelEvent) +{ + // This method is invoked by the event handling thread + LockHolder lock(m_mutex); + + bool shouldSetLatch = wheelEvent.shouldConsiderLatching(); + + if (hasLatchedNode() && !shouldSetLatch) + return false; + + if (shouldSetLatch) + m_latchedNode = 0; + + if (!m_eventTrackingRegions.isEmpty() && m_rootNode) { + ScrollingTreeFrameScrollingNode& frameScrollingNode = downcast<ScrollingTreeFrameScrollingNode>(*m_rootNode); + FloatPoint position = wheelEvent.position(); + position.move(frameScrollingNode.viewToContentsOffset(m_mainFrameScrollPosition)); + + const EventNames& names = eventNames(); + IntPoint roundedPosition = roundedIntPoint(position); + bool isSynchronousDispatchRegion = m_eventTrackingRegions.trackingTypeForPoint(names.wheelEvent, roundedPosition) == TrackingType::Synchronous + || m_eventTrackingRegions.trackingTypeForPoint(names.mousewheelEvent, roundedPosition) == TrackingType::Synchronous; + LOG_WITH_STREAM(Scrolling, stream << "ScrollingTree::shouldHandleWheelEventSynchronously: wheelEvent at " << wheelEvent.position() << " mapped to content point " << position << ", in non-fast region " << isSynchronousDispatchRegion); + + if (isSynchronousDispatchRegion) + return true; + } + return false; +} + +void ScrollingTree::setOrClearLatchedNode(const PlatformWheelEvent& wheelEvent, ScrollingNodeID nodeID) +{ + if (wheelEvent.shouldConsiderLatching()) + setLatchedNode(nodeID); + else if (wheelEvent.shouldResetLatching()) + clearLatchedNode(); +} + +void ScrollingTree::handleWheelEvent(const PlatformWheelEvent& wheelEvent) +{ + if (m_rootNode) + downcast<ScrollingTreeScrollingNode>(*m_rootNode).handleWheelEvent(wheelEvent); +} + +void ScrollingTree::viewportChangedViaDelegatedScrolling(ScrollingNodeID nodeID, const FloatRect& fixedPositionRect, double scale) +{ + ScrollingTreeNode* node = nodeForID(nodeID); + if (!is<ScrollingTreeScrollingNode>(node)) + return; + + downcast<ScrollingTreeScrollingNode>(*node).updateLayersAfterViewportChange(fixedPositionRect, scale); +} + +void ScrollingTree::scrollPositionChangedViaDelegatedScrolling(ScrollingNodeID nodeID, const WebCore::FloatPoint& scrollPosition, bool inUserInteration) +{ + ScrollingTreeNode* node = nodeForID(nodeID); + if (!is<ScrollingTreeOverflowScrollingNode>(node)) + return; + + // Update descendant nodes + downcast<ScrollingTreeOverflowScrollingNode>(*node).updateLayersAfterDelegatedScroll(scrollPosition); + + // Update GraphicsLayers and scroll state. + scrollingTreeNodeDidScroll(nodeID, scrollPosition, std::nullopt, inUserInteration ? ScrollingLayerPositionAction::Sync : ScrollingLayerPositionAction::Set); +} + +void ScrollingTree::commitTreeState(std::unique_ptr<ScrollingStateTree> scrollingStateTree) +{ + bool rootStateNodeChanged = scrollingStateTree->hasNewRootStateNode(); + + ScrollingStateScrollingNode* rootNode = scrollingStateTree->rootStateNode(); + if (rootNode + && (rootStateNodeChanged + || rootNode->hasChangedProperty(ScrollingStateFrameScrollingNode::EventTrackingRegion) + || rootNode->hasChangedProperty(ScrollingStateNode::ScrollLayer) + || rootNode->hasChangedProperty(ScrollingStateFrameScrollingNode::VisualViewportEnabled))) { + LockHolder lock(m_mutex); + + if (rootStateNodeChanged || rootNode->hasChangedProperty(ScrollingStateNode::ScrollLayer)) + m_mainFrameScrollPosition = FloatPoint(); + + if (rootStateNodeChanged || rootNode->hasChangedProperty(ScrollingStateFrameScrollingNode::EventTrackingRegion)) + m_eventTrackingRegions = scrollingStateTree->rootStateNode()->eventTrackingRegions(); + + if (rootStateNodeChanged || rootNode->hasChangedProperty(ScrollingStateFrameScrollingNode::VisualViewportEnabled)) + m_visualViewportEnabled = scrollingStateTree->rootStateNode()->visualViewportEnabled(); + } + + bool scrollRequestIsProgammatic = rootNode ? rootNode->requestedScrollPositionRepresentsProgrammaticScroll() : false; + SetForScope<bool> changeHandlingProgrammaticScroll(m_isHandlingProgrammaticScroll, scrollRequestIsProgammatic); + + removeDestroyedNodes(*scrollingStateTree); + + OrphanScrollingNodeMap orphanNodes; + updateTreeFromStateNode(rootNode, orphanNodes); +} + +void ScrollingTree::updateTreeFromStateNode(const ScrollingStateNode* stateNode, OrphanScrollingNodeMap& orphanNodes) +{ + if (!stateNode) { + m_nodeMap.clear(); + m_rootNode = nullptr; + return; + } + + ScrollingNodeID nodeID = stateNode->scrollingNodeID(); + ScrollingNodeID parentNodeID = stateNode->parentNodeID(); + + auto it = m_nodeMap.find(nodeID); + + RefPtr<ScrollingTreeNode> node; + if (it != m_nodeMap.end()) + node = it->value; + else { + node = createScrollingTreeNode(stateNode->nodeType(), nodeID); + if (!parentNodeID) { + // This is the root node. Clear the node map. + ASSERT(stateNode->nodeType() == FrameScrollingNode); + m_rootNode = node; + m_nodeMap.clear(); + } + m_nodeMap.set(nodeID, node.get()); + } + + if (parentNodeID) { + auto parentIt = m_nodeMap.find(parentNodeID); + ASSERT_WITH_SECURITY_IMPLICATION(parentIt != m_nodeMap.end()); + if (parentIt != m_nodeMap.end()) { + ScrollingTreeNode* parent = parentIt->value; + node->setParent(parent); + parent->appendChild(*node); + } + } + + node->commitStateBeforeChildren(*stateNode); + + // Move all children into the orphanNodes map. Live ones will get added back as we recurse over children. + if (auto nodeChildren = node->children()) { + for (auto& childScrollingNode : *nodeChildren) { + childScrollingNode->setParent(nullptr); + orphanNodes.add(childScrollingNode->scrollingNodeID(), childScrollingNode.get()); + } + nodeChildren->clear(); + } + + // Now update the children if we have any. + if (auto children = stateNode->children()) { + for (auto& child : *children) + updateTreeFromStateNode(child.get(), orphanNodes); + } + + node->commitStateAfterChildren(*stateNode); +} + +void ScrollingTree::removeDestroyedNodes(const ScrollingStateTree& stateTree) +{ + for (const auto& removedNodeID : stateTree.removedNodes()) { + m_nodeMap.remove(removedNodeID); + if (removedNodeID == m_latchedNode) + clearLatchedNode(); + } +} + +ScrollingTreeNode* ScrollingTree::nodeForID(ScrollingNodeID nodeID) const +{ + if (!nodeID) + return nullptr; + + return m_nodeMap.get(nodeID); +} + +void ScrollingTree::setMainFramePinState(bool pinnedToTheLeft, bool pinnedToTheRight, bool pinnedToTheTop, bool pinnedToTheBottom) +{ + LockHolder locker(m_swipeStateMutex); + + m_mainFramePinnedToTheLeft = pinnedToTheLeft; + m_mainFramePinnedToTheRight = pinnedToTheRight; + m_mainFramePinnedToTheTop = pinnedToTheTop; + m_mainFramePinnedToTheBottom = pinnedToTheBottom; +} + +FloatPoint ScrollingTree::mainFrameScrollPosition() +{ + LockHolder lock(m_mutex); + return m_mainFrameScrollPosition; +} + +void ScrollingTree::setMainFrameScrollPosition(FloatPoint position) +{ + LockHolder lock(m_mutex); + m_mainFrameScrollPosition = position; +} + +TrackingType ScrollingTree::eventTrackingTypeForPoint(const AtomicString& eventName, IntPoint p) +{ + LockHolder lock(m_mutex); + + return m_eventTrackingRegions.trackingTypeForPoint(eventName, p); +} + +bool ScrollingTree::isRubberBandInProgress() +{ + LockHolder lock(m_mutex); + + return m_mainFrameIsRubberBanding; +} + +void ScrollingTree::setMainFrameIsRubberBanding(bool isRubberBanding) +{ + LockHolder locker(m_mutex); + + m_mainFrameIsRubberBanding = isRubberBanding; +} + +bool ScrollingTree::isScrollSnapInProgress() +{ + LockHolder lock(m_mutex); + + return m_mainFrameIsScrollSnapping; +} + +void ScrollingTree::setMainFrameIsScrollSnapping(bool isScrollSnapping) +{ + LockHolder locker(m_mutex); + + m_mainFrameIsScrollSnapping = isScrollSnapping; +} + +void ScrollingTree::setCanRubberBandState(bool canRubberBandAtLeft, bool canRubberBandAtRight, bool canRubberBandAtTop, bool canRubberBandAtBottom) +{ + LockHolder locker(m_swipeStateMutex); + + m_rubberBandsAtLeft = canRubberBandAtLeft; + m_rubberBandsAtRight = canRubberBandAtRight; + m_rubberBandsAtTop = canRubberBandAtTop; + m_rubberBandsAtBottom = canRubberBandAtBottom; +} + +bool ScrollingTree::rubberBandsAtLeft() +{ + LockHolder lock(m_swipeStateMutex); + + return m_rubberBandsAtLeft; +} + +bool ScrollingTree::rubberBandsAtRight() +{ + LockHolder lock(m_swipeStateMutex); + + return m_rubberBandsAtRight; +} + +bool ScrollingTree::rubberBandsAtBottom() +{ + LockHolder lock(m_swipeStateMutex); + + return m_rubberBandsAtBottom; +} + +bool ScrollingTree::rubberBandsAtTop() +{ + LockHolder lock(m_swipeStateMutex); + + return m_rubberBandsAtTop; +} + +bool ScrollingTree::isHandlingProgrammaticScroll() +{ + return m_isHandlingProgrammaticScroll; +} + +void ScrollingTree::setScrollPinningBehavior(ScrollPinningBehavior pinning) +{ + LockHolder locker(m_swipeStateMutex); + + m_scrollPinningBehavior = pinning; +} + +ScrollPinningBehavior ScrollingTree::scrollPinningBehavior() +{ + LockHolder lock(m_swipeStateMutex); + + return m_scrollPinningBehavior; +} + +bool ScrollingTree::willWheelEventStartSwipeGesture(const PlatformWheelEvent& wheelEvent) +{ + if (wheelEvent.phase() != PlatformWheelEventPhaseBegan) + return false; + + LockHolder lock(m_swipeStateMutex); + + if (wheelEvent.deltaX() > 0 && m_mainFramePinnedToTheLeft && !m_rubberBandsAtLeft) + return true; + if (wheelEvent.deltaX() < 0 && m_mainFramePinnedToTheRight && !m_rubberBandsAtRight) + return true; + if (wheelEvent.deltaY() > 0 && m_mainFramePinnedToTheTop && !m_rubberBandsAtTop) + return true; + if (wheelEvent.deltaY() < 0 && m_mainFramePinnedToTheBottom && !m_rubberBandsAtBottom) + return true; + + return false; +} + +void ScrollingTree::setScrollingPerformanceLoggingEnabled(bool flag) +{ + m_scrollingPerformanceLoggingEnabled = flag; +} + +bool ScrollingTree::scrollingPerformanceLoggingEnabled() +{ + return m_scrollingPerformanceLoggingEnabled; +} + +ScrollingNodeID ScrollingTree::latchedNode() +{ + LockHolder locker(m_mutex); + return m_latchedNode; +} + +void ScrollingTree::setLatchedNode(ScrollingNodeID node) +{ + LockHolder locker(m_mutex); + m_latchedNode = node; +} + +void ScrollingTree::clearLatchedNode() +{ + LockHolder locker(m_mutex); + m_latchedNode = 0; +} + +String ScrollingTree::scrollingTreeAsText() +{ + TextStream ts(TextStream::LineMode::MultipleLine); + + TextStream::GroupScope scope(ts); + ts << "scrolling tree"; + + if (m_latchedNode) + ts.dumpProperty("latched node", m_latchedNode); + + if (m_mainFrameScrollPosition != IntPoint()) + ts.dumpProperty("main frame scroll position", m_mainFrameScrollPosition); + + { + LockHolder lock(m_mutex); + if (m_rootNode) { + TextStream::GroupScope scope(ts); + m_rootNode->dump(ts, ScrollingStateTreeAsTextBehaviorIncludeLayerPositions); + } + } + + return ts.release(); +} + +} // namespace WebCore + +#endif // ENABLE(ASYNC_SCROLLING) diff --git a/Source/WebCore/page/scrolling/ScrollingTree.h b/Source/WebCore/page/scrolling/ScrollingTree.h new file mode 100644 index 000000000..aaf2d478b --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollingTree.h @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2012-2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(ASYNC_SCROLLING) + +#include "PlatformWheelEvent.h" +#include "Region.h" +#include "ScrollingCoordinator.h" +#include "WheelEventTestTrigger.h" +#include <wtf/HashMap.h> +#include <wtf/Lock.h> +#include <wtf/ThreadSafeRefCounted.h> +#include <wtf/TypeCasts.h> + +namespace WebCore { + +class IntPoint; +class ScrollingStateTree; +class ScrollingStateNode; +class ScrollingTreeNode; +class ScrollingTreeScrollingNode; + +class ScrollingTree : public ThreadSafeRefCounted<ScrollingTree> { +public: + WEBCORE_EXPORT ScrollingTree(); + WEBCORE_EXPORT virtual ~ScrollingTree(); + + enum EventResult { + DidNotHandleEvent, + DidHandleEvent, + SendToMainThread + }; + + virtual bool isThreadedScrollingTree() const { return false; } + virtual bool isRemoteScrollingTree() const { return false; } + virtual bool isScrollingTreeIOS() const { return false; } + + bool visualViewportEnabled() const { return m_visualViewportEnabled; } + + virtual EventResult tryToHandleWheelEvent(const PlatformWheelEvent&) = 0; + WEBCORE_EXPORT bool shouldHandleWheelEventSynchronously(const PlatformWheelEvent&); + + void setMainFrameIsRubberBanding(bool); + bool isRubberBandInProgress(); + void setMainFrameIsScrollSnapping(bool); + bool isScrollSnapInProgress(); + + virtual void invalidate() { } + WEBCORE_EXPORT virtual void commitTreeState(std::unique_ptr<ScrollingStateTree>); + + void setMainFramePinState(bool pinnedToTheLeft, bool pinnedToTheRight, bool pinnedToTheTop, bool pinnedToTheBottom); + + virtual Ref<ScrollingTreeNode> createScrollingTreeNode(ScrollingNodeType, ScrollingNodeID) = 0; + + // Called after a scrolling tree node has handled a scroll and updated its layers. + // Updates FrameView/RenderLayer scrolling state and GraphicsLayers. + virtual void scrollingTreeNodeDidScroll(ScrollingNodeID, const FloatPoint& scrollPosition, const std::optional<FloatPoint>& layoutViewportOrigin, ScrollingLayerPositionAction = ScrollingLayerPositionAction::Sync) = 0; + + // Called for requested scroll position updates. + virtual void scrollingTreeNodeRequestsScroll(ScrollingNodeID, const FloatPoint& /*scrollPosition*/, bool /*representsProgrammaticScroll*/) { } + + // Delegated scrolling/zooming has caused the viewport to change, so update viewport-constrained layers + // (but don't cause scroll events to be fired). + WEBCORE_EXPORT virtual void viewportChangedViaDelegatedScrolling(ScrollingNodeID, const WebCore::FloatRect& fixedPositionRect, double scale); + + // Delegated scrolling has scrolled a node. Update layer positions on descendant tree nodes, + // and call scrollingTreeNodeDidScroll(). + WEBCORE_EXPORT virtual void scrollPositionChangedViaDelegatedScrolling(ScrollingNodeID, const WebCore::FloatPoint& scrollPosition, bool inUserInteration); + + WEBCORE_EXPORT virtual void currentSnapPointIndicesDidChange(ScrollingNodeID, unsigned horizontal, unsigned vertical) = 0; + + FloatPoint mainFrameScrollPosition(); + +#if PLATFORM(IOS) + virtual FloatRect fixedPositionRect() = 0; + virtual void scrollingTreeNodeWillStartPanGesture() { } + virtual void scrollingTreeNodeWillStartScroll() { } + virtual void scrollingTreeNodeDidEndScroll() { } +#endif + + WEBCORE_EXPORT TrackingType eventTrackingTypeForPoint(const AtomicString& eventName, IntPoint); + +#if PLATFORM(MAC) + virtual void handleWheelEventPhase(PlatformWheelEventPhase) = 0; + virtual void setActiveScrollSnapIndices(ScrollingNodeID, unsigned /*horizontalIndex*/, unsigned /*verticalIndex*/) { } + virtual void deferTestsForReason(WheelEventTestTrigger::ScrollableAreaIdentifier, WheelEventTestTrigger::DeferTestTriggerReason) { } + virtual void removeTestDeferralForReason(WheelEventTestTrigger::ScrollableAreaIdentifier, WheelEventTestTrigger::DeferTestTriggerReason) { } +#endif + + // Can be called from any thread. Will update what edges allow rubber-banding. + WEBCORE_EXPORT void setCanRubberBandState(bool canRubberBandAtLeft, bool canRubberBandAtRight, bool canRubberBandAtTop, bool canRubberBandAtBottom); + + bool rubberBandsAtLeft(); + bool rubberBandsAtRight(); + bool rubberBandsAtTop(); + bool rubberBandsAtBottom(); + bool isHandlingProgrammaticScroll(); + + void setScrollPinningBehavior(ScrollPinningBehavior); + ScrollPinningBehavior scrollPinningBehavior(); + + WEBCORE_EXPORT bool willWheelEventStartSwipeGesture(const PlatformWheelEvent&); + + WEBCORE_EXPORT void setScrollingPerformanceLoggingEnabled(bool flag); + bool scrollingPerformanceLoggingEnabled(); + + ScrollingTreeNode* rootNode() const { return m_rootNode.get(); } + + ScrollingNodeID latchedNode(); + void setLatchedNode(ScrollingNodeID); + void clearLatchedNode(); + + bool hasLatchedNode() const { return m_latchedNode; } + void setOrClearLatchedNode(const PlatformWheelEvent&, ScrollingNodeID); + + bool hasFixedOrSticky() const { return !!m_fixedOrStickyNodeCount; } + void fixedOrStickyNodeAdded() { ++m_fixedOrStickyNodeCount; } + void fixedOrStickyNodeRemoved() + { + ASSERT(m_fixedOrStickyNodeCount); + --m_fixedOrStickyNodeCount; + } + + WEBCORE_EXPORT String scrollingTreeAsText(); + +protected: + void setMainFrameScrollPosition(FloatPoint); + void setVisualViewportEnabled(bool b) { m_visualViewportEnabled = b; } + + WEBCORE_EXPORT virtual void handleWheelEvent(const PlatformWheelEvent&); + +private: + void removeDestroyedNodes(const ScrollingStateTree&); + + typedef HashMap<ScrollingNodeID, RefPtr<ScrollingTreeNode>> OrphanScrollingNodeMap; + void updateTreeFromStateNode(const ScrollingStateNode*, OrphanScrollingNodeMap&); + + ScrollingTreeNode* nodeForID(ScrollingNodeID) const; + + RefPtr<ScrollingTreeNode> m_rootNode; + + typedef HashMap<ScrollingNodeID, ScrollingTreeNode*> ScrollingTreeNodeMap; + ScrollingTreeNodeMap m_nodeMap; + + Lock m_mutex; + EventTrackingRegions m_eventTrackingRegions; + FloatPoint m_mainFrameScrollPosition; + + Lock m_swipeStateMutex; + ScrollPinningBehavior m_scrollPinningBehavior { DoNotPin }; + ScrollingNodeID m_latchedNode { 0 }; + + unsigned m_fixedOrStickyNodeCount { 0 }; + + bool m_rubberBandsAtLeft { true }; + bool m_rubberBandsAtRight { true }; + bool m_rubberBandsAtTop { true }; + bool m_rubberBandsAtBottom { true }; + bool m_mainFramePinnedToTheLeft { true }; + bool m_mainFramePinnedToTheRight { true }; + bool m_mainFramePinnedToTheTop { true }; + bool m_mainFramePinnedToTheBottom { true }; + bool m_mainFrameIsRubberBanding { false }; + bool m_mainFrameIsScrollSnapping { false }; + bool m_scrollingPerformanceLoggingEnabled { false }; + bool m_isHandlingProgrammaticScroll { false }; + bool m_visualViewportEnabled { false }; +}; + +} // namespace WebCore + +#define SPECIALIZE_TYPE_TRAITS_SCROLLING_TREE(ToValueTypeName, predicate) \ +SPECIALIZE_TYPE_TRAITS_BEGIN(ToValueTypeName) \ + static bool isType(const WebCore::ScrollingTree& tree) { return tree.predicate; } \ +SPECIALIZE_TYPE_TRAITS_END() +#endif // ENABLE(ASYNC_SCROLLING) diff --git a/Source/WebCore/page/scrolling/ScrollingTreeFrameScrollingNode.cpp b/Source/WebCore/page/scrolling/ScrollingTreeFrameScrollingNode.cpp new file mode 100644 index 000000000..17ac7a431 --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollingTreeFrameScrollingNode.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ScrollingTreeFrameScrollingNode.h" + +#if ENABLE(ASYNC_SCROLLING) + +#include "FrameView.h" +#include "Logging.h" +#include "ScrollingStateTree.h" +#include "ScrollingTree.h" +#include "TextStream.h" + +namespace WebCore { + +ScrollingTreeFrameScrollingNode::ScrollingTreeFrameScrollingNode(ScrollingTree& scrollingTree, ScrollingNodeID nodeID) + : ScrollingTreeScrollingNode(scrollingTree, FrameScrollingNode, nodeID) +{ +} + +ScrollingTreeFrameScrollingNode::~ScrollingTreeFrameScrollingNode() +{ +} + +void ScrollingTreeFrameScrollingNode::commitStateBeforeChildren(const ScrollingStateNode& stateNode) +{ + ScrollingTreeScrollingNode::commitStateBeforeChildren(stateNode); + + const ScrollingStateFrameScrollingNode& state = downcast<ScrollingStateFrameScrollingNode>(stateNode); + + if (state.hasChangedProperty(ScrollingStateFrameScrollingNode::FrameScaleFactor)) + m_frameScaleFactor = state.frameScaleFactor(); + + if (state.hasChangedProperty(ScrollingStateFrameScrollingNode::ReasonsForSynchronousScrolling)) + m_synchronousScrollingReasons = state.synchronousScrollingReasons(); + + if (state.hasChangedProperty(ScrollingStateFrameScrollingNode::HeaderHeight)) + m_headerHeight = state.headerHeight(); + + if (state.hasChangedProperty(ScrollingStateFrameScrollingNode::FooterHeight)) + m_footerHeight = state.footerHeight(); + + if (state.hasChangedProperty(ScrollingStateFrameScrollingNode::BehaviorForFixedElements)) + m_behaviorForFixed = state.scrollBehaviorForFixedElements(); + + if (state.hasChangedProperty(ScrollingStateFrameScrollingNode::TopContentInset)) + m_topContentInset = state.topContentInset(); + + if (state.hasChangedProperty(ScrollingStateFrameScrollingNode::FixedElementsLayoutRelativeToFrame)) + m_fixedElementsLayoutRelativeToFrame = state.fixedElementsLayoutRelativeToFrame(); + + if (state.hasChangedProperty(ScrollingStateFrameScrollingNode::LayoutViewport)) + m_layoutViewport = state.layoutViewport(); + + if (state.hasChangedProperty(ScrollingStateFrameScrollingNode::MinLayoutViewportOrigin)) + m_minLayoutViewportOrigin = state.minLayoutViewportOrigin(); + + if (state.hasChangedProperty(ScrollingStateFrameScrollingNode::MaxLayoutViewportOrigin)) + m_maxLayoutViewportOrigin = state.maxLayoutViewportOrigin(); +} + +void ScrollingTreeFrameScrollingNode::scrollBy(const FloatSize& delta) +{ + setScrollPosition(scrollPosition() + delta); +} + +void ScrollingTreeFrameScrollingNode::scrollByWithoutContentEdgeConstraints(const FloatSize& offset) +{ + setScrollPositionWithoutContentEdgeConstraints(scrollPosition() + offset); +} + +void ScrollingTreeFrameScrollingNode::setScrollPosition(const FloatPoint& scrollPosition) +{ + FloatPoint newScrollPosition = scrollPosition.constrainedBetween(minimumScrollPosition(), maximumScrollPosition()); + setScrollPositionWithoutContentEdgeConstraints(newScrollPosition); +} + +FloatRect ScrollingTreeFrameScrollingNode::layoutViewportForScrollPosition(const FloatPoint& visibleContentOrigin, float scale) const +{ + ASSERT(scrollingTree().visualViewportEnabled()); + + FloatRect visibleContentRect(visibleContentOrigin, scrollableAreaSize()); + LayoutRect visualViewport(FrameView::visibleDocumentRect(visibleContentRect, headerHeight(), footerHeight(), totalContentsSize(), scale)); + LayoutRect layoutViewport(m_layoutViewport); + + LOG_WITH_STREAM(Scrolling, stream << "\nScrolling thread: " << "(visibleContentOrigin " << visibleContentOrigin << ") fixed behavior " << m_behaviorForFixed); + LOG_WITH_STREAM(Scrolling, stream << " layoutViewport: " << layoutViewport); + LOG_WITH_STREAM(Scrolling, stream << " visualViewport: " << visualViewport); + LOG_WITH_STREAM(Scrolling, stream << " scroll positions: min: " << minLayoutViewportOrigin() << " max: "<< maxLayoutViewportOrigin()); + + LayoutPoint newLocation = FrameView::computeLayoutViewportOrigin(LayoutRect(visualViewport), LayoutPoint(minLayoutViewportOrigin()), LayoutPoint(maxLayoutViewportOrigin()), layoutViewport, m_behaviorForFixed); + + if (layoutViewport.location() != newLocation) { + layoutViewport.setLocation(newLocation); + LOG_WITH_STREAM(Scrolling, stream << " new layoutViewport " << layoutViewport); + } + + return layoutViewport; +} + +FloatSize ScrollingTreeFrameScrollingNode::viewToContentsOffset(const FloatPoint& scrollPosition) const +{ + return toFloatSize(scrollPosition) - FloatSize(0, headerHeight() + topContentInset()); +} + +void ScrollingTreeFrameScrollingNode::dumpProperties(TextStream& ts, ScrollingStateTreeAsTextBehavior) const +{ + ts << "frame scrolling node"; + + ts.dumpProperty("layout viewport", m_layoutViewport); + ts.dumpProperty("min layoutViewport origin", m_minLayoutViewportOrigin); + ts.dumpProperty("max layoutViewport origin", m_maxLayoutViewportOrigin); + + if (m_frameScaleFactor != 1) + ts.dumpProperty("frame scale factor", m_frameScaleFactor); + if (m_topContentInset) + ts.dumpProperty("top content inset", m_topContentInset); + + if (m_headerHeight) + ts.dumpProperty("header height", m_headerHeight); + if (m_footerHeight) + ts.dumpProperty("footer height", m_footerHeight); + if (m_synchronousScrollingReasons) + ts.dumpProperty("synchronous scrolling reasons", ScrollingCoordinator::synchronousScrollingReasonsAsText(m_synchronousScrollingReasons)); + + ts.dumpProperty("behavior for fixed", m_behaviorForFixed); + if (m_fixedElementsLayoutRelativeToFrame) + ts.dumpProperty("fixed elements lay out relative to frame", m_fixedElementsLayoutRelativeToFrame); +} + + +} // namespace WebCore + +#endif // ENABLE(ASYNC_SCROLLING) diff --git a/Source/WebCore/page/scrolling/ScrollingTreeFrameScrollingNode.h b/Source/WebCore/page/scrolling/ScrollingTreeFrameScrollingNode.h new file mode 100644 index 000000000..43488c4fa --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollingTreeFrameScrollingNode.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(ASYNC_SCROLLING) + +#include "ScrollingTreeScrollingNode.h" + +namespace WebCore { + +class PlatformWheelEvent; +class ScrollingTree; +class ScrollingStateScrollingNode; + +class ScrollingTreeFrameScrollingNode : public ScrollingTreeScrollingNode { +public: + virtual ~ScrollingTreeFrameScrollingNode(); + + void commitStateBeforeChildren(const ScrollingStateNode&) override; + + // FIXME: We should implement this when we support ScrollingTreeScrollingNodes as children. + void updateLayersAfterAncestorChange(const ScrollingTreeNode& /*changedNode*/, const FloatRect& /*fixedPositionRect*/, const FloatSize& /*cumulativeDelta*/) override { } + + void handleWheelEvent(const PlatformWheelEvent&) override = 0; + void setScrollPosition(const FloatPoint&) override; + void setScrollPositionWithoutContentEdgeConstraints(const FloatPoint&) override = 0; + + void updateLayersAfterViewportChange(const FloatRect& fixedPositionRect, double scale) override = 0; + void updateLayersAfterDelegatedScroll(const FloatPoint&) override { } + + SynchronousScrollingReasons synchronousScrollingReasons() const { return m_synchronousScrollingReasons; } + bool shouldUpdateScrollLayerPositionSynchronously() const { return m_synchronousScrollingReasons; } + bool fixedElementsLayoutRelativeToFrame() const { return m_fixedElementsLayoutRelativeToFrame; } + + FloatSize viewToContentsOffset(const FloatPoint& scrollPosition) const; + FloatRect layoutViewportForScrollPosition(const FloatPoint& visibleContentOrigin, float scale) const; + +protected: + ScrollingTreeFrameScrollingNode(ScrollingTree&, ScrollingNodeID); + + void scrollBy(const FloatSize&); + void scrollByWithoutContentEdgeConstraints(const FloatSize&); + + float frameScaleFactor() const { return m_frameScaleFactor; } + int headerHeight() const { return m_headerHeight; } + int footerHeight() const { return m_footerHeight; } + float topContentInset() const { return m_topContentInset; } + + FloatRect layoutViewport() const { return m_layoutViewport; }; + void setLayoutViewport(const FloatRect& r) { m_layoutViewport = r; }; + + FloatPoint minLayoutViewportOrigin() const { return m_minLayoutViewportOrigin; } + FloatPoint maxLayoutViewportOrigin() const { return m_maxLayoutViewportOrigin; } + + ScrollBehaviorForFixedElements scrollBehaviorForFixedElements() const { return m_behaviorForFixed; } + +private: + void dumpProperties(TextStream&, ScrollingStateTreeAsTextBehavior) const override; + + FloatRect m_layoutViewport; + FloatPoint m_minLayoutViewportOrigin; + FloatPoint m_maxLayoutViewportOrigin; + + float m_frameScaleFactor { 1 }; + float m_topContentInset { 0 }; + + int m_headerHeight { 0 }; + int m_footerHeight { 0 }; + + SynchronousScrollingReasons m_synchronousScrollingReasons { 0 }; + ScrollBehaviorForFixedElements m_behaviorForFixed { StickToDocumentBounds }; + + bool m_fixedElementsLayoutRelativeToFrame { false }; +}; + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_SCROLLING_NODE(ScrollingTreeFrameScrollingNode, isFrameScrollingNode()) + +#endif // ENABLE(ASYNC_SCROLLING) diff --git a/Source/WebCore/page/scrolling/ScrollingTreeNode.cpp b/Source/WebCore/page/scrolling/ScrollingTreeNode.cpp new file mode 100644 index 000000000..0f6616d0f --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollingTreeNode.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ScrollingTreeNode.h" + +#if ENABLE(ASYNC_SCROLLING) + +#include "ScrollingStateTree.h" +#include "TextStream.h" + +namespace WebCore { + +ScrollingTreeNode::ScrollingTreeNode(ScrollingTree& scrollingTree, ScrollingNodeType nodeType, ScrollingNodeID nodeID) + : m_scrollingTree(scrollingTree) + , m_nodeType(nodeType) + , m_nodeID(nodeID) + , m_parent(nullptr) +{ +} + +ScrollingTreeNode::~ScrollingTreeNode() +{ +} + +void ScrollingTreeNode::appendChild(Ref<ScrollingTreeNode>&& childNode) +{ + childNode->setParent(this); + + if (!m_children) + m_children = std::make_unique<Vector<RefPtr<ScrollingTreeNode>>>(); + m_children->append(WTFMove(childNode)); +} + +void ScrollingTreeNode::removeChild(ScrollingTreeNode& node) +{ + if (!m_children) + return; + + size_t index = m_children->find(&node); + + // The index will be notFound if the node to remove is a deeper-than-1-level descendant or + // if node is the root state node. + if (index != notFound) { + m_children->remove(index); + return; + } + + for (auto& child : *m_children) + child->removeChild(node); +} + +void ScrollingTreeNode::dumpProperties(TextStream& ts, ScrollingStateTreeAsTextBehavior behavior) const +{ + if (behavior & ScrollingStateTreeAsTextBehaviorIncludeNodeIDs) + ts.dumpProperty("nodeID", scrollingNodeID()); +} + +void ScrollingTreeNode::dump(TextStream& ts, ScrollingStateTreeAsTextBehavior behavior) const +{ + dumpProperties(ts, behavior); + + if (m_children) { + for (auto& child : *m_children) { + TextStream::GroupScope scope(ts); + child->dump(ts, behavior); + } + } +} + +} // namespace WebCore + +#endif // ENABLE(ASYNC_SCROLLING) diff --git a/Source/WebCore/page/scrolling/ScrollingTreeNode.h b/Source/WebCore/page/scrolling/ScrollingTreeNode.h new file mode 100644 index 000000000..2e163246b --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollingTreeNode.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2012 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(ASYNC_SCROLLING) + +#include "IntRect.h" +#include "ScrollTypes.h" +#include "ScrollingCoordinator.h" +#include "ScrollingStateNode.h" +#include <wtf/RefCounted.h> +#include <wtf/TypeCasts.h> + +namespace WebCore { + +class ScrollingStateFixedNode; +class ScrollingStateScrollingNode; + +class ScrollingTreeNode : public RefCounted<ScrollingTreeNode> { +public: + virtual ~ScrollingTreeNode(); + + ScrollingNodeType nodeType() const { return m_nodeType; } + ScrollingNodeID scrollingNodeID() const { return m_nodeID; } + + bool isFixedNode() const { return nodeType() == FixedNode; } + bool isStickyNode() const { return nodeType() == StickyNode; } + bool isScrollingNode() const { return nodeType() == FrameScrollingNode || nodeType() == OverflowScrollingNode; } + bool isFrameScrollingNode() const { return nodeType() == FrameScrollingNode; } + bool isOverflowScrollingNode() const { return nodeType() == OverflowScrollingNode; } + + virtual void commitStateBeforeChildren(const ScrollingStateNode&) = 0; + virtual void commitStateAfterChildren(const ScrollingStateNode&) { } + + virtual void updateLayersAfterAncestorChange(const ScrollingTreeNode& changedNode, const FloatRect& fixedPositionRect, const FloatSize& cumulativeDelta) = 0; + + ScrollingTreeNode* parent() const { return m_parent; } + void setParent(ScrollingTreeNode* parent) { m_parent = parent; } + + Vector<RefPtr<ScrollingTreeNode>>* children() { return m_children.get(); } + + void appendChild(Ref<ScrollingTreeNode>&&); + void removeChild(ScrollingTreeNode&); + + WEBCORE_EXPORT void dump(TextStream&, ScrollingStateTreeAsTextBehavior) const; + +protected: + ScrollingTreeNode(ScrollingTree&, ScrollingNodeType, ScrollingNodeID); + ScrollingTree& scrollingTree() const { return m_scrollingTree; } + + std::unique_ptr<Vector<RefPtr<ScrollingTreeNode>>> m_children; + + WEBCORE_EXPORT virtual void dumpProperties(TextStream&, ScrollingStateTreeAsTextBehavior) const; + +private: + ScrollingTree& m_scrollingTree; + + const ScrollingNodeType m_nodeType; + const ScrollingNodeID m_nodeID; + + ScrollingTreeNode* m_parent; +}; + +} // namespace WebCore + +#define SPECIALIZE_TYPE_TRAITS_SCROLLING_NODE(ToValueTypeName, predicate) \ +SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::ToValueTypeName) \ + static bool isType(const WebCore::ScrollingTreeNode& node) { return node.predicate; } \ +SPECIALIZE_TYPE_TRAITS_END() + +#endif // ENABLE(ASYNC_SCROLLING) diff --git a/Source/WebCore/page/scrolling/ScrollingTreeOverflowScrollingNode.cpp b/Source/WebCore/page/scrolling/ScrollingTreeOverflowScrollingNode.cpp new file mode 100644 index 000000000..013e350e6 --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollingTreeOverflowScrollingNode.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ScrollingTreeOverflowScrollingNode.h" + +#if ENABLE(ASYNC_SCROLLING) + +#include "ScrollingStateTree.h" +#include "ScrollingTree.h" + +namespace WebCore { + +ScrollingTreeOverflowScrollingNode::ScrollingTreeOverflowScrollingNode(ScrollingTree& scrollingTree, ScrollingNodeID nodeID) + : ScrollingTreeScrollingNode(scrollingTree, OverflowScrollingNode, nodeID) +{ +} + +ScrollingTreeOverflowScrollingNode::~ScrollingTreeOverflowScrollingNode() +{ +} + +} // namespace WebCore + +#endif // ENABLE(ASYNC_SCROLLING) diff --git a/Source/WebCore/page/scrolling/ScrollingTreeOverflowScrollingNode.h b/Source/WebCore/page/scrolling/ScrollingTreeOverflowScrollingNode.h new file mode 100644 index 000000000..97fb95cc4 --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollingTreeOverflowScrollingNode.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(ASYNC_SCROLLING) + +#include "ScrollingTreeScrollingNode.h" + +namespace WebCore { + +class ScrollingTreeOverflowScrollingNode : public ScrollingTreeScrollingNode { +public: + WEBCORE_EXPORT virtual ~ScrollingTreeOverflowScrollingNode(); + +protected: + WEBCORE_EXPORT ScrollingTreeOverflowScrollingNode(ScrollingTree&, ScrollingNodeID); +}; + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_SCROLLING_NODE(ScrollingTreeOverflowScrollingNode, isOverflowScrollingNode()) + +#endif // ENABLE(ASYNC_SCROLLING) diff --git a/Source/WebCore/page/scrolling/ScrollingTreeScrollingNode.cpp b/Source/WebCore/page/scrolling/ScrollingTreeScrollingNode.cpp new file mode 100644 index 000000000..19754f5c2 --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollingTreeScrollingNode.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2012 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ScrollingTreeScrollingNode.h" + +#if ENABLE(ASYNC_SCROLLING) + +#include "ScrollingStateTree.h" +#include "ScrollingTree.h" +#include "TextStream.h" + +namespace WebCore { + +ScrollingTreeScrollingNode::ScrollingTreeScrollingNode(ScrollingTree& scrollingTree, ScrollingNodeType nodeType, ScrollingNodeID nodeID) + : ScrollingTreeNode(scrollingTree, nodeType, nodeID) +{ +} + +ScrollingTreeScrollingNode::~ScrollingTreeScrollingNode() +{ +} + +void ScrollingTreeScrollingNode::commitStateBeforeChildren(const ScrollingStateNode& stateNode) +{ + const ScrollingStateScrollingNode& state = downcast<ScrollingStateScrollingNode>(stateNode); + + if (state.hasChangedProperty(ScrollingStateScrollingNode::ScrollableAreaSize)) + m_scrollableAreaSize = state.scrollableAreaSize(); + + if (state.hasChangedProperty(ScrollingStateScrollingNode::TotalContentsSize)) { + if (scrollingTree().isRubberBandInProgress()) + m_totalContentsSizeForRubberBand = m_totalContentsSize; + else + m_totalContentsSizeForRubberBand = state.totalContentsSize(); + + m_totalContentsSize = state.totalContentsSize(); + } + + if (state.hasChangedProperty(ScrollingStateScrollingNode::ReachableContentsSize)) + m_reachableContentsSize = state.reachableContentsSize(); + + if (state.hasChangedProperty(ScrollingStateScrollingNode::ScrollPosition)) + m_lastCommittedScrollPosition = state.scrollPosition(); + + if (state.hasChangedProperty(ScrollingStateScrollingNode::ScrollOrigin)) + m_scrollOrigin = state.scrollOrigin(); + +#if ENABLE(CSS_SCROLL_SNAP) + if (state.hasChangedProperty(ScrollingStateScrollingNode::HorizontalSnapOffsets)) + m_snapOffsetsInfo.horizontalSnapOffsets = state.horizontalSnapOffsets(); + + if (state.hasChangedProperty(ScrollingStateScrollingNode::VerticalSnapOffsets)) + m_snapOffsetsInfo.verticalSnapOffsets = state.verticalSnapOffsets(); + + if (state.hasChangedProperty(ScrollingStateScrollingNode::HorizontalSnapOffsetRanges)) + m_snapOffsetsInfo.horizontalSnapOffsetRanges = state.horizontalSnapOffsetRanges(); + + if (state.hasChangedProperty(ScrollingStateScrollingNode::VerticalSnapOffsetRanges)) + m_snapOffsetsInfo.verticalSnapOffsetRanges = state.verticalSnapOffsetRanges(); + + if (state.hasChangedProperty(ScrollingStateScrollingNode::CurrentHorizontalSnapOffsetIndex)) + m_currentHorizontalSnapPointIndex = state.currentHorizontalSnapPointIndex(); + + if (state.hasChangedProperty(ScrollingStateScrollingNode::CurrentVerticalSnapOffsetIndex)) + m_currentVerticalSnapPointIndex = state.currentVerticalSnapPointIndex(); +#endif + + if (state.hasChangedProperty(ScrollingStateScrollingNode::ScrollableAreaParams)) + m_scrollableAreaParameters = state.scrollableAreaParameters(); +} + +void ScrollingTreeScrollingNode::commitStateAfterChildren(const ScrollingStateNode& stateNode) +{ + const ScrollingStateScrollingNode& scrollingStateNode = downcast<ScrollingStateScrollingNode>(stateNode); + if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::RequestedScrollPosition)) + scrollingTree().scrollingTreeNodeRequestsScroll(scrollingNodeID(), scrollingStateNode.requestedScrollPosition(), scrollingStateNode.requestedScrollPositionRepresentsProgrammaticScroll()); +} + +void ScrollingTreeScrollingNode::updateLayersAfterAncestorChange(const ScrollingTreeNode& changedNode, const FloatRect& fixedPositionRect, const FloatSize& cumulativeDelta) +{ + if (!m_children) + return; + + for (auto& child : *m_children) + child->updateLayersAfterAncestorChange(changedNode, fixedPositionRect, cumulativeDelta); +} + +void ScrollingTreeScrollingNode::setScrollPosition(const FloatPoint& scrollPosition) +{ + FloatPoint newScrollPosition = scrollPosition.constrainedBetween(minimumScrollPosition(), maximumScrollPosition()); + setScrollPositionWithoutContentEdgeConstraints(newScrollPosition); +} + +void ScrollingTreeScrollingNode::setScrollPositionWithoutContentEdgeConstraints(const FloatPoint& scrollPosition) +{ + setScrollLayerPosition(scrollPosition, { }); + scrollingTree().scrollingTreeNodeDidScroll(scrollingNodeID(), scrollPosition, std::nullopt); +} + +FloatPoint ScrollingTreeScrollingNode::minimumScrollPosition() const +{ + return FloatPoint(); +} + +FloatPoint ScrollingTreeScrollingNode::maximumScrollPosition() const +{ + FloatPoint contentSizePoint(totalContentsSize()); + return FloatPoint(contentSizePoint - scrollableAreaSize()).expandedTo(FloatPoint()); +} + +void ScrollingTreeScrollingNode::dumpProperties(TextStream& ts, ScrollingStateTreeAsTextBehavior behavior) const +{ + ScrollingTreeNode::dumpProperties(ts, behavior); + ts.dumpProperty("scrollable area size", m_scrollableAreaSize); + ts.dumpProperty("total content size", m_totalContentsSize); + if (m_totalContentsSizeForRubberBand != m_totalContentsSize) + ts.dumpProperty("total content size for rubber band", m_totalContentsSizeForRubberBand); + if (m_reachableContentsSize != m_totalContentsSize) + ts.dumpProperty("reachable content size", m_reachableContentsSize); + ts.dumpProperty("scrollable area size", m_lastCommittedScrollPosition); + if (m_scrollOrigin != IntPoint()) + ts.dumpProperty("scrollable area size", m_scrollOrigin); + +#if ENABLE(CSS_SCROLL_SNAP) + if (m_snapOffsetsInfo.horizontalSnapOffsets.size()) + ts.dumpProperty("horizontal snap offsets", m_snapOffsetsInfo.horizontalSnapOffsets); + + if (m_snapOffsetsInfo.verticalSnapOffsets.size()) + ts.dumpProperty("horizontal snap offsets", m_snapOffsetsInfo.verticalSnapOffsets); + + if (m_currentHorizontalSnapPointIndex) + ts.dumpProperty("current horizontal snap point index", m_currentHorizontalSnapPointIndex); + + if (m_currentVerticalSnapPointIndex) + ts.dumpProperty("current vertical snap point index", m_currentVerticalSnapPointIndex); + +#endif +} + +} // namespace WebCore + +#endif // ENABLE(ASYNC_SCROLLING) diff --git a/Source/WebCore/page/scrolling/ScrollingTreeScrollingNode.h b/Source/WebCore/page/scrolling/ScrollingTreeScrollingNode.h new file mode 100644 index 000000000..3594b1086 --- /dev/null +++ b/Source/WebCore/page/scrolling/ScrollingTreeScrollingNode.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2012 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(ASYNC_SCROLLING) + +#include "IntRect.h" +#include "ScrollSnapOffsetsInfo.h" +#include "ScrollTypes.h" +#include "ScrollingCoordinator.h" +#include "ScrollingTreeNode.h" + +namespace WebCore { + +class ScrollingTree; +class ScrollingStateScrollingNode; + +class ScrollingTreeScrollingNode : public ScrollingTreeNode { +public: + virtual ~ScrollingTreeScrollingNode(); + + WEBCORE_EXPORT void commitStateBeforeChildren(const ScrollingStateNode&) override; + WEBCORE_EXPORT void commitStateAfterChildren(const ScrollingStateNode&) override; + + WEBCORE_EXPORT void updateLayersAfterAncestorChange(const ScrollingTreeNode& changedNode, const FloatRect& fixedPositionRect, const FloatSize& cumulativeDelta) override; + + virtual void handleWheelEvent(const PlatformWheelEvent&) = 0; + WEBCORE_EXPORT virtual void setScrollPosition(const FloatPoint&); + WEBCORE_EXPORT virtual void setScrollPositionWithoutContentEdgeConstraints(const FloatPoint&); + + virtual void updateLayersAfterViewportChange(const FloatRect& fixedPositionRect, double scale) = 0; + virtual void updateLayersAfterDelegatedScroll(const FloatPoint&) { } + + virtual FloatPoint scrollPosition() const = 0; + +#if ENABLE(CSS_SCROLL_SNAP) + const Vector<float>& horizontalSnapOffsets() const { return m_snapOffsetsInfo.horizontalSnapOffsets; } + const Vector<float>& verticalSnapOffsets() const { return m_snapOffsetsInfo.verticalSnapOffsets; } + const Vector<ScrollOffsetRange<float>>& horizontalSnapOffsetRanges() const { return m_snapOffsetsInfo.horizontalSnapOffsetRanges; } + const Vector<ScrollOffsetRange<float>>& verticalSnapOffsetRanges() const { return m_snapOffsetsInfo.verticalSnapOffsetRanges; } + unsigned currentHorizontalSnapPointIndex() const { return m_currentHorizontalSnapPointIndex; } + unsigned currentVerticalSnapPointIndex() const { return m_currentVerticalSnapPointIndex; } + void setCurrentHorizontalSnapPointIndex(unsigned index) { m_currentHorizontalSnapPointIndex = index; } + void setCurrentVerticalSnapPointIndex(unsigned index) { m_currentVerticalSnapPointIndex = index; } +#endif + +protected: + ScrollingTreeScrollingNode(ScrollingTree&, ScrollingNodeType, ScrollingNodeID); + + WEBCORE_EXPORT virtual FloatPoint minimumScrollPosition() const; + WEBCORE_EXPORT virtual FloatPoint maximumScrollPosition() const; + + virtual void setScrollLayerPosition(const FloatPoint&, const FloatRect& layoutViewport) = 0; + + FloatPoint lastCommittedScrollPosition() const { return m_lastCommittedScrollPosition; } + const FloatSize& scrollableAreaSize() const { return m_scrollableAreaSize; } + const FloatSize& totalContentsSize() const { return m_totalContentsSize; } + const FloatSize& reachableContentsSize() const { return m_reachableContentsSize; } + const IntPoint& scrollOrigin() const { return m_scrollOrigin; } + + // If the totalContentsSize changes in the middle of a rubber-band, we still want to use the old totalContentsSize for the sake of + // computing the stretchAmount(). Using the old value will keep the animation smooth. When there is no rubber-band in progress at + // all, m_totalContentsSizeForRubberBand should be equivalent to m_totalContentsSize. + const FloatSize& totalContentsSizeForRubberBand() const { return m_totalContentsSizeForRubberBand; } + void setTotalContentsSizeForRubberBand(const FloatSize& totalContentsSizeForRubberBand) { m_totalContentsSizeForRubberBand = totalContentsSizeForRubberBand; } + + ScrollElasticity horizontalScrollElasticity() const { return m_scrollableAreaParameters.horizontalScrollElasticity; } + ScrollElasticity verticalScrollElasticity() const { return m_scrollableAreaParameters.verticalScrollElasticity; } + + bool hasEnabledHorizontalScrollbar() const { return m_scrollableAreaParameters.hasEnabledHorizontalScrollbar; } + bool hasEnabledVerticalScrollbar() const { return m_scrollableAreaParameters.hasEnabledVerticalScrollbar; } + + bool canHaveScrollbars() const { return m_scrollableAreaParameters.horizontalScrollbarMode != ScrollbarAlwaysOff || m_scrollableAreaParameters.verticalScrollbarMode != ScrollbarAlwaysOff; } + + WEBCORE_EXPORT void dumpProperties(TextStream&, ScrollingStateTreeAsTextBehavior) const override; + +private: + FloatSize m_scrollableAreaSize; + FloatSize m_totalContentsSize; + FloatSize m_totalContentsSizeForRubberBand; + FloatSize m_reachableContentsSize; + FloatPoint m_lastCommittedScrollPosition; + IntPoint m_scrollOrigin; +#if ENABLE(CSS_SCROLL_SNAP) + ScrollSnapOffsetsInfo<float> m_snapOffsetsInfo; + unsigned m_currentHorizontalSnapPointIndex { 0 }; + unsigned m_currentVerticalSnapPointIndex { 0 }; +#endif + ScrollableAreaParameters m_scrollableAreaParameters; +}; + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_SCROLLING_NODE(ScrollingTreeScrollingNode, isScrollingNode()) + +#endif // ENABLE(ASYNC_SCROLLING) diff --git a/Source/WebCore/page/scrolling/ThreadedScrollingTree.cpp b/Source/WebCore/page/scrolling/ThreadedScrollingTree.cpp new file mode 100644 index 000000000..f664ed3ac --- /dev/null +++ b/Source/WebCore/page/scrolling/ThreadedScrollingTree.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2014-2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ThreadedScrollingTree.h" + +#if ENABLE(ASYNC_SCROLLING) + +#include "AsyncScrollingCoordinator.h" +#include "PlatformWheelEvent.h" +#include "ScrollingThread.h" +#include "ScrollingTreeFixedNode.h" +#include "ScrollingTreeNode.h" +#include "ScrollingTreeScrollingNode.h" +#include "ScrollingTreeStickyNode.h" +#include <wtf/RunLoop.h> + +namespace WebCore { + +ThreadedScrollingTree::ThreadedScrollingTree(AsyncScrollingCoordinator& scrollingCoordinator) + : m_scrollingCoordinator(&scrollingCoordinator) +{ +} + +ThreadedScrollingTree::~ThreadedScrollingTree() +{ + // invalidate() should have cleared m_scrollingCoordinator. + ASSERT(!m_scrollingCoordinator); +} + +ScrollingTree::EventResult ThreadedScrollingTree::tryToHandleWheelEvent(const PlatformWheelEvent& wheelEvent) +{ + if (shouldHandleWheelEventSynchronously(wheelEvent)) + return SendToMainThread; + + if (willWheelEventStartSwipeGesture(wheelEvent)) + return DidNotHandleEvent; + + RefPtr<ThreadedScrollingTree> protectedThis(this); + ScrollingThread::dispatch([protectedThis, wheelEvent] { + protectedThis->handleWheelEvent(wheelEvent); + }); + + return DidHandleEvent; +} + +void ThreadedScrollingTree::handleWheelEvent(const PlatformWheelEvent& wheelEvent) +{ + ASSERT(ScrollingThread::isCurrentThread()); + ScrollingTree::handleWheelEvent(wheelEvent); +} + +void ThreadedScrollingTree::invalidate() +{ + // Invalidate is dispatched by the ScrollingCoordinator class on the ScrollingThread + // to break the reference cycle between ScrollingTree and ScrollingCoordinator when the + // ScrollingCoordinator's page is destroyed. + ASSERT(ScrollingThread::isCurrentThread()); + + // Since this can potentially be the last reference to the scrolling coordinator, + // we need to release it on the main thread since it has member variables (such as timers) + // that expect to be destroyed from the main thread. + RunLoop::main().dispatch([scrollingCoordinator = WTFMove(m_scrollingCoordinator)] { + }); +} + +void ThreadedScrollingTree::commitTreeState(std::unique_ptr<ScrollingStateTree> scrollingStateTree) +{ + ASSERT(ScrollingThread::isCurrentThread()); + ScrollingTree::commitTreeState(WTFMove(scrollingStateTree)); +} + +void ThreadedScrollingTree::scrollingTreeNodeDidScroll(ScrollingNodeID nodeID, const FloatPoint& scrollPosition, const std::optional<FloatPoint>& layoutViewportOrigin, ScrollingLayerPositionAction scrollingLayerPositionAction) +{ + if (!m_scrollingCoordinator) + return; + + if (nodeID == rootNode()->scrollingNodeID()) + setMainFrameScrollPosition(scrollPosition); + + RunLoop::main().dispatch([scrollingCoordinator = m_scrollingCoordinator, nodeID, scrollPosition, layoutViewportOrigin, localIsHandlingProgrammaticScroll = isHandlingProgrammaticScroll(), scrollingLayerPositionAction] { + scrollingCoordinator->scheduleUpdateScrollPositionAfterAsyncScroll(nodeID, scrollPosition, layoutViewportOrigin, localIsHandlingProgrammaticScroll, scrollingLayerPositionAction); + }); +} + +void ThreadedScrollingTree::currentSnapPointIndicesDidChange(ScrollingNodeID nodeID, unsigned horizontal, unsigned vertical) +{ + if (!m_scrollingCoordinator) + return; + + RunLoop::main().dispatch([scrollingCoordinator = m_scrollingCoordinator, nodeID, horizontal, vertical] { + scrollingCoordinator->setActiveScrollSnapIndices(nodeID, horizontal, vertical); + }); +} + +#if PLATFORM(MAC) +void ThreadedScrollingTree::handleWheelEventPhase(PlatformWheelEventPhase phase) +{ + if (!m_scrollingCoordinator) + return; + + RunLoop::main().dispatch([scrollingCoordinator = m_scrollingCoordinator, phase] { + scrollingCoordinator->handleWheelEventPhase(phase); + }); +} + +void ThreadedScrollingTree::setActiveScrollSnapIndices(ScrollingNodeID nodeID, unsigned horizontalIndex, unsigned verticalIndex) +{ + if (!m_scrollingCoordinator) + return; + + RunLoop::main().dispatch([scrollingCoordinator = m_scrollingCoordinator, nodeID, horizontalIndex, verticalIndex] { + scrollingCoordinator->setActiveScrollSnapIndices(nodeID, horizontalIndex, verticalIndex); + }); +} + +void ThreadedScrollingTree::deferTestsForReason(WheelEventTestTrigger::ScrollableAreaIdentifier identifier, WheelEventTestTrigger::DeferTestTriggerReason reason) +{ + if (!m_scrollingCoordinator) + return; + + RunLoop::main().dispatch([scrollingCoordinator = m_scrollingCoordinator, identifier, reason] { + scrollingCoordinator->deferTestsForReason(identifier, reason); + }); +} + +void ThreadedScrollingTree::removeTestDeferralForReason(WheelEventTestTrigger::ScrollableAreaIdentifier identifier, WheelEventTestTrigger::DeferTestTriggerReason reason) +{ + if (!m_scrollingCoordinator) + return; + + RunLoop::main().dispatch([scrollingCoordinator = m_scrollingCoordinator, identifier, reason] { + scrollingCoordinator->removeTestDeferralForReason(identifier, reason); + }); +} + +#endif + +} // namespace WebCore + +#endif // ENABLE(ASYNC_SCROLLING) diff --git a/Source/WebCore/page/scrolling/ThreadedScrollingTree.h b/Source/WebCore/page/scrolling/ThreadedScrollingTree.h new file mode 100644 index 000000000..6d1d49d67 --- /dev/null +++ b/Source/WebCore/page/scrolling/ThreadedScrollingTree.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2014-2017 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(ASYNC_SCROLLING) + +#include "ScrollingStateTree.h" +#include "ScrollingTree.h" +#include <wtf/RefPtr.h> + +namespace WebCore { + +class AsyncScrollingCoordinator; + +// The ThreadedScrollingTree class lives almost exclusively on the scrolling thread and manages the +// hierarchy of scrollable regions on the page. It's also responsible for dispatching events +// to the correct scrolling tree nodes or dispatching events back to the ScrollingCoordinator +// object on the main thread if they can't be handled on the scrolling thread for various reasons. +class ThreadedScrollingTree : public ScrollingTree { +public: + virtual ~ThreadedScrollingTree(); + + void commitTreeState(std::unique_ptr<ScrollingStateTree>) override; + + void handleWheelEvent(const PlatformWheelEvent&) override; + + // Can be called from any thread. Will try to handle the wheel event on the scrolling thread. + // Returns true if the wheel event can be handled on the scrolling thread and false if the + // event must be sent again to the WebCore event handler. + EventResult tryToHandleWheelEvent(const PlatformWheelEvent&) override; + + void invalidate() override; + +protected: + explicit ThreadedScrollingTree(AsyncScrollingCoordinator&); + + void scrollingTreeNodeDidScroll(ScrollingNodeID, const FloatPoint& scrollPosition, const std::optional<FloatPoint>& layoutViewportOrigin, ScrollingLayerPositionAction = ScrollingLayerPositionAction::Sync) override; + void currentSnapPointIndicesDidChange(ScrollingNodeID, unsigned horizontal, unsigned vertical) override; +#if PLATFORM(MAC) + void handleWheelEventPhase(PlatformWheelEventPhase) override; + void setActiveScrollSnapIndices(ScrollingNodeID, unsigned horizontalIndex, unsigned verticalIndex) override; + void deferTestsForReason(WheelEventTestTrigger::ScrollableAreaIdentifier, WheelEventTestTrigger::DeferTestTriggerReason) override; + void removeTestDeferralForReason(WheelEventTestTrigger::ScrollableAreaIdentifier, WheelEventTestTrigger::DeferTestTriggerReason) override; +#endif + +private: + bool isThreadedScrollingTree() const override { return true; } + + RefPtr<AsyncScrollingCoordinator> m_scrollingCoordinator; +}; + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_SCROLLING_TREE(WebCore::ThreadedScrollingTree, isThreadedScrollingTree()) + +#endif // ENABLE(ASYNC_SCROLLING) diff --git a/Source/WebCore/page/scrolling/coordinatedgraphics/ScrollingCoordinatorCoordinatedGraphics.cpp b/Source/WebCore/page/scrolling/coordinatedgraphics/ScrollingCoordinatorCoordinatedGraphics.cpp new file mode 100644 index 000000000..fa662500e --- /dev/null +++ b/Source/WebCore/page/scrolling/coordinatedgraphics/ScrollingCoordinatorCoordinatedGraphics.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2013 Nokia Corporation and/or its subsidiary(-ies). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if USE(COORDINATED_GRAPHICS) + +#include "ScrollingCoordinatorCoordinatedGraphics.h" + +#include "CoordinatedGraphicsLayer.h" +#include "FrameView.h" +#include "HostWindow.h" +#include "Page.h" +#include "RenderLayer.h" +#include "RenderLayerBacking.h" +#include "ScrollingConstraints.h" +#include "ScrollingStateFixedNode.h" +#include "ScrollingStateScrollingNode.h" +#include "ScrollingStateStickyNode.h" +#include "ScrollingStateTree.h" + +namespace WebCore { + +ScrollingCoordinatorCoordinatedGraphics::ScrollingCoordinatorCoordinatedGraphics(Page* page) + : ScrollingCoordinator(page) + , m_scrollingStateTree(std::make_unique<ScrollingStateTree>()) +{ +} + +ScrollingCoordinatorCoordinatedGraphics::~ScrollingCoordinatorCoordinatedGraphics() +{ +} + +ScrollingNodeID ScrollingCoordinatorCoordinatedGraphics::attachToStateTree(ScrollingNodeType nodeType, ScrollingNodeID newNodeID, ScrollingNodeID parentID) +{ + return m_scrollingStateTree->attachNode(nodeType, newNodeID, parentID); +} + +void ScrollingCoordinatorCoordinatedGraphics::detachFromStateTree(ScrollingNodeID nodeID) +{ + ScrollingStateNode* node = m_scrollingStateTree->stateNodeForID(nodeID); + if (node && node->nodeType() == FixedNode) + downcast<CoordinatedGraphicsLayer>(*static_cast<GraphicsLayer*>(node->layer())).setFixedToViewport(false); + + m_scrollingStateTree->detachNode(nodeID); +} + +void ScrollingCoordinatorCoordinatedGraphics::clearStateTree() +{ + m_scrollingStateTree->clear(); +} + +void ScrollingCoordinatorCoordinatedGraphics::updateViewportConstrainedNode(ScrollingNodeID nodeID, const ViewportConstraints& constraints, GraphicsLayer* graphicsLayer) +{ + ASSERT(supportsFixedPositionLayers()); + + ScrollingStateNode* node = m_scrollingStateTree->stateNodeForID(nodeID); + if (!node) + return; + + switch (constraints.constraintType()) { + case ViewportConstraints::FixedPositionConstraint: { + downcast<CoordinatedGraphicsLayer>(*graphicsLayer).setFixedToViewport(true); + downcast<ScrollingStateFixedNode>(*node).setLayer(graphicsLayer); + break; + } + case ViewportConstraints::StickyPositionConstraint: + break; // FIXME : Support sticky elements. + default: + ASSERT_NOT_REACHED(); + } +} + +void ScrollingCoordinatorCoordinatedGraphics::scrollableAreaScrollLayerDidChange(ScrollableArea& scrollableArea) +{ + CoordinatedGraphicsLayer* layer = downcast<CoordinatedGraphicsLayer>(scrollLayerForScrollableArea(scrollableArea)); + if (!layer) + return; + + layer->setScrollableArea(&scrollableArea); +} + +void ScrollingCoordinatorCoordinatedGraphics::willDestroyScrollableArea(ScrollableArea& scrollableArea) +{ + CoordinatedGraphicsLayer* layer = downcast<CoordinatedGraphicsLayer>(scrollLayerForScrollableArea(scrollableArea)); + if (!layer) + return; + + layer->setScrollableArea(nullptr); +} + +bool ScrollingCoordinatorCoordinatedGraphics::requestScrollPositionUpdate(FrameView& frameView, const IntPoint& scrollPosition) +{ + if (!frameView.delegatesScrolling()) + return false; + + frameView.hostWindow()->delegatedScrollRequested(scrollPosition); + return true; +} + +} // namespace WebCore + +#endif // USE(COORDINATED_GRAPHICS) diff --git a/Source/WebCore/page/scrolling/coordinatedgraphics/ScrollingCoordinatorCoordinatedGraphics.h b/Source/WebCore/page/scrolling/coordinatedgraphics/ScrollingCoordinatorCoordinatedGraphics.h new file mode 100644 index 000000000..075bcfcc8 --- /dev/null +++ b/Source/WebCore/page/scrolling/coordinatedgraphics/ScrollingCoordinatorCoordinatedGraphics.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2013 Nokia Corporation and/or its subsidiary(-ies). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if USE(COORDINATED_GRAPHICS) + +#include "ScrollingCoordinator.h" + +namespace WebCore { + +class ScrollingStateTree; + +class ScrollingCoordinatorCoordinatedGraphics : public ScrollingCoordinator { +public: + explicit ScrollingCoordinatorCoordinatedGraphics(Page*); + virtual ~ScrollingCoordinatorCoordinatedGraphics(); + + bool supportsFixedPositionLayers() const override { return true; } + + ScrollingNodeID attachToStateTree(ScrollingNodeType, ScrollingNodeID newNodeID, ScrollingNodeID parentID) override; + void detachFromStateTree(ScrollingNodeID) override; + void clearStateTree() override; + + void updateViewportConstrainedNode(ScrollingNodeID, const ViewportConstraints&, GraphicsLayer*) override; + + void scrollableAreaScrollLayerDidChange(ScrollableArea&) override; + void willDestroyScrollableArea(ScrollableArea&) override; + + bool requestScrollPositionUpdate(FrameView&, const IntPoint&) override; + +private: + std::unique_ptr<ScrollingStateTree> m_scrollingStateTree; +}; + +} // namespace WebCore + +#endif // USE(COORDINATED_GRAPHICS) diff --git a/Source/WebCore/page/scrolling/coordinatedgraphics/ScrollingStateNodeCoordinatedGraphics.cpp b/Source/WebCore/page/scrolling/coordinatedgraphics/ScrollingStateNodeCoordinatedGraphics.cpp new file mode 100644 index 000000000..aeb6223c1 --- /dev/null +++ b/Source/WebCore/page/scrolling/coordinatedgraphics/ScrollingStateNodeCoordinatedGraphics.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2013 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ScrollingStateNode.h" + +#include "GraphicsLayer.h" +#include "NotImplemented.h" +#include "ScrollingStateTree.h" + +#if USE(COORDINATED_GRAPHICS) + +namespace WebCore { + +void LayerRepresentation::retainPlatformLayer(PlatformLayer*) +{ + notImplemented(); +} + +void LayerRepresentation::releasePlatformLayer(PlatformLayer*) +{ + notImplemented(); +} + +} // namespace WebCore + +#endif // USE(COORDINATED_GRAPHICS) |