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"
34 #include "SerializedScriptValue.h"
35 #include "SharedBuffer.h"
37 #include <wtf/CurrentTime.h>
38 #include <wtf/DateMath.h>
39 #include <wtf/Decoder.h>
40 #include <wtf/Encoder.h>
41 #include <wtf/text/CString.h>
45 const uint32_t backForwardTreeEncodingVersion = 2;
47 static long long generateSequenceNumber()
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);
55 static void defaultNotifyHistoryItemChanged(HistoryItem*)
59 void (*notifyHistoryItemChanged)(HistoryItem*) = defaultNotifyHistoryItemChanged;
61 HistoryItem::HistoryItem()
62 : m_lastVisitedTime(0)
63 , m_lastVisitWasHTTPNonGet(false)
64 , m_pageScaleFactor(0)
65 , m_lastVisitWasFailure(false)
66 , m_isTargetItem(false)
68 , m_itemSequenceNumber(generateSequenceNumber())
69 , m_documentSequenceNumber(generateSequenceNumber())
75 HistoryItem::HistoryItem(const String& urlString, const String& title, double time)
76 : m_urlString(urlString)
77 , m_originalURLString(urlString)
79 , m_lastVisitedTime(time)
80 , m_lastVisitWasHTTPNonGet(false)
81 , m_pageScaleFactor(0)
82 , m_lastVisitWasFailure(false)
83 , m_isTargetItem(false)
85 , m_itemSequenceNumber(generateSequenceNumber())
86 , m_documentSequenceNumber(generateSequenceNumber())
90 iconDatabase().retainIconForPageURL(m_urlString);
93 HistoryItem::HistoryItem(const String& urlString, const String& title, const String& alternateTitle, double time)
94 : m_urlString(urlString)
95 , m_originalURLString(urlString)
97 , m_displayTitle(alternateTitle)
98 , m_lastVisitedTime(time)
99 , m_lastVisitWasHTTPNonGet(false)
100 , m_pageScaleFactor(0)
101 , m_lastVisitWasFailure(false)
102 , m_isTargetItem(false)
104 , m_itemSequenceNumber(generateSequenceNumber())
105 , m_documentSequenceNumber(generateSequenceNumber())
109 iconDatabase().retainIconForPageURL(m_urlString);
112 HistoryItem::HistoryItem(const URL& url, const String& target, const String& parent, const String& title)
113 : m_urlString(url.string())
114 , m_originalURLString(url.string())
118 , m_lastVisitedTime(0)
119 , m_lastVisitWasHTTPNonGet(false)
120 , m_pageScaleFactor(0)
121 , m_lastVisitWasFailure(false)
122 , m_isTargetItem(false)
124 , m_itemSequenceNumber(generateSequenceNumber())
125 , m_documentSequenceNumber(generateSequenceNumber())
129 iconDatabase().retainIconForPageURL(m_urlString);
132 HistoryItem::~HistoryItem()
134 ASSERT(!m_cachedPage);
135 iconDatabase().releaseIconForPageURL(m_urlString);
138 inline 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)
161 m_formData = item.m_formData->copy();
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());
168 if (item.m_redirectURLs)
169 m_redirectURLs = adoptPtr(new Vector<String>(*item.m_redirectURLs));
172 PassRefPtr<HistoryItem> HistoryItem::copy() const
174 return adoptRef(new HistoryItem(*this));
177 void HistoryItem::reset()
179 iconDatabase().releaseIconForPageURL(m_urlString);
181 m_urlString = String();
182 m_originalURLString = String();
183 m_referrer = String();
187 m_displayTitle = String();
189 m_lastVisitedTime = 0;
190 m_lastVisitWasHTTPNonGet = false;
192 m_lastVisitWasFailure = false;
193 m_isTargetItem = false;
195 m_dailyVisitCounts.clear();
196 m_weeklyVisitCounts.clear();
198 m_redirectURLs.clear();
200 m_itemSequenceNumber = generateSequenceNumber();
203 m_documentSequenceNumber = generateSequenceNumber();
206 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 bool HistoryItem::hasCachedPageExpired() const
235 return m_cachedPage ? m_cachedPage->hasExpired() : false;
238 double HistoryItem::lastVisitedTime() const
240 return m_lastVisitedTime;
243 URL HistoryItem::url() const
245 return URL(ParsedURLString, m_urlString);
248 URL HistoryItem::originalURL() const
250 return URL(ParsedURLString, m_originalURLString);
253 const String& HistoryItem::referrer() const
258 const String& HistoryItem::target() const
263 const String& HistoryItem::parent() const
268 void HistoryItem::setAlternateTitle(const String& alternateTitle)
270 m_displayTitle = alternateTitle;
271 notifyHistoryItemChanged(this);
274 void HistoryItem::setURLString(const String& urlString)
276 if (m_urlString != urlString) {
277 iconDatabase().releaseIconForPageURL(m_urlString);
278 m_urlString = urlString;
279 iconDatabase().retainIconForPageURL(m_urlString);
282 notifyHistoryItemChanged(this);
285 void HistoryItem::setURL(const URL& url)
287 pageCache()->remove(this);
288 setURLString(url.string());
289 clearDocumentState();
292 void HistoryItem::setOriginalURLString(const String& urlString)
294 m_originalURLString = urlString;
295 notifyHistoryItemChanged(this);
298 void HistoryItem::setReferrer(const String& referrer)
300 m_referrer = referrer;
301 notifyHistoryItemChanged(this);
304 void HistoryItem::setTitle(const String& title)
307 notifyHistoryItemChanged(this);
310 void HistoryItem::setTarget(const String& target)
313 notifyHistoryItemChanged(this);
316 void HistoryItem::setParent(const String& parent)
321 static inline int timeToDay(double time)
323 return static_cast<int>(ceil(time / secondsPerDay));
326 void HistoryItem::padDailyCountsForNewVisit(double time)
328 if (m_dailyVisitCounts.isEmpty())
329 m_dailyVisitCounts.insert(0, m_visitCount);
331 int daysElapsed = timeToDay(time) - timeToDay(m_lastVisitedTime);
337 padding.fill(0, daysElapsed);
338 m_dailyVisitCounts.insert(0, padding);
341 static const size_t daysPerWeek = 7;
342 static const size_t maxDailyCounts = 2 * daysPerWeek - 1;
343 static const size_t maxWeeklyCounts = 5;
345 void HistoryItem::collapseDailyVisitsToWeekly()
347 while (m_dailyVisitCounts.size() > maxDailyCounts) {
348 int oldestWeekTotal = 0;
349 for (size_t i = 0; i < daysPerWeek; i++)
350 oldestWeekTotal += m_dailyVisitCounts[m_dailyVisitCounts.size() - daysPerWeek + i];
351 m_dailyVisitCounts.shrink(m_dailyVisitCounts.size() - daysPerWeek);
352 m_weeklyVisitCounts.insert(0, oldestWeekTotal);
355 if (m_weeklyVisitCounts.size() > maxWeeklyCounts)
356 m_weeklyVisitCounts.shrink(maxWeeklyCounts);
359 void HistoryItem::recordVisitAtTime(double time, VisitCountBehavior visitCountBehavior)
361 padDailyCountsForNewVisit(time);
363 m_lastVisitedTime = time;
365 if (visitCountBehavior == IncreaseVisitCount) {
367 ++m_dailyVisitCounts[0];
370 collapseDailyVisitsToWeekly();
373 void HistoryItem::setLastVisitedTime(double time)
375 if (m_lastVisitedTime != time)
376 recordVisitAtTime(time);
379 void HistoryItem::visited(const String& title, double time, VisitCountBehavior visitCountBehavior)
382 recordVisitAtTime(time, visitCountBehavior);
385 int HistoryItem::visitCount() const
390 void HistoryItem::recordInitialVisit()
392 ASSERT(!m_visitCount);
393 recordVisitAtTime(m_lastVisitedTime);
396 void HistoryItem::setVisitCount(int count)
398 m_visitCount = count;
401 void HistoryItem::adoptVisitCounts(Vector<int>& dailyCounts, Vector<int>& weeklyCounts)
403 m_dailyVisitCounts.clear();
404 m_dailyVisitCounts.swap(dailyCounts);
405 m_weeklyVisitCounts.clear();
406 m_weeklyVisitCounts.swap(weeklyCounts);
409 const IntPoint& HistoryItem::scrollPoint() const
411 return m_scrollPoint;
414 void HistoryItem::setScrollPoint(const IntPoint& point)
416 m_scrollPoint = point;
419 void HistoryItem::clearScrollPoint()
421 m_scrollPoint.setX(0);
422 m_scrollPoint.setY(0);
425 float HistoryItem::pageScaleFactor() const
427 return m_pageScaleFactor;
430 void HistoryItem::setPageScaleFactor(float scaleFactor)
432 m_pageScaleFactor = scaleFactor;
435 void HistoryItem::setDocumentState(const Vector<String>& state)
437 m_documentState = state;
440 const Vector<String>& HistoryItem::documentState() const
442 return m_documentState;
445 void HistoryItem::clearDocumentState()
447 m_documentState.clear();
450 bool HistoryItem::isTargetItem() const
452 return m_isTargetItem;
455 void HistoryItem::setIsTargetItem(bool flag)
457 m_isTargetItem = flag;
460 void HistoryItem::setStateObject(PassRefPtr<SerializedScriptValue> object)
462 m_stateObject = object;
465 void HistoryItem::addChildItem(PassRefPtr<HistoryItem> child)
467 ASSERT(!childItemWithTarget(child->target()));
468 m_children.append(child);
471 void HistoryItem::setChildItem(PassRefPtr<HistoryItem> child)
473 ASSERT(!child->isTargetItem());
474 unsigned size = m_children.size();
475 for (unsigned i = 0; i < size; ++i) {
476 if (m_children[i]->target() == child->target()) {
477 child->setIsTargetItem(m_children[i]->isTargetItem());
478 m_children[i] = child;
482 m_children.append(child);
485 HistoryItem* HistoryItem::childItemWithTarget(const String& target) const
487 unsigned size = m_children.size();
488 for (unsigned i = 0; i < size; ++i) {
489 if (m_children[i]->target() == target)
490 return m_children[i].get();
495 HistoryItem* HistoryItem::childItemWithDocumentSequenceNumber(long long number) const
497 unsigned size = m_children.size();
498 for (unsigned i = 0; i < size; ++i) {
499 if (m_children[i]->documentSequenceNumber() == number)
500 return m_children[i].get();
505 // <rdar://problem/4895849> HistoryItem::findTargetItem() should be replaced with a non-recursive method.
506 HistoryItem* HistoryItem::findTargetItem()
510 unsigned size = m_children.size();
511 for (unsigned i = 0; i < size; ++i) {
512 if (HistoryItem* match = m_children[i]->targetItem())
518 HistoryItem* HistoryItem::targetItem()
520 HistoryItem* foundItem = findTargetItem();
521 return foundItem ? foundItem : this;
524 const HistoryItemVector& HistoryItem::children() const
529 bool HistoryItem::hasChildren() const
531 return !m_children.isEmpty();
534 void HistoryItem::clearChildren()
539 bool HistoryItem::isAncestorOf(const HistoryItem* item) const
541 for (size_t i = 0; i < m_children.size(); ++i) {
542 HistoryItem* child = m_children[i].get();
545 if (child->isAncestorOf(item))
551 // We do same-document navigation if going to a different item and if either of the following is true:
552 // - The other item corresponds to the same document (for history entries created via pushState or fragment changes).
553 // - The other item corresponds to the same set of documents, including frames (for history entries created via regular navigation)
554 bool HistoryItem::shouldDoSameDocumentNavigationTo(HistoryItem* otherItem) const
556 if (this == otherItem)
559 if (stateObject() || otherItem->stateObject())
560 return documentSequenceNumber() == otherItem->documentSequenceNumber();
562 if ((url().hasFragmentIdentifier() || otherItem->url().hasFragmentIdentifier()) && equalIgnoringFragmentIdentifier(url(), otherItem->url()))
563 return documentSequenceNumber() == otherItem->documentSequenceNumber();
565 return hasSameDocumentTree(otherItem);
568 // Does a recursive check that this item and its descendants have the same
569 // document sequence numbers as the other item.
570 bool HistoryItem::hasSameDocumentTree(HistoryItem* otherItem) const
572 if (documentSequenceNumber() != otherItem->documentSequenceNumber())
575 if (children().size() != otherItem->children().size())
578 for (size_t i = 0; i < children().size(); i++) {
579 HistoryItem* child = children()[i].get();
580 HistoryItem* otherChild = otherItem->childItemWithDocumentSequenceNumber(child->documentSequenceNumber());
581 if (!otherChild || !child->hasSameDocumentTree(otherChild))
588 // Does a non-recursive check that this item and its immediate children have the
589 // same frames as the other item.
590 bool HistoryItem::hasSameFrames(HistoryItem* otherItem) const
592 if (target() != otherItem->target())
595 if (children().size() != otherItem->children().size())
598 for (size_t i = 0; i < children().size(); i++) {
599 if (!otherItem->childItemWithTarget(children()[i]->target()))
606 String HistoryItem::formContentType() const
608 return m_formContentType;
611 void HistoryItem::setFormInfoFromRequest(const ResourceRequest& request)
613 m_referrer = request.httpReferrer();
615 if (equalIgnoringCase(request.httpMethod(), "POST")) {
616 // FIXME: Eventually we have to make this smart enough to handle the case where
617 // we have a stream for the body to handle the "data interspersed with files" feature.
618 m_formData = request.httpBody();
619 m_formContentType = request.httpContentType();
622 m_formContentType = String();
626 void HistoryItem::setFormData(PassRefPtr<FormData> formData)
628 m_formData = formData;
631 void HistoryItem::setFormContentType(const String& formContentType)
633 m_formContentType = formContentType;
636 FormData* HistoryItem::formData()
638 return m_formData.get();
641 bool HistoryItem::isCurrentDocument(Document* doc) const
643 // FIXME: We should find a better way to check if this is the current document.
644 return equalIgnoringFragmentIdentifier(url(), doc->url());
647 void HistoryItem::mergeAutoCompleteHints(HistoryItem* otherItem)
649 // FIXME: this is broken - we should be merging the daily counts
650 // somehow. but this is to support API that's not really used in
651 // practice so leave it broken for now.
653 if (otherItem != this)
654 m_visitCount += otherItem->m_visitCount;
657 void HistoryItem::addRedirectURL(const String& url)
660 m_redirectURLs = adoptPtr(new Vector<String>);
662 // Our API allows us to store all the URLs in the redirect chain, but for
663 // now we only have a use for the final URL.
664 (*m_redirectURLs).resize(1);
665 (*m_redirectURLs)[0] = url;
668 Vector<String>* HistoryItem::redirectURLs() const
670 return m_redirectURLs.get();
673 void HistoryItem::setRedirectURLs(PassOwnPtr<Vector<String> > redirectURLs)
675 m_redirectURLs = redirectURLs;
678 void HistoryItem::encodeBackForwardTree(Encoder& encoder) const
680 encoder.encodeUInt32(backForwardTreeEncodingVersion);
682 encodeBackForwardTreeNode(encoder);
685 void HistoryItem::encodeBackForwardTreeNode(Encoder& encoder) const
687 size_t size = m_children.size();
688 encoder.encodeUInt64(size);
689 for (size_t i = 0; i < size; ++i) {
690 const HistoryItem& child = *m_children[i];
692 encoder.encodeString(child.m_originalURLString);
694 encoder.encodeString(child.m_urlString);
696 child.encodeBackForwardTreeNode(encoder);
699 encoder.encodeInt64(m_documentSequenceNumber);
701 size = m_documentState.size();
702 encoder.encodeUInt64(size);
703 for (size_t i = 0; i < size; ++i)
704 encoder.encodeString(m_documentState[i]);
706 encoder.encodeString(m_formContentType);
708 encoder.encodeBool(m_formData);
710 m_formData->encode(encoder);
712 encoder.encodeInt64(m_itemSequenceNumber);
714 encoder.encodeString(m_referrer);
716 encoder.encodeInt32(m_scrollPoint.x());
717 encoder.encodeInt32(m_scrollPoint.y());
719 encoder.encodeFloat(m_pageScaleFactor);
721 encoder.encodeBool(m_stateObject);
723 encoder.encodeBytes(m_stateObject->data().data(), m_stateObject->data().size());
725 encoder.encodeString(m_target);
728 struct DecodeRecursionStackElement {
729 RefPtr<HistoryItem> node;
733 DecodeRecursionStackElement(PassRefPtr<HistoryItem> node, size_t i, uint64_t size)
741 PassRefPtr<HistoryItem> HistoryItem::decodeBackForwardTree(const String& topURLString, const String& topTitle, const String& topOriginalURLString, Decoder& decoder)
743 // Since the data stream is not trusted, the decode has to be non-recursive.
744 // We don't want bad data to cause a stack overflow.
747 if (!decoder.decodeUInt32(version))
749 if (version != backForwardTreeEncodingVersion)
752 String urlString = topURLString;
753 String title = topTitle;
754 String originalURLString = topOriginalURLString;
756 Vector<DecodeRecursionStackElement, 16> recursionStack;
759 RefPtr<HistoryItem> node = create(urlString, title, 0);
761 node->setOriginalURLString(originalURLString);
766 if (!decoder.decodeUInt64(size))
769 RefPtr<HistoryItem> child;
770 for (i = 0; i < size; ++i) {
771 if (!decoder.decodeString(originalURLString))
774 if (!decoder.decodeString(urlString))
777 recursionStack.append(DecodeRecursionStackElement(node.release(), i, size));
781 node->m_children.append(child.release());
784 if (!decoder.decodeInt64(node->m_documentSequenceNumber))
787 if (!decoder.decodeUInt64(size))
789 for (i = 0; i < size; ++i) {
791 if (!decoder.decodeString(state))
793 node->m_documentState.append(state);
796 if (!decoder.decodeString(node->m_formContentType))
800 if (!decoder.decodeBool(hasFormData))
803 node->m_formData = FormData::decode(decoder);
804 if (!node->m_formData)
808 if (!decoder.decodeInt64(node->m_itemSequenceNumber))
811 if (!decoder.decodeString(node->m_referrer))
815 if (!decoder.decodeInt32(x))
818 if (!decoder.decodeInt32(y))
820 node->m_scrollPoint = IntPoint(x, y);
822 if (!decoder.decodeFloat(node->m_pageScaleFactor))
826 if (!decoder.decodeBool(hasStateObject))
828 if (hasStateObject) {
829 Vector<uint8_t> bytes;
830 if (!decoder.decodeBytes(bytes))
832 node->m_stateObject = SerializedScriptValue::adopt(bytes);
835 if (!decoder.decodeString(node->m_target))
838 // Simulate recursion with our own stack.
839 if (!recursionStack.isEmpty()) {
840 DecodeRecursionStackElement& element = recursionStack.last();
841 child = node.release();
842 node = element.node.release();
845 recursionStack.removeLast();
849 return node.release();
854 int HistoryItem::showTree() const
856 return showTreeWithIndent(0);
859 int HistoryItem::showTreeWithIndent(unsigned indentLevel) const
862 for (unsigned i = 0; i < indentLevel; ++i)
863 prefix.append(" ", 2);
864 prefix.append("\0", 1);
866 fprintf(stderr, "%s+-%s (%p)\n", prefix.data(), m_urlString.utf8().data(), this);
868 int totalSubItems = 0;
869 for (unsigned i = 0; i < m_children.size(); ++i)
870 totalSubItems += m_children[i]->showTreeWithIndent(indentLevel + 1);
871 return totalSubItems + 1;
876 } // namespace WebCore
880 int showTree(const WebCore::HistoryItem* item)
882 return item->showTree();