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::applyLayerPositionsAfterCommit()
258{
259 // Scrolling tree needs to make adjustments only if the UI side positions have changed.
260 if (!m_wasScrolledByDelegatedScrollingSincePreviousCommit)
261 return;
262 m_wasScrolledByDelegatedScrollingSincePreviousCommit = false;
263
264 applyLayerPositions();
265}
266
267void ScrollingTree::applyLayerPositions()
268{
269 ASSERT(isMainThread());
270 LockHolder locker(m_treeMutex);
271
272 if (!m_rootNode)
273 return;
274
275 LOG(Scrolling, "\nScrollingTree %p applyLayerPositions", this);
276
277 applyLayerPositionsRecursive(*m_rootNode);
278
279 LOG(Scrolling, "ScrollingTree %p applyLayerPositions - done\n", this);
280}
281
282void ScrollingTree::applyLayerPositionsRecursive(ScrollingTreeNode& currNode)
283{
284 currNode.applyLayerPositions();
285
286 if (auto children = currNode.children()) {
287 for (auto& child : *children)
288 applyLayerPositionsRecursive(*child);
289 }
290}
291
292ScrollingTreeNode* ScrollingTree::nodeForID(ScrollingNodeID nodeID) const
293{
294 if (!nodeID)
295 return nullptr;
296
297 return m_nodeMap.get(nodeID);
298}
299
300void ScrollingTree::notifyRelatedNodesAfterScrollPositionChange(ScrollingTreeScrollingNode& changedNode)
301{
302 Vector<ScrollingNodeID> additionalUpdateRoots;
303
304 if (is<ScrollingTreeOverflowScrollingNode>(changedNode))
305 additionalUpdateRoots = overflowRelatedNodes().get(changedNode.scrollingNodeID());
306
307 notifyRelatedNodesRecursive(changedNode);
308
309 for (auto positionedNodeID : additionalUpdateRoots) {
310 auto* positionedNode = nodeForID(positionedNodeID);
311 if (positionedNode)
312 notifyRelatedNodesRecursive(*positionedNode);
313 }
314}
315
316void ScrollingTree::notifyRelatedNodesRecursive(ScrollingTreeNode& node)
317{
318 node.applyLayerPositions();
319
320 if (!node.children())
321 return;
322
323 for (auto& child : *node.children()) {
324 // Never need to cross frame boundaries, since scroll layer adjustments are isolated to each document.
325 if (is<ScrollingTreeFrameScrollingNode>(child))
326 continue;
327
328 notifyRelatedNodesRecursive(*child);
329 }
330}
331
332void ScrollingTree::setAsyncFrameOrOverflowScrollingEnabled(bool enabled)
333{
334 m_asyncFrameOrOverflowScrollingEnabled = enabled;
335}
336
337void ScrollingTree::setMainFrameScrollPosition(FloatPoint position)
338{
339 LockHolder lock(m_treeStateMutex);
340 m_treeState.mainFrameScrollPosition = position;
341}
342
343TrackingType ScrollingTree::eventTrackingTypeForPoint(const AtomString& eventName, IntPoint p)
344{
345 LockHolder lock(m_treeStateMutex);
346 return m_treeState.eventTrackingRegions.trackingTypeForPoint(eventName, p);
347}
348
349// Can be called from the main thread.
350bool ScrollingTree::isRubberBandInProgress()
351{
352 LockHolder lock(m_treeStateMutex);
353 return m_treeState.mainFrameIsRubberBanding;
354}
355
356void ScrollingTree::setMainFrameIsRubberBanding(bool isRubberBanding)
357{
358 LockHolder locker(m_treeStateMutex);
359 m_treeState.mainFrameIsRubberBanding = isRubberBanding;
360}
361
362// Can be called from the main thread.
363bool ScrollingTree::isScrollSnapInProgress()
364{
365 LockHolder lock(m_treeStateMutex);
366 return m_treeState.mainFrameIsScrollSnapping;
367}
368
369void ScrollingTree::setMainFrameIsScrollSnapping(bool isScrollSnapping)
370{
371 LockHolder locker(m_treeStateMutex);
372 m_treeState.mainFrameIsScrollSnapping = isScrollSnapping;
373}
374
375void ScrollingTree::setMainFramePinState(bool pinnedToTheLeft, bool pinnedToTheRight, bool pinnedToTheTop, bool pinnedToTheBottom)
376{
377 LockHolder locker(m_swipeStateMutex);
378
379 m_swipeState.mainFramePinnedToTheLeft = pinnedToTheLeft;
380 m_swipeState.mainFramePinnedToTheRight = pinnedToTheRight;
381 m_swipeState.mainFramePinnedToTheTop = pinnedToTheTop;
382 m_swipeState.mainFramePinnedToTheBottom = pinnedToTheBottom;
383}
384
385void ScrollingTree::setCanRubberBandState(bool canRubberBandAtLeft, bool canRubberBandAtRight, bool canRubberBandAtTop, bool canRubberBandAtBottom)
386{
387 LockHolder locker(m_swipeStateMutex);
388
389 m_swipeState.rubberBandsAtLeft = canRubberBandAtLeft;
390 m_swipeState.rubberBandsAtRight = canRubberBandAtRight;
391 m_swipeState.rubberBandsAtTop = canRubberBandAtTop;
392 m_swipeState.rubberBandsAtBottom = canRubberBandAtBottom;
393}
394
395// Can be called from the main thread.
396void ScrollingTree::setScrollPinningBehavior(ScrollPinningBehavior pinning)
397{
398 LockHolder locker(m_swipeStateMutex);
399
400 m_swipeState.scrollPinningBehavior = pinning;
401}
402
403ScrollPinningBehavior ScrollingTree::scrollPinningBehavior()
404{
405 LockHolder lock(m_swipeStateMutex);
406
407 return m_swipeState.scrollPinningBehavior;
408}
409
410bool ScrollingTree::willWheelEventStartSwipeGesture(const PlatformWheelEvent& wheelEvent)
411{
412 if (wheelEvent.phase() != PlatformWheelEventPhaseBegan)
413 return false;
414
415 LockHolder lock(m_swipeStateMutex);
416
417 if (wheelEvent.deltaX() > 0 && m_swipeState.mainFramePinnedToTheLeft && !m_swipeState.rubberBandsAtLeft)
418 return true;
419 if (wheelEvent.deltaX() < 0 && m_swipeState.mainFramePinnedToTheRight && !m_swipeState.rubberBandsAtRight)
420 return true;
421 if (wheelEvent.deltaY() > 0 && m_swipeState.mainFramePinnedToTheTop && !m_swipeState.rubberBandsAtTop)
422 return true;
423 if (wheelEvent.deltaY() < 0 && m_swipeState.mainFramePinnedToTheBottom && !m_swipeState.rubberBandsAtBottom)
424 return true;
425
426 return false;
427}
428
429void ScrollingTree::setScrollingPerformanceLoggingEnabled(bool flag)
430{
431 m_scrollingPerformanceLoggingEnabled = flag;
432}
433
434bool ScrollingTree::scrollingPerformanceLoggingEnabled()
435{
436 return m_scrollingPerformanceLoggingEnabled;
437}
438
439ScrollingNodeID ScrollingTree::latchedNode()
440{
441 LockHolder locker(m_treeStateMutex);
442 return m_treeState.latchedNodeID;
443}
444
445void ScrollingTree::setLatchedNode(ScrollingNodeID node)
446{
447 LockHolder locker(m_treeStateMutex);
448 m_treeState.latchedNodeID = node;
449}
450
451void ScrollingTree::clearLatchedNode()
452{
453 LockHolder locker(m_treeStateMutex);
454 m_treeState.latchedNodeID = 0;
455}
456
457String ScrollingTree::scrollingTreeAsText(ScrollingStateTreeAsTextBehavior behavior)
458{
459 TextStream ts(TextStream::LineMode::MultipleLine);
460
461 {
462 TextStream::GroupScope scope(ts);
463 ts << "scrolling tree";
464
465 LockHolder locker(m_treeStateMutex);
466
467 if (m_treeState.latchedNodeID)
468 ts.dumpProperty("latched node", m_treeState.latchedNodeID);
469
470 if (!m_treeState.mainFrameScrollPosition.isZero())
471 ts.dumpProperty("main frame scroll position", m_treeState.mainFrameScrollPosition);
472
473 if (m_rootNode) {
474 TextStream::GroupScope scope(ts);
475 m_rootNode->dump(ts, behavior | ScrollingStateTreeAsTextBehaviorIncludeLayerPositions);
476 }
477
478 if (behavior & ScrollingStateTreeAsTextBehaviorIncludeNodeIDs && !m_overflowRelatedNodesMap.isEmpty()) {
479 TextStream::GroupScope scope(ts);
480 ts << "overflow related nodes";
481 {
482 TextStream::IndentScope indentScope(ts);
483 for (auto& it : m_overflowRelatedNodesMap)
484 ts << "\n" << indent << it.key << " -> " << it.value;
485 }
486 }
487 }
488 return ts.release();
489}
490
491#if ENABLE(POINTER_EVENTS)
492Optional<TouchActionData> ScrollingTree::touchActionDataAtPoint(IntPoint p) const
493{
494 // FIXME: This does not handle the case where there are multiple regions matching this point.
495 for (auto& touchActionData : m_treeState.eventTrackingRegions.touchActionData) {
496 if (touchActionData.region.contains(p))
497 return touchActionData;
498 }
499
500 return { };
501}
502#endif
503
504} // namespace WebCore
505
506#endif // ENABLE(ASYNC_SCROLLING)
507