| 1 | /* |
| 2 | * Copyright (C) 2012 Apple Inc. All rights reserved. |
| 3 | * |
| 4 | * Redistribution and use in source and binary forms, with or without |
| 5 | * modification, are permitted provided that the following conditions |
| 6 | * are met: |
| 7 | * 1. Redistributions of source code must retain the above copyright |
| 8 | * notice, this list of conditions and the following disclaimer. |
| 9 | * 2. Redistributions in binary form must reproduce the above copyright |
| 10 | * notice, this list of conditions and the following disclaimer in the |
| 11 | * documentation and/or other materials provided with the distribution. |
| 12 | * |
| 13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| 14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| 15 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| 17 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| 18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| 19 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| 20 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| 21 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| 22 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| 23 | * THE POSSIBILITY OF SUCH DAMAGE. |
| 24 | */ |
| 25 | |
| 26 | #include "config.h" |
| 27 | #include "ScrollingTreeScrollingNode.h" |
| 28 | |
| 29 | #if ENABLE(ASYNC_SCROLLING) |
| 30 | |
| 31 | #include "Logging.h" |
| 32 | #include "ScrollingStateScrollingNode.h" |
| 33 | #include "ScrollingStateTree.h" |
| 34 | #include "ScrollingTree.h" |
| 35 | #include <wtf/text/TextStream.h> |
| 36 | |
| 37 | namespace WebCore { |
| 38 | |
| 39 | ScrollingTreeScrollingNode::ScrollingTreeScrollingNode(ScrollingTree& scrollingTree, ScrollingNodeType nodeType, ScrollingNodeID nodeID) |
| 40 | : ScrollingTreeNode(scrollingTree, nodeType, nodeID) |
| 41 | { |
| 42 | } |
| 43 | |
| 44 | ScrollingTreeScrollingNode::~ScrollingTreeScrollingNode() = default; |
| 45 | |
| 46 | void ScrollingTreeScrollingNode::commitStateBeforeChildren(const ScrollingStateNode& stateNode) |
| 47 | { |
| 48 | const ScrollingStateScrollingNode& state = downcast<ScrollingStateScrollingNode>(stateNode); |
| 49 | |
| 50 | if (state.hasChangedProperty(ScrollingStateScrollingNode::ScrollableAreaSize)) |
| 51 | m_scrollableAreaSize = state.scrollableAreaSize(); |
| 52 | |
| 53 | if (state.hasChangedProperty(ScrollingStateScrollingNode::TotalContentsSize)) { |
| 54 | if (scrollingTree().isRubberBandInProgress()) |
| 55 | m_totalContentsSizeForRubberBand = m_totalContentsSize; |
| 56 | else |
| 57 | m_totalContentsSizeForRubberBand = state.totalContentsSize(); |
| 58 | |
| 59 | m_totalContentsSize = state.totalContentsSize(); |
| 60 | } |
| 61 | |
| 62 | if (state.hasChangedProperty(ScrollingStateScrollingNode::ReachableContentsSize)) |
| 63 | m_reachableContentsSize = state.reachableContentsSize(); |
| 64 | |
| 65 | if (state.hasChangedProperty(ScrollingStateScrollingNode::ScrollPosition)) |
| 66 | m_lastCommittedScrollPosition = state.scrollPosition(); |
| 67 | |
| 68 | if (state.hasChangedProperty(ScrollingStateScrollingNode::ParentRelativeScrollableRect)) |
| 69 | m_parentRelativeScrollableRect = state.parentRelativeScrollableRect(); |
| 70 | |
| 71 | if (state.hasChangedProperty(ScrollingStateScrollingNode::ScrollOrigin)) |
| 72 | m_scrollOrigin = state.scrollOrigin(); |
| 73 | |
| 74 | #if ENABLE(CSS_SCROLL_SNAP) |
| 75 | if (state.hasChangedProperty(ScrollingStateScrollingNode::HorizontalSnapOffsets)) |
| 76 | m_snapOffsetsInfo.horizontalSnapOffsets = state.horizontalSnapOffsets(); |
| 77 | |
| 78 | if (state.hasChangedProperty(ScrollingStateScrollingNode::VerticalSnapOffsets)) |
| 79 | m_snapOffsetsInfo.verticalSnapOffsets = state.verticalSnapOffsets(); |
| 80 | |
| 81 | if (state.hasChangedProperty(ScrollingStateScrollingNode::HorizontalSnapOffsetRanges)) |
| 82 | m_snapOffsetsInfo.horizontalSnapOffsetRanges = state.horizontalSnapOffsetRanges(); |
| 83 | |
| 84 | if (state.hasChangedProperty(ScrollingStateScrollingNode::VerticalSnapOffsetRanges)) |
| 85 | m_snapOffsetsInfo.verticalSnapOffsetRanges = state.verticalSnapOffsetRanges(); |
| 86 | |
| 87 | if (state.hasChangedProperty(ScrollingStateScrollingNode::CurrentHorizontalSnapOffsetIndex)) |
| 88 | m_currentHorizontalSnapPointIndex = state.currentHorizontalSnapPointIndex(); |
| 89 | |
| 90 | if (state.hasChangedProperty(ScrollingStateScrollingNode::CurrentVerticalSnapOffsetIndex)) |
| 91 | m_currentVerticalSnapPointIndex = state.currentVerticalSnapPointIndex(); |
| 92 | #endif |
| 93 | |
| 94 | if (state.hasChangedProperty(ScrollingStateScrollingNode::ScrollableAreaParams)) |
| 95 | m_scrollableAreaParameters = state.scrollableAreaParameters(); |
| 96 | |
| 97 | if (state.hasChangedProperty(ScrollingStateScrollingNode::ExpectsWheelEventTestTrigger)) |
| 98 | m_expectsWheelEventTestTrigger = state.expectsWheelEventTestTrigger(); |
| 99 | |
| 100 | #if PLATFORM(COCOA) |
| 101 | if (state.hasChangedProperty(ScrollingStateScrollingNode::ScrollContainerLayer)) |
| 102 | m_scrollContainerLayer = state.scrollContainerLayer(); |
| 103 | |
| 104 | if (state.hasChangedProperty(ScrollingStateScrollingNode::ScrolledContentsLayer)) |
| 105 | m_scrolledContentsLayer = state.scrolledContentsLayer(); |
| 106 | #endif |
| 107 | } |
| 108 | |
| 109 | void ScrollingTreeScrollingNode::commitStateAfterChildren(const ScrollingStateNode& stateNode) |
| 110 | { |
| 111 | const ScrollingStateScrollingNode& scrollingStateNode = downcast<ScrollingStateScrollingNode>(stateNode); |
| 112 | if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::RequestedScrollPosition)) |
| 113 | scrollingTree().scrollingTreeNodeRequestsScroll(scrollingNodeID(), scrollingStateNode.requestedScrollPosition(), scrollingStateNode.requestedScrollPositionRepresentsProgrammaticScroll()); |
| 114 | } |
| 115 | |
| 116 | ScrollingEventResult ScrollingTreeScrollingNode::handleWheelEvent(const PlatformWheelEvent&) |
| 117 | { |
| 118 | return ScrollingEventResult::DidNotHandleEvent; |
| 119 | } |
| 120 | |
| 121 | FloatPoint ScrollingTreeScrollingNode::clampScrollPosition(const FloatPoint& scrollPosition) const |
| 122 | { |
| 123 | return scrollPosition.constrainedBetween(minimumScrollPosition(), maximumScrollPosition()); |
| 124 | } |
| 125 | |
| 126 | FloatPoint ScrollingTreeScrollingNode::minimumScrollPosition() const |
| 127 | { |
| 128 | return { }; |
| 129 | } |
| 130 | |
| 131 | FloatPoint ScrollingTreeScrollingNode::maximumScrollPosition() const |
| 132 | { |
| 133 | FloatPoint contentSizePoint(totalContentsSize()); |
| 134 | return FloatPoint(contentSizePoint - scrollableAreaSize()).expandedTo(FloatPoint()); |
| 135 | } |
| 136 | |
| 137 | bool ScrollingTreeScrollingNode::scrollLimitReached(const PlatformWheelEvent& wheelEvent) const |
| 138 | { |
| 139 | FloatPoint oldScrollPosition = currentScrollPosition(); |
| 140 | FloatPoint newScrollPosition = oldScrollPosition + FloatSize(wheelEvent.deltaX(), -wheelEvent.deltaY()); |
| 141 | newScrollPosition = newScrollPosition.constrainedBetween(minimumScrollPosition(), maximumScrollPosition()); |
| 142 | return newScrollPosition == oldScrollPosition; |
| 143 | } |
| 144 | |
| 145 | FloatPoint ScrollingTreeScrollingNode::adjustedScrollPosition(const FloatPoint& scrollPosition, ScrollPositionClamp clamp) const |
| 146 | { |
| 147 | if (clamp == ScrollPositionClamp::ToContentEdges) |
| 148 | return clampScrollPosition(scrollPosition); |
| 149 | |
| 150 | return scrollPosition; |
| 151 | } |
| 152 | |
| 153 | void ScrollingTreeScrollingNode::scrollBy(const FloatSize& delta, ScrollPositionClamp clamp) |
| 154 | { |
| 155 | scrollTo(currentScrollPosition() + delta, ScrollType::User, clamp); |
| 156 | } |
| 157 | |
| 158 | void ScrollingTreeScrollingNode::scrollTo(const FloatPoint& position, ScrollType scrollType, ScrollPositionClamp clamp) |
| 159 | { |
| 160 | if (position == m_currentScrollPosition) |
| 161 | return; |
| 162 | |
| 163 | scrollingTree().setIsHandlingProgrammaticScroll(scrollType == ScrollType::Programmatic); |
| 164 | |
| 165 | m_currentScrollPosition = adjustedScrollPosition(position, clamp); |
| 166 | |
| 167 | LOG_WITH_STREAM(Scrolling, stream << "ScrollingTreeScrollingNode " << scrollingNodeID() << " scrollTo " << position << " (delta from last committed position " << (m_lastCommittedScrollPosition - m_currentScrollPosition) << ")" ); |
| 168 | |
| 169 | updateViewportForCurrentScrollPosition(); |
| 170 | currentScrollPositionChanged(); |
| 171 | |
| 172 | scrollingTree().setIsHandlingProgrammaticScroll(false); |
| 173 | } |
| 174 | |
| 175 | void ScrollingTreeScrollingNode::currentScrollPositionChanged() |
| 176 | { |
| 177 | repositionScrollingLayers(); |
| 178 | repositionRelatedLayers(); |
| 179 | |
| 180 | scrollingTree().notifyRelatedNodesAfterScrollPositionChange(*this); |
| 181 | scrollingTree().scrollingTreeNodeDidScroll(*this); |
| 182 | } |
| 183 | |
| 184 | bool ScrollingTreeScrollingNode::scrollPositionAndLayoutViewportMatch(const FloatPoint& position, Optional<FloatRect>) |
| 185 | { |
| 186 | return position == m_currentScrollPosition; |
| 187 | } |
| 188 | |
| 189 | void ScrollingTreeScrollingNode::applyLayerPositions(const FloatRect&, FloatSize&) |
| 190 | { |
| 191 | repositionScrollingLayers(); |
| 192 | repositionRelatedLayers(); |
| 193 | } |
| 194 | |
| 195 | void ScrollingTreeScrollingNode::wasScrolledByDelegatedScrolling(const FloatPoint& position, Optional<FloatRect> overrideLayoutViewport) |
| 196 | { |
| 197 | // Even if position and overrideLayoutViewport haven't changed for this node, other nodes may have received new constraint data |
| 198 | // via a commit, so the call to notifyRelatedNodesAfterScrollPositionChange() is necessary. We could avoid this if we knew that |
| 199 | // no commits had happened. |
| 200 | bool scrollPositionChanged = !scrollPositionAndLayoutViewportMatch(position, overrideLayoutViewport); |
| 201 | |
| 202 | m_currentScrollPosition = adjustedScrollPosition(position, ScrollPositionClamp::None); |
| 203 | updateViewportForCurrentScrollPosition(overrideLayoutViewport); |
| 204 | |
| 205 | repositionRelatedLayers(); |
| 206 | |
| 207 | scrollingTree().notifyRelatedNodesAfterScrollPositionChange(*this); |
| 208 | |
| 209 | if (scrollPositionChanged) |
| 210 | scrollingTree().scrollingTreeNodeDidScroll(*this); |
| 211 | } |
| 212 | |
| 213 | LayoutPoint ScrollingTreeScrollingNode::parentToLocalPoint(LayoutPoint point) const |
| 214 | { |
| 215 | return point - toLayoutSize(parentRelativeScrollableRect().location()); |
| 216 | } |
| 217 | |
| 218 | LayoutPoint ScrollingTreeScrollingNode::localToContentsPoint(LayoutPoint point) const |
| 219 | { |
| 220 | return point + LayoutPoint(currentScrollPosition()); |
| 221 | } |
| 222 | |
| 223 | ScrollingTreeScrollingNode* ScrollingTreeScrollingNode::scrollingNodeForPoint(LayoutPoint parentPoint) const |
| 224 | { |
| 225 | if (auto* node = ScrollingTreeNode::scrollingNodeForPoint(parentPoint)) |
| 226 | return node; |
| 227 | |
| 228 | if (parentRelativeScrollableRect().contains(parentPoint)) |
| 229 | return const_cast<ScrollingTreeScrollingNode*>(this); |
| 230 | |
| 231 | return nullptr; |
| 232 | } |
| 233 | |
| 234 | void ScrollingTreeScrollingNode::dumpProperties(TextStream& ts, ScrollingStateTreeAsTextBehavior behavior) const |
| 235 | { |
| 236 | ScrollingTreeNode::dumpProperties(ts, behavior); |
| 237 | ts.dumpProperty("scrollable area size" , m_scrollableAreaSize); |
| 238 | ts.dumpProperty("total content size" , m_totalContentsSize); |
| 239 | if (m_totalContentsSizeForRubberBand != m_totalContentsSize) |
| 240 | ts.dumpProperty("total content size for rubber band" , m_totalContentsSizeForRubberBand); |
| 241 | if (m_reachableContentsSize != m_totalContentsSize) |
| 242 | ts.dumpProperty("reachable content size" , m_reachableContentsSize); |
| 243 | ts.dumpProperty("last committed scroll position" , m_lastCommittedScrollPosition); |
| 244 | |
| 245 | if (!m_parentRelativeScrollableRect.isEmpty()) |
| 246 | ts.dumpProperty("parent relative scrollable rect" , m_parentRelativeScrollableRect); |
| 247 | |
| 248 | if (m_scrollOrigin != IntPoint()) |
| 249 | ts.dumpProperty("scroll origin" , m_scrollOrigin); |
| 250 | |
| 251 | #if ENABLE(CSS_SCROLL_SNAP) |
| 252 | if (m_snapOffsetsInfo.horizontalSnapOffsets.size()) |
| 253 | ts.dumpProperty("horizontal snap offsets" , m_snapOffsetsInfo.horizontalSnapOffsets); |
| 254 | |
| 255 | if (m_snapOffsetsInfo.verticalSnapOffsets.size()) |
| 256 | ts.dumpProperty("vertical snap offsets" , m_snapOffsetsInfo.verticalSnapOffsets); |
| 257 | |
| 258 | if (m_currentHorizontalSnapPointIndex) |
| 259 | ts.dumpProperty("current horizontal snap point index" , m_currentHorizontalSnapPointIndex); |
| 260 | |
| 261 | if (m_currentVerticalSnapPointIndex) |
| 262 | ts.dumpProperty("current vertical snap point index" , m_currentVerticalSnapPointIndex); |
| 263 | |
| 264 | #endif |
| 265 | |
| 266 | ts.dumpProperty("scrollable area parameters" , m_scrollableAreaParameters); |
| 267 | } |
| 268 | |
| 269 | } // namespace WebCore |
| 270 | |
| 271 | #endif // ENABLE(ASYNC_SCROLLING) |
| 272 | |