1/*
2 * Copyright (C) 2012-2015 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 "ScrollingTree.h"
28
29#if ENABLE(ASYNC_SCROLLING)
30
31#include "EventNames.h"
32#include "Logging.h"
33#include "PlatformWheelEvent.h"
34#include "ScrollingStateFrameScrollingNode.h"
35#include "ScrollingStateTree.h"
36#include "ScrollingTreeFrameScrollingNode.h"
37#include "ScrollingTreeNode.h"
38#include "ScrollingTreeOverflowScrollingNode.h"
39#include "ScrollingTreeScrollingNode.h"
40#include <wtf/SetForScope.h>
41#include <wtf/text/TextStream.h>
42
43namespace WebCore {
44
45ScrollingTree::ScrollingTree() = default;
46
47ScrollingTree::~ScrollingTree() = default;
48
49bool ScrollingTree::shouldHandleWheelEventSynchronously(const PlatformWheelEvent& wheelEvent)
50{
51 // This method is invoked by the event handling thread
52 LockHolder lock(m_treeStateMutex);
53
54 bool shouldSetLatch = wheelEvent.shouldConsiderLatching();
55
56 if (hasLatchedNode() && !shouldSetLatch)
57 return false;
58
59 if (shouldSetLatch)
60 m_treeState.latchedNodeID = 0;
61
62 if (!m_treeState.eventTrackingRegions.isEmpty() && m_rootNode) {
63 FloatPoint position = wheelEvent.position();
64 position.move(m_rootNode->viewToContentsOffset(m_treeState.mainFrameScrollPosition));
65
66 const EventNames& names = eventNames();
67 IntPoint roundedPosition = roundedIntPoint(position);
68
69 // Event regions are affected by page scale, so no need to map through scale.
70 bool isSynchronousDispatchRegion = m_treeState.eventTrackingRegions.trackingTypeForPoint(names.wheelEvent, roundedPosition) == TrackingType::Synchronous
71 || m_treeState.eventTrackingRegions.trackingTypeForPoint(names.mousewheelEvent, roundedPosition) == TrackingType::Synchronous;
72 LOG_WITH_STREAM(Scrolling, stream << "ScrollingTree::shouldHandleWheelEventSynchronously: wheelEvent at " << wheelEvent.position() << " mapped to content point " << position << ", in non-fast region " << isSynchronousDispatchRegion);
73
74 if (isSynchronousDispatchRegion)
75 return true;
76 }
77 return false;
78}
79
80void ScrollingTree::setOrClearLatchedNode(const PlatformWheelEvent& wheelEvent, ScrollingNodeID nodeID)
81{
82 if (wheelEvent.shouldConsiderLatching()) {
83 LOG_WITH_STREAM(Scrolling, stream << "ScrollingTree " << this << " setOrClearLatchedNode: setting latched node " << nodeID);
84 setLatchedNode(nodeID);
85 } else if (wheelEvent.shouldResetLatching()) {
86 LOG_WITH_STREAM(Scrolling, stream << "ScrollingTree " << this << " setOrClearLatchedNode: clearing latched node (was " << latchedNode() << ")");
87 clearLatchedNode();
88 }
89}
90
91ScrollingEventResult ScrollingTree::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
92{
93 LOG_WITH_STREAM(Scrolling, stream << "ScrollingTree " << this << " handleWheelEvent (async scrolling enabled: " << asyncFrameOrOverflowScrollingEnabled() << ")");
94
95 LockHolder locker(m_treeMutex);
96
97 if (!asyncFrameOrOverflowScrollingEnabled()) {
98 if (m_rootNode)
99 m_rootNode->handleWheelEvent(wheelEvent);
100 return ScrollingEventResult::DidNotHandleEvent;
101 }
102
103 if (hasLatchedNode()) {
104 LOG_WITH_STREAM(Scrolling, stream << " has latched node " << latchedNode());
105 auto* node = nodeForID(latchedNode());
106 if (is<ScrollingTreeScrollingNode>(node))
107 return downcast<ScrollingTreeScrollingNode>(*node).handleWheelEvent(wheelEvent);
108 }
109
110 if (m_rootNode) {
111 FloatPoint position = wheelEvent.position();
112 ScrollingTreeNode* node = m_rootNode->scrollingNodeForPoint(LayoutPoint(position));
113
114 LOG_WITH_STREAM(Scrolling, stream << "ScrollingTree::handleWheelEvent found node " << (node ? node->scrollingNodeID() : 0) << " for point " << position << "\n");
115
116 while (node) {
117 if (is<ScrollingTreeScrollingNode>(*node)) {
118 auto& scrollingNode = downcast<ScrollingTreeScrollingNode>(*node);
119 // FIXME: this needs to consult latching logic.
120 if (scrollingNode.handleWheelEvent(wheelEvent) == ScrollingEventResult::DidHandleEvent)
121 return ScrollingEventResult::DidHandleEvent;
122 }
123 node = node->parent();
124 }
125 }
126 return ScrollingEventResult::DidNotHandleEvent;
127}
128
129void ScrollingTree::mainFrameViewportChangedViaDelegatedScrolling(const FloatPoint& scrollPosition, const FloatRect& layoutViewport, double)
130{
131 LOG_WITH_STREAM(Scrolling, stream << "ScrollingTree::viewportChangedViaDelegatedScrolling - layoutViewport " << layoutViewport);
132
133 if (!m_rootNode)
134 return;
135
136 m_rootNode->wasScrolledByDelegatedScrolling(scrollPosition, layoutViewport);
137}
138
139void ScrollingTree::commitTreeState(std::unique_ptr<ScrollingStateTree> scrollingStateTree)
140{
141 LockHolder locker(m_treeMutex);
142
143 bool rootStateNodeChanged = scrollingStateTree->hasNewRootStateNode();
144
145 LOG(Scrolling, "\nScrollingTree %p commitTreeState", this);
146
147 auto* rootNode = scrollingStateTree->rootStateNode();
148 if (rootNode
149 && (rootStateNodeChanged
150 || rootNode->hasChangedProperty(ScrollingStateFrameScrollingNode::EventTrackingRegion)
151 || rootNode->hasChangedProperty(ScrollingStateScrollingNode::ScrolledContentsLayer)
152 || rootNode->hasChangedProperty(ScrollingStateFrameScrollingNode::AsyncFrameOrOverflowScrollingEnabled))) {
153 LockHolder lock(m_treeStateMutex);
154
155 if (rootStateNodeChanged || rootNode->hasChangedProperty(ScrollingStateScrollingNode::ScrolledContentsLayer))
156 m_treeState.mainFrameScrollPosition = { };
157
158 if (rootStateNodeChanged || rootNode->hasChangedProperty(ScrollingStateFrameScrollingNode::EventTrackingRegion))
159 m_treeState.eventTrackingRegions = scrollingStateTree->rootStateNode()->eventTrackingRegions();
160
161 if (rootStateNodeChanged || rootNode->hasChangedProperty(ScrollingStateFrameScrollingNode::AsyncFrameOrOverflowScrollingEnabled))
162 m_asyncFrameOrOverflowScrollingEnabled = scrollingStateTree->rootStateNode()->asyncFrameOrOverflowScrollingEnabled();
163 }
164
165 // unvisitedNodes starts with all nodes in the map; we remove nodes as we visit them. At the end, it's the unvisited nodes.
166 // We can't use orphanNodes for this, because orphanNodes won't contain descendants of removed nodes.
167 HashSet<ScrollingNodeID> unvisitedNodes;
168 for (auto nodeID : m_nodeMap.keys())
169 unvisitedNodes.add(nodeID);
170
171 m_overflowRelatedNodesMap.clear();
172 m_positionedNodesWithRelatedOverflow.clear();
173
174 // orphanNodes keeps child nodes alive while we rebuild child lists.
175 OrphanScrollingNodeMap orphanNodes;
176 updateTreeFromStateNode(rootNode, orphanNodes, unvisitedNodes);
177
178 for (auto nodeID : unvisitedNodes) {
179 if (nodeID == m_treeState.latchedNodeID)
180 clearLatchedNode();
181
182 LOG(Scrolling, "ScrollingTree::commitTreeState - removing unvisited node %" PRIu64, nodeID);
183 m_nodeMap.remove(nodeID);
184 }
185
186 LOG(Scrolling, "committed ScrollingTree\n%s", scrollingTreeAsText(ScrollingStateTreeAsTextBehaviorDebug).utf8().data());
187}
188
189void ScrollingTree::updateTreeFromStateNode(const ScrollingStateNode* stateNode, OrphanScrollingNodeMap& orphanNodes, HashSet<ScrollingNodeID>& unvisitedNodes)
190{
191 if (!stateNode) {
192 m_nodeMap.clear();
193 m_rootNode = nullptr;
194 return;
195 }
196
197 ScrollingNodeID nodeID = stateNode->scrollingNodeID();
198 ScrollingNodeID parentNodeID = stateNode->parentNodeID();
199
200 auto it = m_nodeMap.find(nodeID);
201
202 RefPtr<ScrollingTreeNode> node;
203 if (it != m_nodeMap.end()) {
204 node = it->value;
205 unvisitedNodes.remove(nodeID);
206 } else {
207 node = createScrollingTreeNode(stateNode->nodeType(), nodeID);
208 if (!parentNodeID) {
209 // This is the root node. Clear the node map.
210 ASSERT(stateNode->isFrameScrollingNode());
211 m_rootNode = downcast<ScrollingTreeFrameScrollingNode>(node.get());
212 m_nodeMap.clear();
213 }
214 m_nodeMap.set(nodeID, node.get());
215 }
216
217 if (parentNodeID) {
218 auto parentIt = m_nodeMap.find(parentNodeID);
219 ASSERT_WITH_SECURITY_IMPLICATION(parentIt != m_nodeMap.end());
220 if (parentIt != m_nodeMap.end()) {
221 auto* parent = parentIt->value;
222
223 auto* oldParent = node->parent();
224 if (oldParent)
225 oldParent->removeChild(*node);
226
227 if (oldParent != parent)
228 node->setParent(parent);
229
230 parent->appendChild(*node);
231 } else {
232 // FIXME: Use WeakPtr in m_nodeMap.
233 m_nodeMap.remove(nodeID);
234 }
235 }
236
237 node->commitStateBeforeChildren(*stateNode);
238
239 // Move all children into the orphanNodes map. Live ones will get added back as we recurse over children.
240 if (auto nodeChildren = node->children()) {
241 for (auto& childScrollingNode : *nodeChildren) {
242 childScrollingNode->setParent(nullptr);
243 orphanNodes.add(childScrollingNode->scrollingNodeID(), childScrollingNode.get());
244 }
245 nodeChildren->clear();
246 }
247
248 // Now update the children if we have any.
249 if (auto children = stateNode->children()) {
250 for (auto& child : *children)
251 updateTreeFromStateNode(child.get(), orphanNodes, unvisitedNodes);
252 }
253
254 node->commitStateAfterChildren(*stateNode);
255}
256
257void ScrollingTree::applyLayerPositions()
258{
259 ASSERT(isMainThread());
260 LockHolder locker(m_treeMutex);
261
262 if (!m_rootNode)
263 return;
264
265 LOG(Scrolling, "\nScrollingTree %p applyLayerPositions", this);
266
267 applyLayerPositionsRecursive(*m_rootNode, { }, { });
268
269 LOG(Scrolling, "ScrollingTree %p applyLayerPositions - done\n", this);
270}
271
272void ScrollingTree::applyLayerPositionsRecursive(ScrollingTreeNode& currNode, FloatRect layoutViewport, FloatSize cumulativeDelta)
273{
274 if (is<ScrollingTreeFrameScrollingNode>(currNode)) {
275 layoutViewport = downcast<ScrollingTreeFrameScrollingNode>(currNode).layoutViewport();
276 cumulativeDelta = { };
277 }
278
279 currNode.applyLayerPositions(layoutViewport, cumulativeDelta);
280
281 if (auto children = currNode.children()) {
282 for (auto& child : *children)
283 applyLayerPositionsRecursive(*child, layoutViewport, cumulativeDelta);
284 }
285}
286
287ScrollingTreeNode* ScrollingTree::nodeForID(ScrollingNodeID nodeID) const
288{
289 if (!nodeID)
290 return nullptr;
291
292 return m_nodeMap.get(nodeID);
293}
294
295void ScrollingTree::notifyRelatedNodesAfterScrollPositionChange(ScrollingTreeScrollingNode& changedNode)
296{
297 Vector<ScrollingNodeID> additionalUpdateRoots;
298
299 FloatSize deltaFromLastCommittedScrollPosition;
300 FloatRect currentFrameLayoutViewport;
301 if (is<ScrollingTreeFrameScrollingNode>(changedNode))
302 currentFrameLayoutViewport = downcast<ScrollingTreeFrameScrollingNode>(changedNode).layoutViewport();
303 else if (is<ScrollingTreeOverflowScrollingNode>(changedNode)) {
304 deltaFromLastCommittedScrollPosition = changedNode.lastCommittedScrollPosition() - changedNode.currentScrollPosition();
305
306 if (auto* frameScrollingNode = changedNode.enclosingFrameNodeIncludingSelf())
307 currentFrameLayoutViewport = frameScrollingNode->layoutViewport();
308
309 additionalUpdateRoots = overflowRelatedNodes().get(changedNode.scrollingNodeID());
310 }
311
312 notifyRelatedNodesRecursive(changedNode, changedNode, currentFrameLayoutViewport, deltaFromLastCommittedScrollPosition);
313
314 for (auto positionedNodeID : additionalUpdateRoots) {
315 auto* positionedNode = nodeForID(positionedNodeID);
316 if (positionedNode)
317 notifyRelatedNodesRecursive(changedNode, *positionedNode, currentFrameLayoutViewport, deltaFromLastCommittedScrollPosition);
318 }
319}
320
321void ScrollingTree::notifyRelatedNodesRecursive(ScrollingTreeScrollingNode& changedNode, ScrollingTreeNode& currNode, const FloatRect& layoutViewport, FloatSize cumulativeDelta)
322{
323 currNode.relatedNodeScrollPositionDidChange(changedNode, layoutViewport, cumulativeDelta);
324
325 if (!currNode.children())
326 return;
327
328 for (auto& child : *currNode.children()) {
329 // Never need to cross frame boundaries, since scroll layer adjustments are isolated to each document.
330 if (is<ScrollingTreeFrameScrollingNode>(child))
331 continue;
332
333 notifyRelatedNodesRecursive(changedNode, *child, layoutViewport, cumulativeDelta);
334 }
335}
336
337void ScrollingTree::setAsyncFrameOrOverflowScrollingEnabled(bool enabled)
338{
339 m_asyncFrameOrOverflowScrollingEnabled = enabled;
340}
341
342void ScrollingTree::setMainFrameScrollPosition(FloatPoint position)
343{
344 LockHolder lock(m_treeStateMutex);
345 m_treeState.mainFrameScrollPosition = position;
346}
347
348TrackingType ScrollingTree::eventTrackingTypeForPoint(const AtomicString& eventName, IntPoint p)
349{
350 LockHolder lock(m_treeStateMutex);
351 return m_treeState.eventTrackingRegions.trackingTypeForPoint(eventName, p);
352}
353
354// Can be called from the main thread.
355bool ScrollingTree::isRubberBandInProgress()
356{
357 LockHolder lock(m_treeStateMutex);
358 return m_treeState.mainFrameIsRubberBanding;
359}
360
361void ScrollingTree::setMainFrameIsRubberBanding(bool isRubberBanding)
362{
363 LockHolder locker(m_treeStateMutex);
364 m_treeState.mainFrameIsRubberBanding = isRubberBanding;
365}
366
367// Can be called from the main thread.
368bool ScrollingTree::isScrollSnapInProgress()
369{
370 LockHolder lock(m_treeStateMutex);
371 return m_treeState.mainFrameIsScrollSnapping;
372}
373
374void ScrollingTree::setMainFrameIsScrollSnapping(bool isScrollSnapping)
375{
376 LockHolder locker(m_treeStateMutex);
377 m_treeState.mainFrameIsScrollSnapping = isScrollSnapping;
378}
379
380void ScrollingTree::setMainFramePinState(bool pinnedToTheLeft, bool pinnedToTheRight, bool pinnedToTheTop, bool pinnedToTheBottom)
381{
382 LockHolder locker(m_swipeStateMutex);
383
384 m_swipeState.mainFramePinnedToTheLeft = pinnedToTheLeft;
385 m_swipeState.mainFramePinnedToTheRight = pinnedToTheRight;
386 m_swipeState.mainFramePinnedToTheTop = pinnedToTheTop;
387 m_swipeState.mainFramePinnedToTheBottom = pinnedToTheBottom;
388}
389
390void ScrollingTree::setCanRubberBandState(bool canRubberBandAtLeft, bool canRubberBandAtRight, bool canRubberBandAtTop, bool canRubberBandAtBottom)
391{
392 LockHolder locker(m_swipeStateMutex);
393
394 m_swipeState.rubberBandsAtLeft = canRubberBandAtLeft;
395 m_swipeState.rubberBandsAtRight = canRubberBandAtRight;
396 m_swipeState.rubberBandsAtTop = canRubberBandAtTop;
397 m_swipeState.rubberBandsAtBottom = canRubberBandAtBottom;
398}
399
400// Can be called from the main thread.
401void ScrollingTree::setScrollPinningBehavior(ScrollPinningBehavior pinning)
402{
403 LockHolder locker(m_swipeStateMutex);
404
405 m_swipeState.scrollPinningBehavior = pinning;
406}
407
408ScrollPinningBehavior ScrollingTree::scrollPinningBehavior()
409{
410 LockHolder lock(m_swipeStateMutex);
411
412 return m_swipeState.scrollPinningBehavior;
413}
414
415bool ScrollingTree::willWheelEventStartSwipeGesture(const PlatformWheelEvent& wheelEvent)
416{
417 if (wheelEvent.phase() != PlatformWheelEventPhaseBegan)
418 return false;
419
420 LockHolder lock(m_swipeStateMutex);
421
422 if (wheelEvent.deltaX() > 0 && m_swipeState.mainFramePinnedToTheLeft && !m_swipeState.rubberBandsAtLeft)
423 return true;
424 if (wheelEvent.deltaX() < 0 && m_swipeState.mainFramePinnedToTheRight && !m_swipeState.rubberBandsAtRight)
425 return true;
426 if (wheelEvent.deltaY() > 0 && m_swipeState.mainFramePinnedToTheTop && !m_swipeState.rubberBandsAtTop)
427 return true;
428 if (wheelEvent.deltaY() < 0 && m_swipeState.mainFramePinnedToTheBottom && !m_swipeState.rubberBandsAtBottom)
429 return true;
430
431 return false;
432}
433
434void ScrollingTree::setScrollingPerformanceLoggingEnabled(bool flag)
435{
436 m_scrollingPerformanceLoggingEnabled = flag;
437}
438
439bool ScrollingTree::scrollingPerformanceLoggingEnabled()
440{
441 return m_scrollingPerformanceLoggingEnabled;
442}
443
444ScrollingNodeID ScrollingTree::latchedNode()
445{
446 LockHolder locker(m_treeStateMutex);
447 return m_treeState.latchedNodeID;
448}
449
450void ScrollingTree::setLatchedNode(ScrollingNodeID node)
451{
452 LockHolder locker(m_treeStateMutex);
453 m_treeState.latchedNodeID = node;
454}
455
456void ScrollingTree::clearLatchedNode()
457{
458 LockHolder locker(m_treeStateMutex);
459 m_treeState.latchedNodeID = 0;
460}
461
462String ScrollingTree::scrollingTreeAsText(ScrollingStateTreeAsTextBehavior behavior)
463{
464 TextStream ts(TextStream::LineMode::MultipleLine);
465
466 {
467 TextStream::GroupScope scope(ts);
468 ts << "scrolling tree";
469
470 LockHolder locker(m_treeStateMutex);
471
472 if (m_treeState.latchedNodeID)
473 ts.dumpProperty("latched node", m_treeState.latchedNodeID);
474
475 if (!m_treeState.mainFrameScrollPosition.isZero())
476 ts.dumpProperty("main frame scroll position", m_treeState.mainFrameScrollPosition);
477
478 if (m_rootNode) {
479 TextStream::GroupScope scope(ts);
480 m_rootNode->dump(ts, behavior | ScrollingStateTreeAsTextBehaviorIncludeLayerPositions);
481 }
482
483 if (behavior & ScrollingStateTreeAsTextBehaviorIncludeNodeIDs && !m_overflowRelatedNodesMap.isEmpty()) {
484 TextStream::GroupScope scope(ts);
485 ts << "overflow related nodes";
486 {
487 TextStream::IndentScope indentScope(ts);
488 for (auto& it : m_overflowRelatedNodesMap)
489 ts << "\n" << indent << it.key << " -> " << it.value;
490 }
491 }
492 }
493 return ts.release();
494}
495
496#if ENABLE(POINTER_EVENTS)
497Optional<TouchActionData> ScrollingTree::touchActionDataAtPoint(IntPoint p) const
498{
499 // FIXME: This does not handle the case where there are multiple regions matching this point.
500 for (auto& touchActionData : m_treeState.eventTrackingRegions.touchActionData) {
501 if (touchActionData.region.contains(p))
502 return touchActionData;
503 }
504
505 return { };
506}
507#endif
508
509} // namespace WebCore
510
511#endif // ENABLE(ASYNC_SCROLLING)
512