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 "HistoryItem.h"
28
29#include "CachedPage.h"
30#include "Document.h"
31#include "IconDatabase.h"
32#include "PageCache.h"
33#include "ResourceRequest.h"
34#include "SerializedScriptValue.h"
35#include "SharedBuffer.h"
36#include <stdio.h>
37#include <wtf/CurrentTime.h>
38#include <wtf/Decoder.h>
39#include <wtf/Encoder.h>
40#include <wtf/MathExtras.h>
41#include <wtf/text/CString.h>
42
43namespace WebCore {
44
45const uint32_t backForwardTreeEncodingVersion = 2;
46
47static long long generateSequenceNumber()
48{
49    // Initialize to the current time to reduce the likelihood of generating
50    // identifiers that overlap with those from past/future browser sessions.
51    static long long next = static_cast<long long>(currentTime() * 1000000.0);
52    return ++next;
53}
54
55static void defaultNotifyHistoryItemChanged(HistoryItem*)
56{
57}
58
59void (*notifyHistoryItemChanged)(HistoryItem*) = defaultNotifyHistoryItemChanged;
60
61HistoryItem::HistoryItem()
62    : m_lastVisitedTime(0)
63    , m_lastVisitWasHTTPNonGet(false)
64    , m_pageScaleFactor(0)
65    , m_lastVisitWasFailure(false)
66    , m_isTargetItem(false)
67    , m_visitCount(0)
68    , m_itemSequenceNumber(generateSequenceNumber())
69    , m_documentSequenceNumber(generateSequenceNumber())
70    , m_next(0)
71    , m_prev(0)
72{
73}
74
75HistoryItem::HistoryItem(const String& urlString, const String& title, double time)
76    : m_urlString(urlString)
77    , m_originalURLString(urlString)
78    , m_title(title)
79    , m_lastVisitedTime(time)
80    , m_lastVisitWasHTTPNonGet(false)
81    , m_pageScaleFactor(0)
82    , m_lastVisitWasFailure(false)
83    , m_isTargetItem(false)
84    , m_visitCount(0)
85    , m_itemSequenceNumber(generateSequenceNumber())
86    , m_documentSequenceNumber(generateSequenceNumber())
87    , m_next(0)
88    , m_prev(0)
89{
90    iconDatabase().retainIconForPageURL(m_urlString);
91}
92
93HistoryItem::HistoryItem(const String& urlString, const String& title, const String& alternateTitle, double time)
94    : m_urlString(urlString)
95    , m_originalURLString(urlString)
96    , m_title(title)
97    , m_displayTitle(alternateTitle)
98    , m_lastVisitedTime(time)
99    , m_lastVisitWasHTTPNonGet(false)
100    , m_pageScaleFactor(0)
101    , m_lastVisitWasFailure(false)
102    , m_isTargetItem(false)
103    , m_visitCount(0)
104    , m_itemSequenceNumber(generateSequenceNumber())
105    , m_documentSequenceNumber(generateSequenceNumber())
106    , m_next(0)
107    , m_prev(0)
108{
109    iconDatabase().retainIconForPageURL(m_urlString);
110}
111
112HistoryItem::HistoryItem(const KURL& url, const String& target, const String& parent, const String& title)
113    : m_urlString(url.string())
114    , m_originalURLString(url.string())
115    , m_target(target)
116    , m_parent(parent)
117    , m_title(title)
118    , m_lastVisitedTime(0)
119    , m_lastVisitWasHTTPNonGet(false)
120    , m_pageScaleFactor(0)
121    , m_lastVisitWasFailure(false)
122    , m_isTargetItem(false)
123    , m_visitCount(0)
124    , m_itemSequenceNumber(generateSequenceNumber())
125    , m_documentSequenceNumber(generateSequenceNumber())
126    , m_next(0)
127    , m_prev(0)
128{
129    iconDatabase().retainIconForPageURL(m_urlString);
130}
131
132HistoryItem::~HistoryItem()
133{
134    ASSERT(!m_cachedPage);
135    iconDatabase().releaseIconForPageURL(m_urlString);
136}
137
138inline HistoryItem::HistoryItem(const HistoryItem& item)
139    : RefCounted<HistoryItem>()
140    , m_urlString(item.m_urlString)
141    , m_originalURLString(item.m_originalURLString)
142    , m_referrer(item.m_referrer)
143    , m_target(item.m_target)
144    , m_parent(item.m_parent)
145    , m_title(item.m_title)
146    , m_displayTitle(item.m_displayTitle)
147    , m_lastVisitedTime(item.m_lastVisitedTime)
148    , m_lastVisitWasHTTPNonGet(item.m_lastVisitWasHTTPNonGet)
149    , m_scrollPoint(item.m_scrollPoint)
150    , m_pageScaleFactor(item.m_pageScaleFactor)
151    , m_lastVisitWasFailure(item.m_lastVisitWasFailure)
152    , m_isTargetItem(item.m_isTargetItem)
153    , m_visitCount(item.m_visitCount)
154    , m_dailyVisitCounts(item.m_dailyVisitCounts)
155    , m_weeklyVisitCounts(item.m_weeklyVisitCounts)
156    , m_itemSequenceNumber(item.m_itemSequenceNumber)
157    , m_documentSequenceNumber(item.m_documentSequenceNumber)
158    , m_formContentType(item.m_formContentType)
159{
160    if (item.m_formData)
161        m_formData = item.m_formData->copy();
162
163    unsigned size = item.m_children.size();
164    m_children.reserveInitialCapacity(size);
165    for (unsigned i = 0; i < size; ++i)
166        m_children.uncheckedAppend(item.m_children[i]->copy());
167
168    if (item.m_redirectURLs)
169        m_redirectURLs = adoptPtr(new Vector<String>(*item.m_redirectURLs));
170}
171
172PassRefPtr<HistoryItem> HistoryItem::copy() const
173{
174    return adoptRef(new HistoryItem(*this));
175}
176
177void HistoryItem::reset()
178{
179    iconDatabase().releaseIconForPageURL(m_urlString);
180
181    m_urlString = String();
182    m_originalURLString = String();
183    m_referrer = String();
184    m_target = String();
185    m_parent = String();
186    m_title = String();
187    m_displayTitle = String();
188
189    m_lastVisitedTime = 0;
190    m_lastVisitWasHTTPNonGet = false;
191
192    m_lastVisitWasFailure = false;
193    m_isTargetItem = false;
194    m_visitCount = 0;
195    m_dailyVisitCounts.clear();
196    m_weeklyVisitCounts.clear();
197
198    m_redirectURLs.clear();
199
200    m_itemSequenceNumber = generateSequenceNumber();
201
202    m_stateObject = 0;
203    m_documentSequenceNumber = generateSequenceNumber();
204
205    m_formData = 0;
206    m_formContentType = String();
207
208    clearChildren();
209}
210
211const String& HistoryItem::urlString() const
212{
213    return m_urlString;
214}
215
216// The first URL we loaded to get to where this history item points.  Includes both client
217// and server redirects.
218const String& HistoryItem::originalURLString() const
219{
220    return m_originalURLString;
221}
222
223const String& HistoryItem::title() const
224{
225    return m_title;
226}
227
228const String& HistoryItem::alternateTitle() const
229{
230    return m_displayTitle;
231}
232
233bool HistoryItem::hasCachedPageExpired() const
234{
235    return m_cachedPage ? m_cachedPage->hasExpired() : false;
236}
237
238double HistoryItem::lastVisitedTime() const
239{
240    return m_lastVisitedTime;
241}
242
243KURL HistoryItem::url() const
244{
245    return KURL(ParsedURLString, m_urlString);
246}
247
248KURL HistoryItem::originalURL() const
249{
250    return KURL(ParsedURLString, m_originalURLString);
251}
252
253const String& HistoryItem::referrer() const
254{
255    return m_referrer;
256}
257
258const String& HistoryItem::target() const
259{
260    return m_target;
261}
262
263const String& HistoryItem::parent() const
264{
265    return m_parent;
266}
267
268void HistoryItem::setAlternateTitle(const String& alternateTitle)
269{
270    m_displayTitle = alternateTitle;
271    notifyHistoryItemChanged(this);
272}
273
274void HistoryItem::setURLString(const String& urlString)
275{
276    if (m_urlString != urlString) {
277        iconDatabase().releaseIconForPageURL(m_urlString);
278        m_urlString = urlString;
279        iconDatabase().retainIconForPageURL(m_urlString);
280    }
281
282    notifyHistoryItemChanged(this);
283}
284
285void HistoryItem::setURL(const KURL& url)
286{
287    pageCache()->remove(this);
288    setURLString(url.string());
289    clearDocumentState();
290}
291
292void HistoryItem::setOriginalURLString(const String& urlString)
293{
294    m_originalURLString = urlString;
295    notifyHistoryItemChanged(this);
296}
297
298void HistoryItem::setReferrer(const String& referrer)
299{
300    m_referrer = referrer;
301    notifyHistoryItemChanged(this);
302}
303
304void HistoryItem::setTitle(const String& title)
305{
306    m_title = title;
307    notifyHistoryItemChanged(this);
308}
309
310void HistoryItem::setTarget(const String& target)
311{
312    m_target = target;
313    notifyHistoryItemChanged(this);
314}
315
316void HistoryItem::setParent(const String& parent)
317{
318    m_parent = parent;
319}
320
321static inline int timeToDay(double time)
322{
323    static const double secondsPerDay = 60 * 60 * 24;
324    return static_cast<int>(ceil(time / secondsPerDay));
325}
326
327void HistoryItem::padDailyCountsForNewVisit(double time)
328{
329    if (m_dailyVisitCounts.isEmpty())
330        m_dailyVisitCounts.insert(0, m_visitCount);
331
332    int daysElapsed = timeToDay(time) - timeToDay(m_lastVisitedTime);
333
334    if (daysElapsed < 0)
335      daysElapsed = 0;
336
337    Vector<int> padding;
338    padding.fill(0, daysElapsed);
339    m_dailyVisitCounts.insert(0, padding);
340}
341
342static const size_t daysPerWeek = 7;
343static const size_t maxDailyCounts = 2 * daysPerWeek - 1;
344static const size_t maxWeeklyCounts = 5;
345
346void HistoryItem::collapseDailyVisitsToWeekly()
347{
348    while (m_dailyVisitCounts.size() > maxDailyCounts) {
349        int oldestWeekTotal = 0;
350        for (size_t i = 0; i < daysPerWeek; i++)
351            oldestWeekTotal += m_dailyVisitCounts[m_dailyVisitCounts.size() - daysPerWeek + i];
352        m_dailyVisitCounts.shrink(m_dailyVisitCounts.size() - daysPerWeek);
353        m_weeklyVisitCounts.insert(0, oldestWeekTotal);
354    }
355
356    if (m_weeklyVisitCounts.size() > maxWeeklyCounts)
357        m_weeklyVisitCounts.shrink(maxWeeklyCounts);
358}
359
360void HistoryItem::recordVisitAtTime(double time, VisitCountBehavior visitCountBehavior)
361{
362    padDailyCountsForNewVisit(time);
363
364    m_lastVisitedTime = time;
365
366    if (visitCountBehavior == IncreaseVisitCount) {
367        ++m_visitCount;
368        ++m_dailyVisitCounts[0];
369    }
370
371    collapseDailyVisitsToWeekly();
372}
373
374void HistoryItem::setLastVisitedTime(double time)
375{
376    if (m_lastVisitedTime != time)
377        recordVisitAtTime(time);
378}
379
380void HistoryItem::visited(const String& title, double time, VisitCountBehavior visitCountBehavior)
381{
382    m_title = title;
383    recordVisitAtTime(time, visitCountBehavior);
384}
385
386int HistoryItem::visitCount() const
387{
388    return m_visitCount;
389}
390
391void HistoryItem::recordInitialVisit()
392{
393    ASSERT(!m_visitCount);
394    recordVisitAtTime(m_lastVisitedTime);
395}
396
397void HistoryItem::setVisitCount(int count)
398{
399    m_visitCount = count;
400}
401
402void HistoryItem::adoptVisitCounts(Vector<int>& dailyCounts, Vector<int>& weeklyCounts)
403{
404    m_dailyVisitCounts.clear();
405    m_dailyVisitCounts.swap(dailyCounts);
406    m_weeklyVisitCounts.clear();
407    m_weeklyVisitCounts.swap(weeklyCounts);
408}
409
410const IntPoint& HistoryItem::scrollPoint() const
411{
412    return m_scrollPoint;
413}
414
415void HistoryItem::setScrollPoint(const IntPoint& point)
416{
417    m_scrollPoint = point;
418}
419
420void HistoryItem::clearScrollPoint()
421{
422    m_scrollPoint.setX(0);
423    m_scrollPoint.setY(0);
424}
425
426float HistoryItem::pageScaleFactor() const
427{
428    return m_pageScaleFactor;
429}
430
431void HistoryItem::setPageScaleFactor(float scaleFactor)
432{
433    m_pageScaleFactor = scaleFactor;
434}
435
436void HistoryItem::setDocumentState(const Vector<String>& state)
437{
438    m_documentState = state;
439}
440
441const Vector<String>& HistoryItem::documentState() const
442{
443    return m_documentState;
444}
445
446void HistoryItem::clearDocumentState()
447{
448    m_documentState.clear();
449}
450
451bool HistoryItem::isTargetItem() const
452{
453    return m_isTargetItem;
454}
455
456void HistoryItem::setIsTargetItem(bool flag)
457{
458    m_isTargetItem = flag;
459}
460
461void HistoryItem::setStateObject(PassRefPtr<SerializedScriptValue> object)
462{
463    m_stateObject = object;
464}
465
466void HistoryItem::addChildItem(PassRefPtr<HistoryItem> child)
467{
468    ASSERT(!childItemWithTarget(child->target()));
469    m_children.append(child);
470}
471
472void HistoryItem::setChildItem(PassRefPtr<HistoryItem> child)
473{
474    ASSERT(!child->isTargetItem());
475    unsigned size = m_children.size();
476    for (unsigned i = 0; i < size; ++i)  {
477        if (m_children[i]->target() == child->target()) {
478            child->setIsTargetItem(m_children[i]->isTargetItem());
479            m_children[i] = child;
480            return;
481        }
482    }
483    m_children.append(child);
484}
485
486HistoryItem* HistoryItem::childItemWithTarget(const String& target) const
487{
488    unsigned size = m_children.size();
489    for (unsigned i = 0; i < size; ++i) {
490        if (m_children[i]->target() == target)
491            return m_children[i].get();
492    }
493    return 0;
494}
495
496HistoryItem* HistoryItem::childItemWithDocumentSequenceNumber(long long number) const
497{
498    unsigned size = m_children.size();
499    for (unsigned i = 0; i < size; ++i) {
500        if (m_children[i]->documentSequenceNumber() == number)
501            return m_children[i].get();
502    }
503    return 0;
504}
505
506// <rdar://problem/4895849> HistoryItem::findTargetItem() should be replaced with a non-recursive method.
507HistoryItem* HistoryItem::findTargetItem()
508{
509    if (m_isTargetItem)
510        return this;
511    unsigned size = m_children.size();
512    for (unsigned i = 0; i < size; ++i) {
513        if (HistoryItem* match = m_children[i]->targetItem())
514            return match;
515    }
516    return 0;
517}
518
519HistoryItem* HistoryItem::targetItem()
520{
521    HistoryItem* foundItem = findTargetItem();
522    return foundItem ? foundItem : this;
523}
524
525const HistoryItemVector& HistoryItem::children() const
526{
527    return m_children;
528}
529
530bool HistoryItem::hasChildren() const
531{
532    return !m_children.isEmpty();
533}
534
535void HistoryItem::clearChildren()
536{
537    m_children.clear();
538}
539
540bool HistoryItem::isAncestorOf(const HistoryItem* item) const
541{
542    for (size_t i = 0; i < m_children.size(); ++i) {
543        HistoryItem* child = m_children[i].get();
544        if (child == item)
545            return true;
546        if (child->isAncestorOf(item))
547            return true;
548    }
549    return false;
550}
551
552// We do same-document navigation if going to a different item and if either of the following is true:
553// - The other item corresponds to the same document (for history entries created via pushState or fragment changes).
554// - The other item corresponds to the same set of documents, including frames (for history entries created via regular navigation)
555bool HistoryItem::shouldDoSameDocumentNavigationTo(HistoryItem* otherItem) const
556{
557    if (this == otherItem)
558        return false;
559
560    if (stateObject() || otherItem->stateObject())
561        return documentSequenceNumber() == otherItem->documentSequenceNumber();
562
563    if ((url().hasFragmentIdentifier() || otherItem->url().hasFragmentIdentifier()) && equalIgnoringFragmentIdentifier(url(), otherItem->url()))
564        return documentSequenceNumber() == otherItem->documentSequenceNumber();
565
566    return hasSameDocumentTree(otherItem);
567}
568
569// Does a recursive check that this item and its descendants have the same
570// document sequence numbers as the other item.
571bool HistoryItem::hasSameDocumentTree(HistoryItem* otherItem) const
572{
573    if (documentSequenceNumber() != otherItem->documentSequenceNumber())
574        return false;
575
576    if (children().size() != otherItem->children().size())
577        return false;
578
579    for (size_t i = 0; i < children().size(); i++) {
580        HistoryItem* child = children()[i].get();
581        HistoryItem* otherChild = otherItem->childItemWithDocumentSequenceNumber(child->documentSequenceNumber());
582        if (!otherChild || !child->hasSameDocumentTree(otherChild))
583            return false;
584    }
585
586    return true;
587}
588
589// Does a non-recursive check that this item and its immediate children have the
590// same frames as the other item.
591bool HistoryItem::hasSameFrames(HistoryItem* otherItem) const
592{
593    if (target() != otherItem->target())
594        return false;
595
596    if (children().size() != otherItem->children().size())
597        return false;
598
599    for (size_t i = 0; i < children().size(); i++) {
600        if (!otherItem->childItemWithTarget(children()[i]->target()))
601            return false;
602    }
603
604    return true;
605}
606
607String HistoryItem::formContentType() const
608{
609    return m_formContentType;
610}
611
612void HistoryItem::setFormInfoFromRequest(const ResourceRequest& request)
613{
614    m_referrer = request.httpReferrer();
615
616    if (equalIgnoringCase(request.httpMethod(), "POST")) {
617        // FIXME: Eventually we have to make this smart enough to handle the case where
618        // we have a stream for the body to handle the "data interspersed with files" feature.
619        m_formData = request.httpBody();
620        m_formContentType = request.httpContentType();
621    } else {
622        m_formData = 0;
623        m_formContentType = String();
624    }
625}
626
627void HistoryItem::setFormData(PassRefPtr<FormData> formData)
628{
629    m_formData = formData;
630}
631
632void HistoryItem::setFormContentType(const String& formContentType)
633{
634    m_formContentType = formContentType;
635}
636
637FormData* HistoryItem::formData()
638{
639    return m_formData.get();
640}
641
642bool HistoryItem::isCurrentDocument(Document* doc) const
643{
644    // FIXME: We should find a better way to check if this is the current document.
645    return equalIgnoringFragmentIdentifier(url(), doc->url());
646}
647
648void HistoryItem::mergeAutoCompleteHints(HistoryItem* otherItem)
649{
650    // FIXME: this is broken - we should be merging the daily counts
651    // somehow.  but this is to support API that's not really used in
652    // practice so leave it broken for now.
653    ASSERT(otherItem);
654    if (otherItem != this)
655        m_visitCount += otherItem->m_visitCount;
656}
657
658void HistoryItem::addRedirectURL(const String& url)
659{
660    if (!m_redirectURLs)
661        m_redirectURLs = adoptPtr(new Vector<String>);
662
663    // Our API allows us to store all the URLs in the redirect chain, but for
664    // now we only have a use for the final URL.
665    (*m_redirectURLs).resize(1);
666    (*m_redirectURLs)[0] = url;
667}
668
669Vector<String>* HistoryItem::redirectURLs() const
670{
671    return m_redirectURLs.get();
672}
673
674void HistoryItem::setRedirectURLs(PassOwnPtr<Vector<String> > redirectURLs)
675{
676    m_redirectURLs = redirectURLs;
677}
678
679void HistoryItem::encodeBackForwardTree(Encoder& encoder) const
680{
681    encoder.encodeUInt32(backForwardTreeEncodingVersion);
682
683    encodeBackForwardTreeNode(encoder);
684}
685
686void HistoryItem::encodeBackForwardTreeNode(Encoder& encoder) const
687{
688    size_t size = m_children.size();
689    encoder.encodeUInt64(size);
690    for (size_t i = 0; i < size; ++i) {
691        const HistoryItem& child = *m_children[i];
692
693        encoder.encodeString(child.m_originalURLString);
694
695        encoder.encodeString(child.m_urlString);
696
697        child.encodeBackForwardTreeNode(encoder);
698    }
699
700    encoder.encodeInt64(m_documentSequenceNumber);
701
702    size = m_documentState.size();
703    encoder.encodeUInt64(size);
704    for (size_t i = 0; i < size; ++i)
705        encoder.encodeString(m_documentState[i]);
706
707    encoder.encodeString(m_formContentType);
708
709    encoder.encodeBool(m_formData);
710    if (m_formData)
711        m_formData->encode(encoder);
712
713    encoder.encodeInt64(m_itemSequenceNumber);
714
715    encoder.encodeString(m_referrer);
716
717    encoder.encodeInt32(m_scrollPoint.x());
718    encoder.encodeInt32(m_scrollPoint.y());
719
720    encoder.encodeFloat(m_pageScaleFactor);
721
722    encoder.encodeBool(m_stateObject);
723    if (m_stateObject)
724        encoder.encodeBytes(m_stateObject->data().data(), m_stateObject->data().size());
725
726    encoder.encodeString(m_target);
727}
728
729struct DecodeRecursionStackElement {
730    RefPtr<HistoryItem> node;
731    size_t i;
732    uint64_t size;
733
734    DecodeRecursionStackElement(PassRefPtr<HistoryItem> node, size_t i, uint64_t size)
735        : node(node)
736        , i(i)
737        , size(size)
738    {
739    }
740};
741
742PassRefPtr<HistoryItem> HistoryItem::decodeBackForwardTree(const String& topURLString, const String& topTitle, const String& topOriginalURLString, Decoder& decoder)
743{
744    // Since the data stream is not trusted, the decode has to be non-recursive.
745    // We don't want bad data to cause a stack overflow.
746
747    uint32_t version;
748    if (!decoder.decodeUInt32(version))
749        return 0;
750    if (version != backForwardTreeEncodingVersion)
751        return 0;
752
753    String urlString = topURLString;
754    String title = topTitle;
755    String originalURLString = topOriginalURLString;
756
757    Vector<DecodeRecursionStackElement, 16> recursionStack;
758
759recurse:
760    RefPtr<HistoryItem> node = create(urlString, title, 0);
761
762    node->setOriginalURLString(originalURLString);
763
764    title = String();
765
766    uint64_t size;
767    if (!decoder.decodeUInt64(size))
768        return 0;
769    size_t i;
770    RefPtr<HistoryItem> child;
771    for (i = 0; i < size; ++i) {
772        if (!decoder.decodeString(originalURLString))
773            return 0;
774
775        if (!decoder.decodeString(urlString))
776            return 0;
777
778        recursionStack.append(DecodeRecursionStackElement(node.release(), i, size));
779        goto recurse;
780
781resume:
782        node->m_children.append(child.release());
783    }
784
785    if (!decoder.decodeInt64(node->m_documentSequenceNumber))
786        return 0;
787
788    if (!decoder.decodeUInt64(size))
789        return 0;
790    for (i = 0; i < size; ++i) {
791        String state;
792        if (!decoder.decodeString(state))
793            return 0;
794        node->m_documentState.append(state);
795    }
796
797    if (!decoder.decodeString(node->m_formContentType))
798        return 0;
799
800    bool hasFormData;
801    if (!decoder.decodeBool(hasFormData))
802        return 0;
803    if (hasFormData) {
804        node->m_formData = FormData::decode(decoder);
805        if (!node->m_formData)
806            return 0;
807    }
808
809    if (!decoder.decodeInt64(node->m_itemSequenceNumber))
810        return 0;
811
812    if (!decoder.decodeString(node->m_referrer))
813        return 0;
814
815    int32_t x;
816    if (!decoder.decodeInt32(x))
817        return 0;
818    int32_t y;
819    if (!decoder.decodeInt32(y))
820        return 0;
821    node->m_scrollPoint = IntPoint(x, y);
822
823    if (!decoder.decodeFloat(node->m_pageScaleFactor))
824        return 0;
825
826    bool hasStateObject;
827    if (!decoder.decodeBool(hasStateObject))
828        return 0;
829    if (hasStateObject) {
830        Vector<uint8_t> bytes;
831        if (!decoder.decodeBytes(bytes))
832            return 0;
833        node->m_stateObject = SerializedScriptValue::adopt(bytes);
834    }
835
836    if (!decoder.decodeString(node->m_target))
837        return 0;
838
839    // Simulate recursion with our own stack.
840    if (!recursionStack.isEmpty()) {
841        DecodeRecursionStackElement& element = recursionStack.last();
842        child = node.release();
843        node = element.node.release();
844        i = element.i;
845        size = element.size;
846        recursionStack.removeLast();
847        goto resume;
848    }
849
850    return node.release();
851}
852
853#ifndef NDEBUG
854
855int HistoryItem::showTree() const
856{
857    return showTreeWithIndent(0);
858}
859
860int HistoryItem::showTreeWithIndent(unsigned indentLevel) const
861{
862    Vector<char> prefix;
863    for (unsigned i = 0; i < indentLevel; ++i)
864        prefix.append("  ", 2);
865    prefix.append("\0", 1);
866
867    fprintf(stderr, "%s+-%s (%p)\n", prefix.data(), m_urlString.utf8().data(), this);
868
869    int totalSubItems = 0;
870    for (unsigned i = 0; i < m_children.size(); ++i)
871        totalSubItems += m_children[i]->showTreeWithIndent(indentLevel + 1);
872    return totalSubItems + 1;
873}
874
875#endif
876
877} // namespace WebCore
878
879#ifndef NDEBUG
880
881int showTree(const WebCore::HistoryItem* item)
882{
883    return item->showTree();
884}
885
886#endif
887