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(THREADED_SCROLLING) || USE(COORDINATED_GRAPHICS)
30
31#include "ScrollingStateFixedNode.h"
32#include "ScrollingStateScrollingNode.h"
33#include "ScrollingStateStickyNode.h"
34
35namespace WebCore {
36
37PassOwnPtr<ScrollingStateTree> ScrollingStateTree::create()
38{
39    return adoptPtr(new ScrollingStateTree);
40}
41
42ScrollingStateTree::ScrollingStateTree()
43    : m_hasChangedProperties(false)
44    , m_hasNewRootStateNode(false)
45{
46}
47
48ScrollingStateTree::~ScrollingStateTree()
49{
50}
51
52ScrollingNodeID ScrollingStateTree::attachNode(ScrollingNodeType nodeType, ScrollingNodeID newNodeID, ScrollingNodeID parentID)
53{
54    ASSERT(newNodeID);
55
56    if (ScrollingStateNode* node = stateNodeForID(newNodeID)) {
57        ScrollingStateNode* parent = stateNodeForID(parentID);
58        if (!parent)
59            return newNodeID;
60        if (node->parent() == parent)
61            return newNodeID;
62
63        // The node is being re-parented. To do that, we'll remove it, and then re-create a new node.
64        removeNode(node);
65    }
66
67    ScrollingStateNode* newNode = 0;
68    if (!parentID) {
69        // If we're resetting the root node, we should clear the HashMap and destroy the current children.
70        clear();
71
72        setRootStateNode(ScrollingStateScrollingNode::create(this, newNodeID));
73        newNode = rootStateNode();
74        m_hasNewRootStateNode = true;
75    } else {
76        ScrollingStateNode* parent = stateNodeForID(parentID);
77        if (!parent)
78            return 0;
79
80        switch (nodeType) {
81        case FixedNode: {
82            OwnPtr<ScrollingStateFixedNode> fixedNode = ScrollingStateFixedNode::create(this, newNodeID);
83            newNode = fixedNode.get();
84            parent->appendChild(fixedNode.release());
85            break;
86        }
87        case StickyNode: {
88            OwnPtr<ScrollingStateStickyNode> stickyNode = ScrollingStateStickyNode::create(this, newNodeID);
89            newNode = stickyNode.get();
90            parent->appendChild(stickyNode.release());
91            break;
92        }
93        case ScrollingNode: {
94            // FIXME: We currently only support child nodes that are fixed.
95            ASSERT_NOT_REACHED();
96            OwnPtr<ScrollingStateScrollingNode> scrollingNode = ScrollingStateScrollingNode::create(this, newNodeID);
97            newNode = scrollingNode.get();
98            parent->appendChild(scrollingNode.release());
99            break;
100        }
101        }
102    }
103
104    m_stateNodeMap.set(newNodeID, newNode);
105    return newNodeID;
106}
107
108void ScrollingStateTree::detachNode(ScrollingNodeID nodeID)
109{
110    if (!nodeID)
111        return;
112
113    // The node may not be found if clearStateTree() was recently called.
114    ScrollingStateNode* node = m_stateNodeMap.take(nodeID);
115    if (!node)
116        return;
117
118    removeNode(node);
119}
120
121void ScrollingStateTree::clear()
122{
123    removeNode(rootStateNode());
124    m_stateNodeMap.clear();
125}
126
127PassOwnPtr<ScrollingStateTree> ScrollingStateTree::commit()
128{
129    // This function clones and resets the current state tree, but leaves the tree structure intact.
130    OwnPtr<ScrollingStateTree> treeStateClone = ScrollingStateTree::create();
131    if (m_rootStateNode)
132        treeStateClone->setRootStateNode(static_pointer_cast<ScrollingStateScrollingNode>(m_rootStateNode->cloneAndReset()));
133
134    // Copy the IDs of the nodes that have been removed since the last commit into the clone.
135    treeStateClone->m_nodesRemovedSinceLastCommit.swap(m_nodesRemovedSinceLastCommit);
136
137    // Now the clone tree has changed properties, and the original tree does not.
138    treeStateClone->m_hasChangedProperties = true;
139    m_hasChangedProperties = false;
140
141    treeStateClone->m_hasNewRootStateNode = m_hasNewRootStateNode;
142    m_hasNewRootStateNode = false;
143
144    return treeStateClone.release();
145}
146
147void ScrollingStateTree::removeNode(ScrollingStateNode* node)
148{
149    if (!node)
150        return;
151
152    if (node == m_rootStateNode) {
153        didRemoveNode(node->scrollingNodeID());
154        m_rootStateNode = nullptr;
155        return;
156    }
157
158    ASSERT(m_rootStateNode);
159    m_rootStateNode->removeChild(node);
160
161    // ScrollingStateTree::removeNode() will destroy children, so we have to make sure we remove those children
162    // from the HashMap.
163    size_t size = m_nodesRemovedSinceLastCommit.size();
164    for (size_t i = 0; i < size; ++i)
165        m_stateNodeMap.remove(m_nodesRemovedSinceLastCommit[i]);
166}
167
168void ScrollingStateTree::didRemoveNode(ScrollingNodeID nodeID)
169{
170    m_nodesRemovedSinceLastCommit.append(nodeID);
171    m_hasChangedProperties = true;
172}
173
174ScrollingStateNode* ScrollingStateTree::stateNodeForID(ScrollingNodeID scrollLayerID)
175{
176    if (!scrollLayerID)
177        return 0;
178
179    HashMap<ScrollingNodeID, ScrollingStateNode*>::const_iterator it = m_stateNodeMap.find(scrollLayerID);
180    if (it == m_stateNodeMap.end())
181        return 0;
182
183    ASSERT(it->value->scrollingNodeID() == scrollLayerID);
184    return it->value;
185}
186
187} // namespace WebCore
188
189#endif // ENABLE(THREADED_SCROLLING) || USE(COORDINATED_GRAPHICS)
190