1/*
2 * Copyright (C) 2012, 2013, 2014 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 "PlatformWheelEvent.h"
32#include "ScrollingStateTree.h"
33#include "ScrollingTreeNode.h"
34#include "ScrollingTreeScrollingNode.h"
35#include <wtf/TemporaryChange.h>
36
37namespace WebCore {
38
39ScrollingTree::ScrollingTree()
40    : m_hasWheelEventHandlers(false)
41    , m_rubberBandsAtLeft(true)
42    , m_rubberBandsAtRight(true)
43    , m_rubberBandsAtTop(true)
44    , m_rubberBandsAtBottom(true)
45    , m_mainFramePinnedToTheLeft(true)
46    , m_mainFramePinnedToTheRight(true)
47    , m_mainFramePinnedToTheTop(true)
48    , m_mainFramePinnedToTheBottom(true)
49    , m_mainFrameIsRubberBanding(false)
50    , m_scrollPinningBehavior(DoNotPin)
51    , m_latchedNode(0)
52    , m_scrollingPerformanceLoggingEnabled(false)
53    , m_isHandlingProgrammaticScroll(false)
54    , m_fixedOrStickyNodeCount(0)
55{
56}
57
58ScrollingTree::~ScrollingTree()
59{
60}
61
62bool ScrollingTree::shouldHandleWheelEventSynchronously(const PlatformWheelEvent& wheelEvent)
63{
64    // This method is invoked by the event handling thread
65    MutexLocker lock(m_mutex);
66
67    if (m_hasWheelEventHandlers)
68        return true;
69
70    bool shouldSetLatch = wheelEvent.shouldConsiderLatching();
71
72    if (hasLatchedNode() && !shouldSetLatch)
73        return false;
74
75    if (shouldSetLatch)
76        m_latchedNode = 0;
77
78    if (!m_nonFastScrollableRegion.isEmpty()) {
79        // FIXME: This is not correct for non-default scroll origins.
80        FloatPoint position = wheelEvent.position();
81        position.moveBy(m_mainFrameScrollPosition);
82        if (m_nonFastScrollableRegion.contains(roundedIntPoint(position)))
83            return true;
84    }
85    return false;
86}
87
88void ScrollingTree::setOrClearLatchedNode(const PlatformWheelEvent& wheelEvent, ScrollingNodeID nodeID)
89{
90    if (wheelEvent.shouldConsiderLatching())
91        setLatchedNode(nodeID);
92    else if (wheelEvent.shouldResetLatching())
93        clearLatchedNode();
94}
95
96void ScrollingTree::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
97{
98    if (m_rootNode)
99        toScrollingTreeScrollingNode(m_rootNode.get())->handleWheelEvent(wheelEvent);
100}
101
102void ScrollingTree::viewportChangedViaDelegatedScrolling(ScrollingNodeID nodeID, const WebCore::FloatRect& fixedPositionRect, double scale)
103{
104    ScrollingTreeNode* node = nodeForID(nodeID);
105    if (!node)
106        return;
107
108    if (!node->isScrollingNode())
109        return;
110
111    toScrollingTreeScrollingNode(node)->updateLayersAfterViewportChange(fixedPositionRect, scale);
112}
113
114void ScrollingTree::scrollPositionChangedViaDelegatedScrolling(ScrollingNodeID nodeID, const WebCore::FloatPoint& scrollPosition, bool inUserInteration)
115{
116    ScrollingTreeNode* node = nodeForID(nodeID);
117    if (!node)
118        return;
119
120    if (node->nodeType() != OverflowScrollingNode)
121        return;
122
123    // Update descendant nodes
124    toScrollingTreeScrollingNode(node)->updateLayersAfterDelegatedScroll(scrollPosition);
125
126    // Update GraphicsLayers and scroll state.
127    scrollingTreeNodeDidScroll(nodeID, scrollPosition, inUserInteration ? SyncScrollingLayerPosition : SetScrollingLayerPosition);
128}
129
130void ScrollingTree::commitNewTreeState(PassOwnPtr<ScrollingStateTree> scrollingStateTree)
131{
132    bool rootStateNodeChanged = scrollingStateTree->hasNewRootStateNode();
133
134    ScrollingStateScrollingNode* rootNode = scrollingStateTree->rootStateNode();
135    if (rootNode
136        && (rootStateNodeChanged
137            || rootNode->hasChangedProperty(ScrollingStateFrameScrollingNode::WheelEventHandlerCount)
138            || rootNode->hasChangedProperty(ScrollingStateFrameScrollingNode::NonFastScrollableRegion)
139            || rootNode->hasChangedProperty(ScrollingStateNode::ScrollLayer))) {
140        MutexLocker lock(m_mutex);
141
142        if (rootStateNodeChanged || rootNode->hasChangedProperty(ScrollingStateNode::ScrollLayer))
143            m_mainFrameScrollPosition = FloatPoint();
144        if (rootStateNodeChanged || rootNode->hasChangedProperty(ScrollingStateFrameScrollingNode::WheelEventHandlerCount))
145            m_hasWheelEventHandlers = scrollingStateTree->rootStateNode()->wheelEventHandlerCount();
146        if (rootStateNodeChanged || rootNode->hasChangedProperty(ScrollingStateFrameScrollingNode::NonFastScrollableRegion))
147            m_nonFastScrollableRegion = scrollingStateTree->rootStateNode()->nonFastScrollableRegion();
148    }
149
150    bool scrollRequestIsProgammatic = rootNode ? rootNode->requestedScrollPositionRepresentsProgrammaticScroll() : false;
151    TemporaryChange<bool> changeHandlingProgrammaticScroll(m_isHandlingProgrammaticScroll, scrollRequestIsProgammatic);
152
153    removeDestroyedNodes(*scrollingStateTree);
154
155    OrphanScrollingNodeMap orphanNodes;
156    updateTreeFromStateNode(rootNode, orphanNodes);
157}
158
159void ScrollingTree::updateTreeFromStateNode(const ScrollingStateNode* stateNode, OrphanScrollingNodeMap& orphanNodes)
160{
161    if (!stateNode) {
162        m_nodeMap.clear();
163        m_rootNode = nullptr;
164        return;
165    }
166
167    ScrollingNodeID nodeID = stateNode->scrollingNodeID();
168    ScrollingNodeID parentNodeID = stateNode->parentNodeID();
169
170    auto it = m_nodeMap.find(nodeID);
171
172    RefPtr<ScrollingTreeNode> node;
173    if (it != m_nodeMap.end())
174        node = it->value;
175    else {
176        node = createScrollingTreeNode(stateNode->nodeType(), nodeID);
177        if (!parentNodeID) {
178            // This is the root node. Clear the node map.
179            ASSERT(stateNode->nodeType() == FrameScrollingNode);
180            m_rootNode = node;
181            m_nodeMap.clear();
182        }
183        m_nodeMap.set(nodeID, node.get());
184    }
185
186    if (parentNodeID) {
187        auto parentIt = m_nodeMap.find(parentNodeID);
188        ASSERT_WITH_SECURITY_IMPLICATION(parentIt != m_nodeMap.end());
189        if (parentIt != m_nodeMap.end()) {
190            ScrollingTreeNode* parent = parentIt->value;
191            node->setParent(parent);
192            parent->appendChild(node);
193        }
194    }
195
196    node->updateBeforeChildren(*stateNode);
197
198    // Move all children into the orphanNodes map. Live ones will get added back as we recurse over children.
199    if (auto nodeChildren = node->children()) {
200        for (auto& childScrollingNode : *nodeChildren) {
201            childScrollingNode->setParent(nullptr);
202            orphanNodes.add(childScrollingNode->scrollingNodeID(), childScrollingNode);
203        }
204        nodeChildren->clear();
205    }
206
207    // Now update the children if we have any.
208    if (auto children = stateNode->children()) {
209        for (auto& child : *children)
210            updateTreeFromStateNode(child.get(), orphanNodes);
211    }
212
213    node->updateAfterChildren(*stateNode);
214}
215
216void ScrollingTree::removeDestroyedNodes(const ScrollingStateTree& stateTree)
217{
218    for (const auto& removedNodeID : stateTree.removedNodes()) {
219        m_nodeMap.remove(removedNodeID);
220        if (removedNodeID == m_latchedNode)
221            clearLatchedNode();
222    }
223}
224
225ScrollingTreeNode* ScrollingTree::nodeForID(ScrollingNodeID nodeID) const
226{
227    if (!nodeID)
228        return nullptr;
229
230    return m_nodeMap.get(nodeID);
231}
232
233void ScrollingTree::setMainFramePinState(bool pinnedToTheLeft, bool pinnedToTheRight, bool pinnedToTheTop, bool pinnedToTheBottom)
234{
235    MutexLocker locker(m_swipeStateMutex);
236
237    m_mainFramePinnedToTheLeft = pinnedToTheLeft;
238    m_mainFramePinnedToTheRight = pinnedToTheRight;
239    m_mainFramePinnedToTheTop = pinnedToTheTop;
240    m_mainFramePinnedToTheBottom = pinnedToTheBottom;
241}
242
243FloatPoint ScrollingTree::mainFrameScrollPosition()
244{
245    MutexLocker lock(m_mutex);
246    return m_mainFrameScrollPosition;
247}
248
249void ScrollingTree::setMainFrameScrollPosition(FloatPoint position)
250{
251    MutexLocker lock(m_mutex);
252    m_mainFrameScrollPosition = position;
253}
254
255bool ScrollingTree::isPointInNonFastScrollableRegion(IntPoint p)
256{
257    MutexLocker lock(m_mutex);
258
259    return m_nonFastScrollableRegion.contains(p);
260}
261
262bool ScrollingTree::isRubberBandInProgress()
263{
264    MutexLocker lock(m_mutex);
265
266    return m_mainFrameIsRubberBanding;
267}
268
269void ScrollingTree::setMainFrameIsRubberBanding(bool isRubberBanding)
270{
271    MutexLocker locker(m_mutex);
272
273    m_mainFrameIsRubberBanding = isRubberBanding;
274}
275
276void ScrollingTree::setCanRubberBandState(bool canRubberBandAtLeft, bool canRubberBandAtRight, bool canRubberBandAtTop, bool canRubberBandAtBottom)
277{
278    MutexLocker locker(m_swipeStateMutex);
279
280    m_rubberBandsAtLeft = canRubberBandAtLeft;
281    m_rubberBandsAtRight = canRubberBandAtRight;
282    m_rubberBandsAtTop = canRubberBandAtTop;
283    m_rubberBandsAtBottom = canRubberBandAtBottom;
284}
285
286bool ScrollingTree::rubberBandsAtLeft()
287{
288    MutexLocker lock(m_swipeStateMutex);
289
290    return m_rubberBandsAtLeft;
291}
292
293bool ScrollingTree::rubberBandsAtRight()
294{
295    MutexLocker lock(m_swipeStateMutex);
296
297    return m_rubberBandsAtRight;
298}
299
300bool ScrollingTree::rubberBandsAtBottom()
301{
302    MutexLocker lock(m_swipeStateMutex);
303
304    return m_rubberBandsAtBottom;
305}
306
307bool ScrollingTree::rubberBandsAtTop()
308{
309    MutexLocker lock(m_swipeStateMutex);
310
311    return m_rubberBandsAtTop;
312}
313
314bool ScrollingTree::isHandlingProgrammaticScroll()
315{
316    return m_isHandlingProgrammaticScroll;
317}
318
319void ScrollingTree::setScrollPinningBehavior(ScrollPinningBehavior pinning)
320{
321    MutexLocker locker(m_swipeStateMutex);
322
323    m_scrollPinningBehavior = pinning;
324}
325
326ScrollPinningBehavior ScrollingTree::scrollPinningBehavior()
327{
328    MutexLocker lock(m_swipeStateMutex);
329
330    return m_scrollPinningBehavior;
331}
332
333bool ScrollingTree::willWheelEventStartSwipeGesture(const PlatformWheelEvent& wheelEvent)
334{
335    if (wheelEvent.phase() != PlatformWheelEventPhaseBegan)
336        return false;
337
338    MutexLocker lock(m_swipeStateMutex);
339
340    if (wheelEvent.deltaX() > 0 && m_mainFramePinnedToTheLeft && !m_rubberBandsAtLeft)
341        return true;
342    if (wheelEvent.deltaX() < 0 && m_mainFramePinnedToTheRight && !m_rubberBandsAtRight)
343        return true;
344    if (wheelEvent.deltaY() > 0 && m_mainFramePinnedToTheTop && !m_rubberBandsAtTop)
345        return true;
346    if (wheelEvent.deltaY() < 0 && m_mainFramePinnedToTheBottom && !m_rubberBandsAtBottom)
347        return true;
348
349    return false;
350}
351
352void ScrollingTree::setScrollingPerformanceLoggingEnabled(bool flag)
353{
354    m_scrollingPerformanceLoggingEnabled = flag;
355}
356
357bool ScrollingTree::scrollingPerformanceLoggingEnabled()
358{
359    return m_scrollingPerformanceLoggingEnabled;
360}
361
362ScrollingNodeID ScrollingTree::latchedNode()
363{
364    MutexLocker locker(m_mutex);
365    return m_latchedNode;
366}
367
368void ScrollingTree::setLatchedNode(ScrollingNodeID node)
369{
370    MutexLocker locker(m_mutex);
371    m_latchedNode = node;
372}
373
374void ScrollingTree::clearLatchedNode()
375{
376    MutexLocker locker(m_mutex);
377    m_latchedNode = 0;
378}
379
380} // namespace WebCore
381
382#endif // ENABLE(ASYNC_SCROLLING)
383