2 * Copyright (C) 2005, 2006, 2008, 2011 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "HistoryItem.h"
29 #include "CachedPage.h"
31 #include "IconDatabase.h"
32 #include "PageCache.h"
33 #include "ResourceRequest.h"
35 #include <wtf/CurrentTime.h>
36 #include <wtf/Decoder.h>
37 #include <wtf/Encoder.h>
38 #include <wtf/MathExtras.h>
39 #include <wtf/text/CString.h>
43 const uint32_t backForwardTreeEncodingVersion = 2;
45 static long long generateSequenceNumber()
47 // Initialize to the current time to reduce the likelihood of generating
48 // identifiers that overlap with those from past/future browser sessions.
49 static long long next = static_cast<long long>(currentTime() * 1000000.0);
53 static void defaultNotifyHistoryItemChanged(HistoryItem*)
57 void (*notifyHistoryItemChanged)(HistoryItem*) = defaultNotifyHistoryItemChanged;
59 HistoryItem::HistoryItem()
60 : m_lastVisitedTime(0)
61 , m_lastVisitWasHTTPNonGet(false)
62 , m_pageScaleFactor(1)
63 , m_lastVisitWasFailure(false)
64 , m_isTargetItem(false)
66 , m_itemSequenceNumber(generateSequenceNumber())
67 , m_documentSequenceNumber(generateSequenceNumber())
73 HistoryItem::HistoryItem(const String& urlString, const String& title, double time)
74 : m_urlString(urlString)
75 , m_originalURLString(urlString)
77 , m_lastVisitedTime(time)
78 , m_lastVisitWasHTTPNonGet(false)
79 , m_pageScaleFactor(1)
80 , m_lastVisitWasFailure(false)
81 , m_isTargetItem(false)
83 , m_itemSequenceNumber(generateSequenceNumber())
84 , m_documentSequenceNumber(generateSequenceNumber())
88 iconDatabase()->retainIconForPageURL(m_urlString);
91 HistoryItem::HistoryItem(const String& urlString, const String& title, const String& alternateTitle, double time)
92 : m_urlString(urlString)
93 , m_originalURLString(urlString)
95 , m_displayTitle(alternateTitle)
96 , m_lastVisitedTime(time)
97 , m_lastVisitWasHTTPNonGet(false)
98 , m_pageScaleFactor(1)
99 , m_lastVisitWasFailure(false)
100 , m_isTargetItem(false)
102 , m_itemSequenceNumber(generateSequenceNumber())
103 , m_documentSequenceNumber(generateSequenceNumber())
107 iconDatabase()->retainIconForPageURL(m_urlString);
110 HistoryItem::HistoryItem(const KURL& url, const String& target, const String& parent, const String& title)
111 : m_urlString(url.string())
112 , m_originalURLString(url.string())
116 , m_lastVisitedTime(0)
117 , m_lastVisitWasHTTPNonGet(false)
118 , m_pageScaleFactor(1)
119 , m_lastVisitWasFailure(false)
120 , m_isTargetItem(false)
122 , m_itemSequenceNumber(generateSequenceNumber())
123 , m_documentSequenceNumber(generateSequenceNumber())
127 iconDatabase()->retainIconForPageURL(m_urlString);
130 HistoryItem::~HistoryItem()
132 ASSERT(!m_cachedPage);
133 iconDatabase()->releaseIconForPageURL(m_urlString);
134 #if PLATFORM(ANDROID)
136 m_bridge->detachHistoryItem();
140 inline HistoryItem::HistoryItem(const HistoryItem& item)
141 : RefCounted<HistoryItem>()
142 , m_urlString(item.m_urlString)
143 , m_originalURLString(item.m_originalURLString)
144 , m_referrer(item.m_referrer)
145 , m_target(item.m_target)
146 , m_parent(item.m_parent)
147 , m_title(item.m_title)
148 , m_displayTitle(item.m_displayTitle)
149 , m_lastVisitedTime(item.m_lastVisitedTime)
150 , m_lastVisitWasHTTPNonGet(item.m_lastVisitWasHTTPNonGet)
151 , m_scrollPoint(item.m_scrollPoint)
152 , m_pageScaleFactor(item.m_pageScaleFactor)
153 , m_lastVisitWasFailure(item.m_lastVisitWasFailure)
154 , m_isTargetItem(item.m_isTargetItem)
155 , m_visitCount(item.m_visitCount)
156 , m_dailyVisitCounts(item.m_dailyVisitCounts)
157 , m_weeklyVisitCounts(item.m_weeklyVisitCounts)
158 , m_itemSequenceNumber(item.m_itemSequenceNumber)
159 , m_documentSequenceNumber(item.m_documentSequenceNumber)
160 , m_formContentType(item.m_formContentType)
163 m_formData = item.m_formData->copy();
165 unsigned size = item.m_children.size();
166 m_children.reserveInitialCapacity(size);
167 for (unsigned i = 0; i < size; ++i)
168 m_children.uncheckedAppend(item.m_children[i]->copy());
170 if (item.m_redirectURLs)
171 m_redirectURLs = adoptPtr(new Vector<String>(*item.m_redirectURLs));
174 PassRefPtr<HistoryItem> HistoryItem::copy() const
176 return adoptRef(new HistoryItem(*this));
179 void HistoryItem::reset()
181 iconDatabase()->releaseIconForPageURL(m_urlString);
183 m_urlString = String();
184 m_originalURLString = String();
185 m_referrer = String();
189 m_displayTitle = String();
191 m_lastVisitedTime = 0;
192 m_lastVisitWasHTTPNonGet = false;
194 m_lastVisitWasFailure = false;
195 m_isTargetItem = false;
197 m_dailyVisitCounts.clear();
198 m_weeklyVisitCounts.clear();
200 m_redirectURLs.clear();
202 m_itemSequenceNumber = generateSequenceNumber();
205 m_documentSequenceNumber = generateSequenceNumber();
208 m_formContentType = String();
211 const String& HistoryItem::urlString() const
216 // The first URL we loaded to get to where this history item points. Includes both client
217 // and server redirects.
218 const String& HistoryItem::originalURLString() const
220 return m_originalURLString;
223 const String& HistoryItem::title() const
228 const String& HistoryItem::alternateTitle() const
230 return m_displayTitle;
233 Image* HistoryItem::icon() const
235 Image* result = iconDatabase()->iconForPageURL(m_urlString, IntSize(16, 16));
236 return result ? result : iconDatabase()->defaultIcon(IntSize(16, 16));
239 double HistoryItem::lastVisitedTime() const
241 return m_lastVisitedTime;
244 KURL HistoryItem::url() const
246 return KURL(ParsedURLString, m_urlString);
249 KURL HistoryItem::originalURL() const
251 return KURL(ParsedURLString, m_originalURLString);
254 const String& HistoryItem::referrer() const
259 const String& HistoryItem::target() const
264 const String& HistoryItem::parent() const
269 void HistoryItem::setAlternateTitle(const String& alternateTitle)
271 m_displayTitle = alternateTitle;
272 notifyHistoryItemChanged(this);
275 void HistoryItem::setURLString(const String& urlString)
277 if (m_urlString != urlString) {
278 iconDatabase()->releaseIconForPageURL(m_urlString);
279 m_urlString = urlString;
280 iconDatabase()->retainIconForPageURL(m_urlString);
283 notifyHistoryItemChanged(this);
286 void HistoryItem::setURL(const KURL& url)
288 pageCache()->remove(this);
289 setURLString(url.string());
290 clearDocumentState();
293 void HistoryItem::setOriginalURLString(const String& urlString)
295 m_originalURLString = urlString;
296 notifyHistoryItemChanged(this);
299 void HistoryItem::setReferrer(const String& referrer)
301 m_referrer = referrer;
302 notifyHistoryItemChanged(this);
305 void HistoryItem::setTitle(const String& title)
308 notifyHistoryItemChanged(this);
311 void HistoryItem::setTarget(const String& target)
314 notifyHistoryItemChanged(this);
317 void HistoryItem::setParent(const String& parent)
322 static inline int timeToDay(double time)
324 static const double secondsPerDay = 60 * 60 * 24;
325 return static_cast<int>(ceil(time / secondsPerDay));
328 void HistoryItem::padDailyCountsForNewVisit(double time)
330 if (m_dailyVisitCounts.isEmpty())
331 m_dailyVisitCounts.prepend(m_visitCount);
333 int daysElapsed = timeToDay(time) - timeToDay(m_lastVisitedTime);
339 padding.fill(0, daysElapsed);
340 m_dailyVisitCounts.prepend(padding);
343 static const size_t daysPerWeek = 7;
344 static const size_t maxDailyCounts = 2 * daysPerWeek - 1;
345 static const size_t maxWeeklyCounts = 5;
347 void HistoryItem::collapseDailyVisitsToWeekly()
349 while (m_dailyVisitCounts.size() > maxDailyCounts) {
350 int oldestWeekTotal = 0;
351 for (size_t i = 0; i < daysPerWeek; i++)
352 oldestWeekTotal += m_dailyVisitCounts[m_dailyVisitCounts.size() - daysPerWeek + i];
353 m_dailyVisitCounts.shrink(m_dailyVisitCounts.size() - daysPerWeek);
354 m_weeklyVisitCounts.prepend(oldestWeekTotal);
357 if (m_weeklyVisitCounts.size() > maxWeeklyCounts)
358 m_weeklyVisitCounts.shrink(maxWeeklyCounts);
361 void HistoryItem::recordVisitAtTime(double time, VisitCountBehavior visitCountBehavior)
363 padDailyCountsForNewVisit(time);
365 m_lastVisitedTime = time;
367 if (visitCountBehavior == IncreaseVisitCount) {
369 ++m_dailyVisitCounts[0];
372 collapseDailyVisitsToWeekly();
375 void HistoryItem::setLastVisitedTime(double time)
377 if (m_lastVisitedTime != time)
378 recordVisitAtTime(time);
381 void HistoryItem::visited(const String& title, double time, VisitCountBehavior visitCountBehavior)
384 recordVisitAtTime(time, visitCountBehavior);
387 int HistoryItem::visitCount() const
392 void HistoryItem::recordInitialVisit()
394 ASSERT(!m_visitCount);
395 recordVisitAtTime(m_lastVisitedTime);
398 void HistoryItem::setVisitCount(int count)
400 m_visitCount = count;
403 void HistoryItem::adoptVisitCounts(Vector<int>& dailyCounts, Vector<int>& weeklyCounts)
405 m_dailyVisitCounts.clear();
406 m_dailyVisitCounts.swap(dailyCounts);
407 m_weeklyVisitCounts.clear();
408 m_weeklyVisitCounts.swap(weeklyCounts);
411 const IntPoint& HistoryItem::scrollPoint() const
413 return m_scrollPoint;
416 void HistoryItem::setScrollPoint(const IntPoint& point)
418 m_scrollPoint = point;
421 void HistoryItem::clearScrollPoint()
423 m_scrollPoint.setX(0);
424 m_scrollPoint.setY(0);
427 float HistoryItem::pageScaleFactor() const
429 return m_pageScaleFactor;
432 void HistoryItem::setPageScaleFactor(float scaleFactor)
434 m_pageScaleFactor = scaleFactor;
437 void HistoryItem::setDocumentState(const Vector<String>& state)
439 m_documentState = state;
440 #if PLATFORM(ANDROID)
441 notifyHistoryItemChanged(this);
445 const Vector<String>& HistoryItem::documentState() const
447 return m_documentState;
450 void HistoryItem::clearDocumentState()
452 m_documentState.clear();
453 #if PLATFORM(ANDROID)
454 notifyHistoryItemChanged(this);
458 bool HistoryItem::isTargetItem() const
460 return m_isTargetItem;
463 void HistoryItem::setIsTargetItem(bool flag)
465 m_isTargetItem = flag;
466 #if PLATFORM(ANDROID)
467 notifyHistoryItemChanged(this);
471 void HistoryItem::setStateObject(PassRefPtr<SerializedScriptValue> object)
473 m_stateObject = object;
476 void HistoryItem::addChildItem(PassRefPtr<HistoryItem> child)
478 ASSERT(!childItemWithTarget(child->target()));
479 m_children.append(child);
480 #if PLATFORM(ANDROID)
481 notifyHistoryItemChanged(this);
485 void HistoryItem::setChildItem(PassRefPtr<HistoryItem> child)
487 ASSERT(!child->isTargetItem());
488 unsigned size = m_children.size();
489 for (unsigned i = 0; i < size; ++i) {
490 if (m_children[i]->target() == child->target()) {
491 child->setIsTargetItem(m_children[i]->isTargetItem());
492 m_children[i] = child;
496 m_children.append(child);
499 HistoryItem* HistoryItem::childItemWithTarget(const String& target) const
501 unsigned size = m_children.size();
502 for (unsigned i = 0; i < size; ++i) {
503 if (m_children[i]->target() == target)
504 return m_children[i].get();
509 HistoryItem* HistoryItem::childItemWithDocumentSequenceNumber(long long number) const
511 unsigned size = m_children.size();
512 for (unsigned i = 0; i < size; ++i) {
513 if (m_children[i]->documentSequenceNumber() == number)
514 return m_children[i].get();
519 // <rdar://problem/4895849> HistoryItem::findTargetItem() should be replaced with a non-recursive method.
520 HistoryItem* HistoryItem::findTargetItem()
524 unsigned size = m_children.size();
525 for (unsigned i = 0; i < size; ++i) {
526 if (HistoryItem* match = m_children[i]->targetItem())
532 HistoryItem* HistoryItem::targetItem()
534 HistoryItem* foundItem = findTargetItem();
535 return foundItem ? foundItem : this;
538 const HistoryItemVector& HistoryItem::children() const
543 bool HistoryItem::hasChildren() const
545 return !m_children.isEmpty();
548 void HistoryItem::clearChildren()
553 // We do same-document navigation if going to a different item and if either of the following is true:
554 // - The other item corresponds to the same document (for history entries created via pushState or fragment changes).
555 // - The other item corresponds to the same set of documents, including frames (for history entries created via regular navigation)
556 bool HistoryItem::shouldDoSameDocumentNavigationTo(HistoryItem* otherItem) const
558 if (this == otherItem)
561 if (stateObject() || otherItem->stateObject())
562 return documentSequenceNumber() == otherItem->documentSequenceNumber();
564 if ((url().hasFragmentIdentifier() || otherItem->url().hasFragmentIdentifier()) && equalIgnoringFragmentIdentifier(url(), otherItem->url()))
565 return documentSequenceNumber() == otherItem->documentSequenceNumber();
567 return hasSameDocumentTree(otherItem);
570 // Does a recursive check that this item and its descendants have the same
571 // document sequence numbers as the other item.
572 bool HistoryItem::hasSameDocumentTree(HistoryItem* otherItem) const
574 if (documentSequenceNumber() != otherItem->documentSequenceNumber())
577 if (children().size() != otherItem->children().size())
580 for (size_t i = 0; i < children().size(); i++) {
581 HistoryItem* child = children()[i].get();
582 HistoryItem* otherChild = otherItem->childItemWithDocumentSequenceNumber(child->documentSequenceNumber());
583 if (!otherChild || !child->hasSameDocumentTree(otherChild))
590 // Does a non-recursive check that this item and its immediate children have the
591 // same frames as the other item.
592 bool HistoryItem::hasSameFrames(HistoryItem* otherItem) const
594 if (target() != otherItem->target())
597 if (children().size() != otherItem->children().size())
600 for (size_t i = 0; i < children().size(); i++) {
601 if (!otherItem->childItemWithTarget(children()[i]->target()))
608 String HistoryItem::formContentType() const
610 return m_formContentType;
613 void HistoryItem::setFormInfoFromRequest(const ResourceRequest& request)
615 m_referrer = request.httpReferrer();
617 if (equalIgnoringCase(request.httpMethod(), "POST")) {
618 // FIXME: Eventually we have to make this smart enough to handle the case where
619 // we have a stream for the body to handle the "data interspersed with files" feature.
620 m_formData = request.httpBody();
621 m_formContentType = request.httpContentType();
624 m_formContentType = String();
626 #if PLATFORM(ANDROID)
627 notifyHistoryItemChanged(this);
631 void HistoryItem::setFormData(PassRefPtr<FormData> formData)
633 m_formData = formData;
636 void HistoryItem::setFormContentType(const String& formContentType)
638 m_formContentType = formContentType;
641 FormData* HistoryItem::formData()
643 return m_formData.get();
646 bool HistoryItem::isCurrentDocument(Document* doc) const
648 // FIXME: We should find a better way to check if this is the current document.
649 return equalIgnoringFragmentIdentifier(url(), doc->url());
652 void HistoryItem::mergeAutoCompleteHints(HistoryItem* otherItem)
654 // FIXME: this is broken - we should be merging the daily counts
655 // somehow. but this is to support API that's not really used in
656 // practice so leave it broken for now.
658 if (otherItem != this)
659 m_visitCount += otherItem->m_visitCount;
662 void HistoryItem::addRedirectURL(const String& url)
665 m_redirectURLs = adoptPtr(new Vector<String>);
667 // Our API allows us to store all the URLs in the redirect chain, but for
668 // now we only have a use for the final URL.
669 (*m_redirectURLs).resize(1);
670 (*m_redirectURLs)[0] = url;
673 Vector<String>* HistoryItem::redirectURLs() const
675 return m_redirectURLs.get();
678 void HistoryItem::setRedirectURLs(PassOwnPtr<Vector<String> > redirectURLs)
680 m_redirectURLs = redirectURLs;
683 void HistoryItem::encodeBackForwardTree(Encoder& encoder) const
685 encoder.encodeUInt32(backForwardTreeEncodingVersion);
687 encodeBackForwardTreeNode(encoder);
690 void HistoryItem::encodeBackForwardTreeNode(Encoder& encoder) const
692 size_t size = m_children.size();
693 encoder.encodeUInt64(size);
694 for (size_t i = 0; i < size; ++i) {
695 const HistoryItem& child = *m_children[i];
697 encoder.encodeString(child.m_originalURLString);
699 encoder.encodeString(child.m_urlString);
701 child.encodeBackForwardTreeNode(encoder);
704 encoder.encodeInt64(m_documentSequenceNumber);
706 size = m_documentState.size();
707 encoder.encodeUInt64(size);
708 for (size_t i = 0; i < size; ++i)
709 encoder.encodeString(m_documentState[i]);
711 encoder.encodeString(m_formContentType);
713 encoder.encodeBool(m_formData);
715 m_formData->encodeForBackForward(encoder);
717 encoder.encodeInt64(m_itemSequenceNumber);
719 encoder.encodeString(m_referrer);
721 encoder.encodeInt32(m_scrollPoint.x());
722 encoder.encodeInt32(m_scrollPoint.y());
724 encoder.encodeFloat(m_pageScaleFactor);
726 encoder.encodeBool(m_stateObject);
729 encoder.encodeBytes(m_stateObject->data().data(), m_stateObject->data().size());
731 encoder.encodeString(m_stateObject->toWireString());
735 encoder.encodeString(m_target);
738 struct DecodeRecursionStackElement {
739 RefPtr<HistoryItem> node;
743 DecodeRecursionStackElement(PassRefPtr<HistoryItem> node, size_t i, uint64_t size)
751 PassRefPtr<HistoryItem> HistoryItem::decodeBackForwardTree(const String& topURLString, const String& topTitle, const String& topOriginalURLString, Decoder& decoder)
753 // Since the data stream is not trusted, the decode has to be non-recursive.
754 // We don't want bad data to cause a stack overflow.
757 if (!decoder.decodeUInt32(version))
759 if (version != backForwardTreeEncodingVersion)
762 String urlString = topURLString;
763 String title = topTitle;
764 String originalURLString = topOriginalURLString;
766 Vector<DecodeRecursionStackElement, 16> recursionStack;
769 RefPtr<HistoryItem> node = create(urlString, title, 0);
771 node->setOriginalURLString(originalURLString);
776 if (!decoder.decodeUInt64(size))
779 RefPtr<HistoryItem> child;
780 for (i = 0; i < size; ++i) {
781 if (!decoder.decodeString(originalURLString))
784 if (!decoder.decodeString(urlString))
787 recursionStack.append(DecodeRecursionStackElement(node.release(), i, size));
791 node->m_children.append(child.release());
794 if (!decoder.decodeInt64(node->m_documentSequenceNumber))
797 if (!decoder.decodeUInt64(size))
799 for (i = 0; i < size; ++i) {
801 if (!decoder.decodeString(state))
803 node->m_documentState.append(state);
806 if (!decoder.decodeString(node->m_formContentType))
810 if (!decoder.decodeBool(hasFormData))
813 node->m_formData = FormData::decodeForBackForward(decoder);
814 if (!node->m_formData)
818 if (!decoder.decodeInt64(node->m_itemSequenceNumber))
821 if (!decoder.decodeString(node->m_referrer))
825 if (!decoder.decodeInt32(x))
828 if (!decoder.decodeInt32(y))
830 node->m_scrollPoint = IntPoint(x, y);
832 if (!decoder.decodeFloat(node->m_pageScaleFactor))
836 if (!decoder.decodeBool(hasStateObject))
838 if (hasStateObject) {
840 Vector<uint8_t> bytes;
841 if (!decoder.decodeBytes(bytes))
843 node->m_stateObject = SerializedScriptValue::adopt(bytes);
846 if (!decoder.decodeString(string))
848 node->m_stateObject = SerializedScriptValue::createFromWire(string);
852 if (!decoder.decodeString(node->m_target))
855 // Simulate recursion with our own stack.
856 if (!recursionStack.isEmpty()) {
857 DecodeRecursionStackElement& element = recursionStack.last();
858 child = node.release();
859 node = element.node.release();
862 recursionStack.removeLast();
866 return node.release();
871 int HistoryItem::showTree() const
873 return showTreeWithIndent(0);
876 int HistoryItem::showTreeWithIndent(unsigned indentLevel) const
879 for (unsigned i = 0; i < indentLevel; ++i)
880 prefix.append(" ", 2);
881 prefix.append("\0", 1);
883 fprintf(stderr, "%s+-%s (%p)\n", prefix.data(), m_urlString.utf8().data(), this);
885 int totalSubItems = 0;
886 for (unsigned i = 0; i < m_children.size(); ++i)
887 totalSubItems += m_children[i]->showTreeWithIndent(indentLevel + 1);
888 return totalSubItems + 1;
893 } // namespace WebCore
897 int showTree(const WebCore::HistoryItem* item)
899 return item->showTree();