1/*
2 * Copyright (C) 2005, 2006 Apple Inc.  All rights reserved.
3 * Copyright (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "BackForwardList.h"
29
30#include "Frame.h"
31#include "FrameLoader.h"
32#include "FrameLoaderClient.h"
33#include "HistoryItem.h"
34#include "Logging.h"
35#include "Page.h"
36#include "PageCache.h"
37#include "SerializedScriptValue.h"
38
39namespace WebCore {
40
41static const unsigned DefaultCapacity = 100;
42static const unsigned NoCurrentItemIndex = UINT_MAX;
43
44BackForwardList::BackForwardList(Page* page)
45    : m_page(page)
46    , m_current(NoCurrentItemIndex)
47    , m_capacity(DefaultCapacity)
48    , m_closed(true)
49    , m_enabled(true)
50{
51}
52
53BackForwardList::~BackForwardList()
54{
55    ASSERT(m_closed);
56}
57
58void BackForwardList::addItem(PassRefPtr<HistoryItem> prpItem)
59{
60    ASSERT(prpItem);
61    if (m_capacity == 0 || !m_enabled)
62        return;
63
64    // Toss anything in the forward list
65    if (m_current != NoCurrentItemIndex) {
66        unsigned targetSize = m_current + 1;
67        while (m_entries.size() > targetSize) {
68            RefPtr<HistoryItem> item = m_entries.last();
69            m_entries.removeLast();
70            m_entryHash.remove(item);
71            pageCache()->remove(item.get());
72        }
73    }
74
75    // Toss the first item if the list is getting too big, as long as we're not using it
76    // (or even if we are, if we only want 1 entry).
77    if (m_entries.size() == m_capacity && (m_current != 0 || m_capacity == 1)) {
78        RefPtr<HistoryItem> item = m_entries[0];
79        m_entries.remove(0);
80        m_entryHash.remove(item);
81        pageCache()->remove(item.get());
82        m_current--;
83    }
84
85    m_entryHash.add(prpItem.get());
86    m_entries.insert(m_current + 1, prpItem);
87    m_current++;
88}
89
90void BackForwardList::goBack()
91{
92    ASSERT(m_current > 0);
93    if (m_current > 0) {
94        m_current--;
95    }
96}
97
98void BackForwardList::goForward()
99{
100    ASSERT(m_current < m_entries.size() - 1);
101    if (m_current < m_entries.size() - 1) {
102        m_current++;
103    }
104}
105
106void BackForwardList::goToItem(HistoryItem* item)
107{
108    if (!m_entries.size() || !item)
109        return;
110
111    unsigned int index = 0;
112    for (; index < m_entries.size(); ++index)
113        if (m_entries[index] == item)
114            break;
115    if (index < m_entries.size()) {
116        m_current = index;
117    }
118}
119
120HistoryItem* BackForwardList::backItem()
121{
122    if (m_current && m_current != NoCurrentItemIndex)
123        return m_entries[m_current - 1].get();
124    return 0;
125}
126
127HistoryItem* BackForwardList::currentItem()
128{
129    if (m_current != NoCurrentItemIndex)
130        return m_entries[m_current].get();
131    return 0;
132}
133
134HistoryItem* BackForwardList::forwardItem()
135{
136    if (m_entries.size() && m_current < m_entries.size() - 1)
137        return m_entries[m_current + 1].get();
138    return 0;
139}
140
141void BackForwardList::backListWithLimit(int limit, HistoryItemVector& list)
142{
143    list.clear();
144    if (m_current != NoCurrentItemIndex) {
145        unsigned first = std::max((int)m_current - limit, 0);
146        for (; first < m_current; ++first)
147            list.append(m_entries[first]);
148    }
149}
150
151void BackForwardList::forwardListWithLimit(int limit, HistoryItemVector& list)
152{
153    ASSERT(limit > -1);
154    list.clear();
155    if (!m_entries.size())
156        return;
157
158    unsigned lastEntry = m_entries.size() - 1;
159    if (m_current < lastEntry) {
160        int last = std::min(m_current + limit, lastEntry);
161        limit = m_current + 1;
162        for (; limit <= last; ++limit)
163            list.append(m_entries[limit]);
164    }
165}
166
167int BackForwardList::capacity()
168{
169    return m_capacity;
170}
171
172void BackForwardList::setCapacity(int size)
173{
174    while (size < (int)m_entries.size()) {
175        RefPtr<HistoryItem> item = m_entries.last();
176        m_entries.removeLast();
177        m_entryHash.remove(item);
178        pageCache()->remove(item.get());
179    }
180
181    if (!size)
182        m_current = NoCurrentItemIndex;
183    else if (m_current > m_entries.size() - 1) {
184        m_current = m_entries.size() - 1;
185    }
186    m_capacity = size;
187}
188
189bool BackForwardList::enabled()
190{
191    return m_enabled;
192}
193
194void BackForwardList::setEnabled(bool enabled)
195{
196    m_enabled = enabled;
197    if (!enabled) {
198        int capacity = m_capacity;
199        setCapacity(0);
200        setCapacity(capacity);
201    }
202}
203
204int BackForwardList::backListCount()
205{
206    return m_current == NoCurrentItemIndex ? 0 : m_current;
207}
208
209int BackForwardList::forwardListCount()
210{
211    return m_current == NoCurrentItemIndex ? 0 : (int)m_entries.size() - (m_current + 1);
212}
213
214HistoryItem* BackForwardList::itemAtIndex(int index)
215{
216    // Do range checks without doing math on index to avoid overflow.
217    if (index < -(int)m_current)
218        return 0;
219
220    if (index > forwardListCount())
221        return 0;
222
223    return m_entries[index + m_current].get();
224}
225
226HistoryItemVector& BackForwardList::entries()
227{
228    return m_entries;
229}
230
231#if PLATFORM(IOS)
232unsigned BackForwardList::current()
233{
234    return m_current;
235}
236
237void BackForwardList::setCurrent(unsigned newCurrent)
238{
239    m_current = newCurrent;
240}
241
242bool BackForwardList::clearAllPageCaches()
243{
244    bool didRemoveAtLeastOneItem = false;
245    unsigned length = m_entries.size();
246    for (unsigned i = 0; i < length; ++i) {
247        HistoryItem* item = m_entries[i].get();
248        if (item->isInPageCache()) {
249            didRemoveAtLeastOneItem = true;
250            pageCache()->remove(item);
251        }
252    }
253    return didRemoveAtLeastOneItem;
254}
255#endif
256
257void BackForwardList::close()
258{
259    int size = m_entries.size();
260    for (int i = 0; i < size; ++i)
261        pageCache()->remove(m_entries[i].get());
262    m_entries.clear();
263    m_entryHash.clear();
264    m_page = 0;
265    m_closed = true;
266}
267
268bool BackForwardList::closed()
269{
270    return m_closed;
271}
272
273void BackForwardList::removeItem(HistoryItem* item)
274{
275    if (!item)
276        return;
277
278    for (unsigned i = 0; i < m_entries.size(); ++i)
279        if (m_entries[i] == item) {
280            m_entries.remove(i);
281            m_entryHash.remove(item);
282            if (m_current == NoCurrentItemIndex || m_current < i)
283                break;
284            if (m_current > i)
285                m_current--;
286            else {
287                size_t count = m_entries.size();
288                if (m_current >= count)
289                    m_current = count ? count - 1 : NoCurrentItemIndex;
290            }
291            break;
292        }
293}
294
295bool BackForwardList::containsItem(HistoryItem* entry)
296{
297    return m_entryHash.contains(entry);
298}
299
300}; // namespace WebCore
301