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 "ScrollingStateTree.h"
28
29#if ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS)
30
31#include "AsyncScrollingCoordinator.h"
32#include "ScrollingStateFixedNode.h"
33#include "ScrollingStateFrameScrollingNode.h"
34#include "ScrollingStateOverflowScrollingNode.h"
35#include "ScrollingStateStickyNode.h"
36
37namespace WebCore {
38
39PassOwnPtr<ScrollingStateTree> ScrollingStateTree::create(AsyncScrollingCoordinator* scrollingCoordinator)
40{
41    return adoptPtr(new ScrollingStateTree(scrollingCoordinator));
42}
43
44ScrollingStateTree::ScrollingStateTree(AsyncScrollingCoordinator* scrollingCoordinator)
45    : m_scrollingCoordinator(scrollingCoordinator)
46    , m_hasChangedProperties(false)
47    , m_hasNewRootStateNode(false)
48    , m_preferredLayerRepresentation(LayerRepresentation::GraphicsLayerRepresentation)
49{
50}
51
52ScrollingStateTree::~ScrollingStateTree()
53{
54}
55
56void ScrollingStateTree::setHasChangedProperties(bool changedProperties)
57{
58#if ENABLE(ASYNC_SCROLLING)
59    bool gainedChangedProperties = !m_hasChangedProperties && changedProperties;
60#endif
61
62    m_hasChangedProperties = changedProperties;
63
64#if ENABLE(ASYNC_SCROLLING)
65    if (gainedChangedProperties && m_scrollingCoordinator)
66        m_scrollingCoordinator->scrollingStateTreePropertiesChanged();
67#endif
68}
69
70PassRefPtr<ScrollingStateNode> ScrollingStateTree::createNode(ScrollingNodeType nodeType, ScrollingNodeID nodeID)
71{
72    switch (nodeType) {
73    case FixedNode:
74        return ScrollingStateFixedNode::create(*this, nodeID);
75    case StickyNode:
76        return ScrollingStateStickyNode::create(*this, nodeID);
77    case FrameScrollingNode:
78        return ScrollingStateFrameScrollingNode::create(*this, nodeID);
79    case OverflowScrollingNode:
80        return ScrollingStateOverflowScrollingNode::create(*this, nodeID);
81    }
82    ASSERT_NOT_REACHED();
83    return nullptr;
84}
85
86ScrollingNodeID ScrollingStateTree::attachNode(ScrollingNodeType nodeType, ScrollingNodeID newNodeID, ScrollingNodeID parentID)
87{
88    ASSERT(newNodeID);
89    if (ScrollingStateNode* node = stateNodeForID(newNodeID)) {
90        if (!parentID)
91            return newNodeID;
92
93        ScrollingStateNode* parent = stateNodeForID(parentID);
94        if (!parent)
95            return newNodeID;
96
97        if (node->parent() == parent)
98            return newNodeID;
99
100        // The node is being re-parented. To do that, we'll remove it, and then re-create a new node.
101        removeNodeAndAllDescendants(node, SubframeNodeRemoval::Orphan);
102    }
103
104    ScrollingStateNode* newNode = nullptr;
105    if (!parentID) {
106        // If we're resetting the root node, we should clear the HashMap and destroy the current children.
107        clear();
108
109        setRootStateNode(ScrollingStateFrameScrollingNode::create(*this, newNodeID));
110        newNode = rootStateNode();
111        m_hasNewRootStateNode = true;
112    } else {
113        ScrollingStateNode* parent = stateNodeForID(parentID);
114        if (!parent)
115            return 0;
116
117        if (nodeType == FrameScrollingNode && parentID) {
118            if (RefPtr<ScrollingStateNode> orphanedNode = m_orphanedSubframeNodes.take(newNodeID)) {
119                newNode = orphanedNode.get();
120                parent->appendChild(orphanedNode.release());
121            }
122        }
123
124        if (!newNode) {
125            RefPtr<ScrollingStateNode> stateNode = createNode(nodeType, newNodeID);
126            newNode = stateNode.get();
127            parent->appendChild(stateNode.release());
128        }
129    }
130
131    m_stateNodeMap.set(newNodeID, newNode);
132    m_nodesRemovedSinceLastCommit.remove(newNodeID);
133    return newNodeID;
134}
135
136void ScrollingStateTree::detachNode(ScrollingNodeID nodeID)
137{
138    if (!nodeID)
139        return;
140
141    // The node may not be found if clearStateTree() was recently called.
142    ScrollingStateNode* node = m_stateNodeMap.take(nodeID);
143    if (!node)
144        return;
145
146    removeNodeAndAllDescendants(node, SubframeNodeRemoval::Orphan);
147}
148
149void ScrollingStateTree::clear()
150{
151    if (rootStateNode())
152        removeNodeAndAllDescendants(rootStateNode());
153
154    m_stateNodeMap.clear();
155    m_orphanedSubframeNodes.clear();
156}
157
158PassOwnPtr<ScrollingStateTree> ScrollingStateTree::commit(LayerRepresentation::Type preferredLayerRepresentation)
159{
160    if (!m_orphanedSubframeNodes.isEmpty()) {
161        // If we still have orphaned subtrees, remove them from m_stateNodeMap since they will be deleted
162        // when clearing m_orphanedSubframeNodes.
163        for (auto& orphanNode : m_orphanedSubframeNodes.values())
164            recursiveNodeWillBeRemoved(orphanNode.get(), SubframeNodeRemoval::Delete);
165        m_orphanedSubframeNodes.clear();
166    }
167
168    // This function clones and resets the current state tree, but leaves the tree structure intact.
169    OwnPtr<ScrollingStateTree> treeStateClone = ScrollingStateTree::create();
170    treeStateClone->setPreferredLayerRepresentation(preferredLayerRepresentation);
171
172    if (m_rootStateNode)
173        treeStateClone->setRootStateNode(static_pointer_cast<ScrollingStateFrameScrollingNode>(m_rootStateNode->cloneAndReset(*treeStateClone)));
174
175    // Copy the IDs of the nodes that have been removed since the last commit into the clone.
176    treeStateClone->m_nodesRemovedSinceLastCommit.swap(m_nodesRemovedSinceLastCommit);
177
178    // Now the clone tree has changed properties, and the original tree does not.
179    treeStateClone->m_hasChangedProperties = m_hasChangedProperties;
180    m_hasChangedProperties = false;
181
182    treeStateClone->m_hasNewRootStateNode = m_hasNewRootStateNode;
183    m_hasNewRootStateNode = false;
184
185    return treeStateClone.release();
186}
187
188void ScrollingStateTree::addNode(ScrollingStateNode* node)
189{
190    m_stateNodeMap.add(node->scrollingNodeID(), node);
191}
192
193void ScrollingStateTree::removeNodeAndAllDescendants(ScrollingStateNode* node, SubframeNodeRemoval subframeNodeRemoval)
194{
195    ScrollingStateNode* parent = node->parent();
196
197    recursiveNodeWillBeRemoved(node, subframeNodeRemoval);
198
199    if (node == m_rootStateNode)
200        m_rootStateNode = nullptr;
201    else if (parent) {
202        ASSERT(parent->children() && parent->children()->find(node) != notFound);
203        if (auto children = parent->children()) {
204            size_t index = children->find(node);
205            if (index != notFound)
206                children->remove(index);
207        }
208    }
209}
210
211void ScrollingStateTree::recursiveNodeWillBeRemoved(ScrollingStateNode* currNode, SubframeNodeRemoval subframeNodeRemoval)
212{
213    currNode->setParent(nullptr);
214    if (subframeNodeRemoval == SubframeNodeRemoval::Orphan && currNode != m_rootStateNode && currNode->isFrameScrollingNode()) {
215        m_orphanedSubframeNodes.add(currNode->scrollingNodeID(), currNode);
216        return;
217    }
218
219    willRemoveNode(currNode);
220
221    if (auto children = currNode->children()) {
222        for (auto& child : *children)
223            recursiveNodeWillBeRemoved(child.get(), subframeNodeRemoval);
224    }
225}
226
227void ScrollingStateTree::willRemoveNode(ScrollingStateNode* node)
228{
229    m_nodesRemovedSinceLastCommit.add(node->scrollingNodeID());
230    m_stateNodeMap.remove(node->scrollingNodeID());
231    setHasChangedProperties();
232}
233
234void ScrollingStateTree::setRemovedNodes(HashSet<ScrollingNodeID> nodes)
235{
236    m_nodesRemovedSinceLastCommit = WTF::move(nodes);
237}
238
239ScrollingStateNode* ScrollingStateTree::stateNodeForID(ScrollingNodeID scrollLayerID)
240{
241    if (!scrollLayerID)
242        return 0;
243
244    auto it = m_stateNodeMap.find(scrollLayerID);
245    if (it == m_stateNodeMap.end())
246        return 0;
247
248    ASSERT(it->value->scrollingNodeID() == scrollLayerID);
249    return it->value;
250}
251
252} // namespace WebCore
253
254#endif // ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS)
255