1/*
2 * Copyright (C) 2005, 2006, 2008, 2011 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 COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "core/history/HistoryItem.h"
28
29#include <stdio.h>
30#include "bindings/v8/SerializedScriptValue.h"
31#include "core/dom/Document.h"
32#include "core/platform/network/ResourceRequest.h"
33#include "wtf/CurrentTime.h"
34#include "wtf/text/CString.h"
35
36namespace WebCore {
37
38static long long generateSequenceNumber()
39{
40    // Initialize to the current time to reduce the likelihood of generating
41    // identifiers that overlap with those from past/future browser sessions.
42    static long long next = static_cast<long long>(currentTime() * 1000000.0);
43    return ++next;
44}
45
46HistoryItem::HistoryItem()
47    : m_lastVisitedTime(0)
48    , m_pageScaleFactor(0)
49    , m_isTargetItem(false)
50    , m_visitCount(0)
51    , m_itemSequenceNumber(generateSequenceNumber())
52    , m_documentSequenceNumber(generateSequenceNumber())
53{
54}
55
56HistoryItem::HistoryItem(const String& urlString)
57    : m_urlString(urlString)
58    , m_originalURLString(urlString)
59    , m_lastVisitedTime(0)
60    , m_pageScaleFactor(0)
61    , m_isTargetItem(false)
62    , m_visitCount(0)
63    , m_itemSequenceNumber(generateSequenceNumber())
64    , m_documentSequenceNumber(generateSequenceNumber())
65{
66}
67
68HistoryItem::~HistoryItem()
69{
70}
71
72inline HistoryItem::HistoryItem(const HistoryItem& item)
73    : RefCounted<HistoryItem>()
74    , m_urlString(item.m_urlString)
75    , m_originalURLString(item.m_originalURLString)
76    , m_referrer(item.m_referrer)
77    , m_target(item.m_target)
78    , m_parent(item.m_parent)
79    , m_title(item.m_title)
80    , m_displayTitle(item.m_displayTitle)
81    , m_lastVisitedTime(item.m_lastVisitedTime)
82    , m_scrollPoint(item.m_scrollPoint)
83    , m_pageScaleFactor(item.m_pageScaleFactor)
84    , m_isTargetItem(item.m_isTargetItem)
85    , m_visitCount(item.m_visitCount)
86    , m_itemSequenceNumber(item.m_itemSequenceNumber)
87    , m_documentSequenceNumber(item.m_documentSequenceNumber)
88    , m_formContentType(item.m_formContentType)
89{
90    if (item.m_formData)
91        m_formData = item.m_formData->copy();
92
93    unsigned size = item.m_children.size();
94    m_children.reserveInitialCapacity(size);
95    for (unsigned i = 0; i < size; ++i)
96        m_children.uncheckedAppend(item.m_children[i]->copy());
97}
98
99PassRefPtr<HistoryItem> HistoryItem::copy() const
100{
101    return adoptRef(new HistoryItem(*this));
102}
103
104void HistoryItem::reset()
105{
106    m_urlString = String();
107    m_originalURLString = String();
108    m_referrer = String();
109    m_target = String();
110    m_parent = String();
111    m_title = String();
112    m_displayTitle = String();
113
114    m_lastVisitedTime = 0;
115
116    m_isTargetItem = false;
117    m_visitCount = 0;
118
119    m_itemSequenceNumber = generateSequenceNumber();
120
121    m_stateObject = 0;
122    m_documentSequenceNumber = generateSequenceNumber();
123
124    m_formData = 0;
125    m_formContentType = String();
126
127    clearChildren();
128}
129
130const String& HistoryItem::urlString() const
131{
132    return m_urlString;
133}
134
135// The first URL we loaded to get to where this history item points.  Includes both client
136// and server redirects.
137const String& HistoryItem::originalURLString() const
138{
139    return m_originalURLString;
140}
141
142const String& HistoryItem::title() const
143{
144    return m_title;
145}
146
147const String& HistoryItem::alternateTitle() const
148{
149    return m_displayTitle;
150}
151
152double HistoryItem::lastVisitedTime() const
153{
154    return m_lastVisitedTime;
155}
156
157KURL HistoryItem::url() const
158{
159    return KURL(ParsedURLString, m_urlString);
160}
161
162KURL HistoryItem::originalURL() const
163{
164    return KURL(ParsedURLString, m_originalURLString);
165}
166
167const String& HistoryItem::referrer() const
168{
169    return m_referrer;
170}
171
172const String& HistoryItem::target() const
173{
174    return m_target;
175}
176
177const String& HistoryItem::parent() const
178{
179    return m_parent;
180}
181
182void HistoryItem::setAlternateTitle(const String& alternateTitle)
183{
184    m_displayTitle = alternateTitle;
185}
186
187void HistoryItem::setURLString(const String& urlString)
188{
189    if (m_urlString != urlString)
190        m_urlString = urlString;
191}
192
193void HistoryItem::setURL(const KURL& url)
194{
195    setURLString(url.string());
196    clearDocumentState();
197}
198
199void HistoryItem::setOriginalURLString(const String& urlString)
200{
201    m_originalURLString = urlString;
202}
203
204void HistoryItem::setReferrer(const String& referrer)
205{
206    m_referrer = referrer;
207}
208
209void HistoryItem::setTitle(const String& title)
210{
211    m_title = title;
212}
213
214void HistoryItem::setTarget(const String& target)
215{
216    m_target = target;
217}
218
219void HistoryItem::setParent(const String& parent)
220{
221    m_parent = parent;
222}
223
224void HistoryItem::recordVisitAtTime(double time)
225{
226    m_lastVisitedTime = time;
227    ++m_visitCount;
228}
229
230void HistoryItem::setLastVisitedTime(double time)
231{
232    if (m_lastVisitedTime != time)
233        recordVisitAtTime(time);
234}
235
236int HistoryItem::visitCount() const
237{
238    return m_visitCount;
239}
240
241void HistoryItem::setVisitCount(int count)
242{
243    m_visitCount = count;
244}
245
246const IntPoint& HistoryItem::scrollPoint() const
247{
248    return m_scrollPoint;
249}
250
251void HistoryItem::setScrollPoint(const IntPoint& point)
252{
253    m_scrollPoint = point;
254}
255
256void HistoryItem::clearScrollPoint()
257{
258    m_scrollPoint.setX(0);
259    m_scrollPoint.setY(0);
260}
261
262float HistoryItem::pageScaleFactor() const
263{
264    return m_pageScaleFactor;
265}
266
267void HistoryItem::setPageScaleFactor(float scaleFactor)
268{
269    m_pageScaleFactor = scaleFactor;
270}
271
272void HistoryItem::setDocumentState(const Vector<String>& state)
273{
274    m_documentState = state;
275}
276
277const Vector<String>& HistoryItem::documentState() const
278{
279    return m_documentState;
280}
281
282void HistoryItem::clearDocumentState()
283{
284    m_documentState.clear();
285}
286
287bool HistoryItem::isTargetItem() const
288{
289    return m_isTargetItem;
290}
291
292void HistoryItem::setIsTargetItem(bool flag)
293{
294    m_isTargetItem = flag;
295}
296
297void HistoryItem::setStateObject(PassRefPtr<SerializedScriptValue> object)
298{
299    m_stateObject = object;
300}
301
302void HistoryItem::addChildItem(PassRefPtr<HistoryItem> child)
303{
304    ASSERT(!childItemWithTarget(child->target()));
305    m_children.append(child);
306}
307
308void HistoryItem::setChildItem(PassRefPtr<HistoryItem> child)
309{
310    ASSERT(!child->isTargetItem());
311    unsigned size = m_children.size();
312    for (unsigned i = 0; i < size; ++i)  {
313        if (m_children[i]->target() == child->target()) {
314            child->setIsTargetItem(m_children[i]->isTargetItem());
315            m_children[i] = child;
316            return;
317        }
318    }
319    m_children.append(child);
320}
321
322HistoryItem* HistoryItem::childItemWithTarget(const String& target) const
323{
324    unsigned size = m_children.size();
325    for (unsigned i = 0; i < size; ++i) {
326        if (m_children[i]->target() == target)
327            return m_children[i].get();
328    }
329    return 0;
330}
331
332HistoryItem* HistoryItem::childItemWithDocumentSequenceNumber(long long number) const
333{
334    unsigned size = m_children.size();
335    for (unsigned i = 0; i < size; ++i) {
336        if (m_children[i]->documentSequenceNumber() == number)
337            return m_children[i].get();
338    }
339    return 0;
340}
341
342const HistoryItemVector& HistoryItem::children() const
343{
344    return m_children;
345}
346
347void HistoryItem::clearChildren()
348{
349    m_children.clear();
350}
351
352bool HistoryItem::isAncestorOf(const HistoryItem* item) const
353{
354    for (size_t i = 0; i < m_children.size(); ++i) {
355        HistoryItem* child = m_children[i].get();
356        if (child == item)
357            return true;
358        if (child->isAncestorOf(item))
359            return true;
360    }
361    return false;
362}
363
364// We do same-document navigation if going to a different item and if either of the following is true:
365// - The other item corresponds to the same document (for history entries created via pushState or fragment changes).
366// - The other item corresponds to the same set of documents, including frames (for history entries created via regular navigation)
367bool HistoryItem::shouldDoSameDocumentNavigationTo(HistoryItem* otherItem) const
368{
369    if (this == otherItem)
370        return false;
371
372    if (stateObject() || otherItem->stateObject())
373        return documentSequenceNumber() == otherItem->documentSequenceNumber();
374
375    if ((url().hasFragmentIdentifier() || otherItem->url().hasFragmentIdentifier()) && equalIgnoringFragmentIdentifier(url(), otherItem->url()))
376        return documentSequenceNumber() == otherItem->documentSequenceNumber();
377
378    return hasSameDocumentTree(otherItem);
379}
380
381// Does a recursive check that this item and its descendants have the same
382// document sequence numbers as the other item.
383bool HistoryItem::hasSameDocumentTree(HistoryItem* otherItem) const
384{
385    if (documentSequenceNumber() != otherItem->documentSequenceNumber())
386        return false;
387
388    if (children().size() != otherItem->children().size())
389        return false;
390
391    for (size_t i = 0; i < children().size(); i++) {
392        HistoryItem* child = children()[i].get();
393        HistoryItem* otherChild = otherItem->childItemWithDocumentSequenceNumber(child->documentSequenceNumber());
394        if (!otherChild || !child->hasSameDocumentTree(otherChild))
395            return false;
396    }
397
398    return true;
399}
400
401// Does a non-recursive check that this item and its immediate children have the
402// same frames as the other item.
403bool HistoryItem::hasSameFrames(HistoryItem* otherItem) const
404{
405    if (target() != otherItem->target())
406        return false;
407
408    if (children().size() != otherItem->children().size())
409        return false;
410
411    for (size_t i = 0; i < children().size(); i++) {
412        if (!otherItem->childItemWithTarget(children()[i]->target()))
413            return false;
414    }
415
416    return true;
417}
418
419String HistoryItem::formContentType() const
420{
421    return m_formContentType;
422}
423
424void HistoryItem::setFormInfoFromRequest(const ResourceRequest& request)
425{
426    m_referrer = request.httpReferrer();
427
428    if (equalIgnoringCase(request.httpMethod(), "POST")) {
429        // FIXME: Eventually we have to make this smart enough to handle the case where
430        // we have a stream for the body to handle the "data interspersed with files" feature.
431        m_formData = request.httpBody();
432        m_formContentType = request.httpContentType();
433    } else {
434        m_formData = 0;
435        m_formContentType = String();
436    }
437}
438
439void HistoryItem::setFormData(PassRefPtr<FormData> formData)
440{
441    m_formData = formData;
442}
443
444void HistoryItem::setFormContentType(const String& formContentType)
445{
446    m_formContentType = formContentType;
447}
448
449FormData* HistoryItem::formData()
450{
451    return m_formData.get();
452}
453
454bool HistoryItem::isCurrentDocument(Document* doc) const
455{
456    // FIXME: We should find a better way to check if this is the current document.
457    return equalIgnoringFragmentIdentifier(url(), doc->url());
458}
459
460#ifndef NDEBUG
461
462int HistoryItem::showTree() const
463{
464    return showTreeWithIndent(0);
465}
466
467int HistoryItem::showTreeWithIndent(unsigned indentLevel) const
468{
469    Vector<char> prefix;
470    for (unsigned i = 0; i < indentLevel; ++i)
471        prefix.append("  ", 2);
472    prefix.append("\0", 1);
473
474    fprintf(stderr, "%s+-%s (%p)\n", prefix.data(), m_urlString.utf8().data(), this);
475
476    int totalSubItems = 0;
477    for (unsigned i = 0; i < m_children.size(); ++i)
478        totalSubItems += m_children[i]->showTreeWithIndent(indentLevel + 1);
479    return totalSubItems + 1;
480}
481
482#endif
483
484} // namespace WebCore
485
486#ifndef NDEBUG
487
488int showTree(const WebCore::HistoryItem* item)
489{
490    return item->showTree();
491}
492
493#endif
494