2 * Copyright (C) 2005, 2006, 2008 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/MathExtras.h>
37 #include <wtf/text/CString.h>
41 static long long generateSequenceNumber()
43 // Initialize to the current time to reduce the likelihood of generating
44 // identifiers that overlap with those from past/future browser sessions.
45 static long long next = static_cast<long long>(currentTime() * 1000000.0);
49 static void defaultNotifyHistoryItemChanged(HistoryItem*)
53 void (*notifyHistoryItemChanged)(HistoryItem*) = defaultNotifyHistoryItemChanged;
55 HistoryItem::HistoryItem()
56 : m_lastVisitedTime(0)
57 , m_lastVisitWasHTTPNonGet(false)
58 , m_lastVisitWasFailure(false)
59 , m_isTargetItem(false)
61 , m_itemSequenceNumber(generateSequenceNumber())
62 , m_documentSequenceNumber(generateSequenceNumber())
68 HistoryItem::HistoryItem(const String& urlString, const String& title, double time)
69 : m_urlString(urlString)
70 , m_originalURLString(urlString)
72 , m_lastVisitedTime(time)
73 , m_lastVisitWasHTTPNonGet(false)
74 , m_lastVisitWasFailure(false)
75 , m_isTargetItem(false)
77 , m_itemSequenceNumber(generateSequenceNumber())
78 , m_documentSequenceNumber(generateSequenceNumber())
82 iconDatabase()->retainIconForPageURL(m_urlString);
85 HistoryItem::HistoryItem(const String& urlString, const String& title, const String& alternateTitle, double time)
86 : m_urlString(urlString)
87 , m_originalURLString(urlString)
89 , m_displayTitle(alternateTitle)
90 , m_lastVisitedTime(time)
91 , m_lastVisitWasHTTPNonGet(false)
92 , m_lastVisitWasFailure(false)
93 , m_isTargetItem(false)
95 , m_itemSequenceNumber(generateSequenceNumber())
96 , m_documentSequenceNumber(generateSequenceNumber())
100 iconDatabase()->retainIconForPageURL(m_urlString);
103 HistoryItem::HistoryItem(const KURL& url, const String& target, const String& parent, const String& title)
104 : m_urlString(url.string())
105 , m_originalURLString(url.string())
109 , m_lastVisitedTime(0)
110 , m_lastVisitWasHTTPNonGet(false)
111 , m_lastVisitWasFailure(false)
112 , m_isTargetItem(false)
114 , m_itemSequenceNumber(generateSequenceNumber())
115 , m_documentSequenceNumber(generateSequenceNumber())
119 iconDatabase()->retainIconForPageURL(m_urlString);
122 HistoryItem::~HistoryItem()
124 ASSERT(!m_cachedPage);
125 iconDatabase()->releaseIconForPageURL(m_urlString);
126 #if PLATFORM(ANDROID)
128 m_bridge->detachHistoryItem();
132 inline HistoryItem::HistoryItem(const HistoryItem& item)
133 : RefCounted<HistoryItem>()
134 , m_urlString(item.m_urlString)
135 , m_originalURLString(item.m_originalURLString)
136 , m_referrer(item.m_referrer)
137 , m_target(item.m_target)
138 , m_parent(item.m_parent)
139 , m_title(item.m_title)
140 , m_displayTitle(item.m_displayTitle)
141 , m_lastVisitedTime(item.m_lastVisitedTime)
142 , m_lastVisitWasHTTPNonGet(item.m_lastVisitWasHTTPNonGet)
143 , m_scrollPoint(item.m_scrollPoint)
144 , m_lastVisitWasFailure(item.m_lastVisitWasFailure)
145 , m_isTargetItem(item.m_isTargetItem)
146 , m_visitCount(item.m_visitCount)
147 , m_dailyVisitCounts(item.m_dailyVisitCounts)
148 , m_weeklyVisitCounts(item.m_weeklyVisitCounts)
149 , m_itemSequenceNumber(item.m_itemSequenceNumber)
150 , m_documentSequenceNumber(item.m_documentSequenceNumber)
151 , m_formContentType(item.m_formContentType)
154 m_formData = item.m_formData->copy();
156 unsigned size = item.m_children.size();
157 m_children.reserveInitialCapacity(size);
158 for (unsigned i = 0; i < size; ++i)
159 m_children.uncheckedAppend(item.m_children[i]->copy());
161 if (item.m_redirectURLs)
162 m_redirectURLs = adoptPtr(new Vector<String>(*item.m_redirectURLs));
165 PassRefPtr<HistoryItem> HistoryItem::copy() const
167 return adoptRef(new HistoryItem(*this));
170 void HistoryItem::reset()
172 iconDatabase()->releaseIconForPageURL(m_urlString);
174 m_urlString = String();
175 m_originalURLString = String();
176 m_referrer = String();
180 m_displayTitle = String();
182 m_lastVisitedTime = 0;
183 m_lastVisitWasHTTPNonGet = false;
185 m_lastVisitWasFailure = false;
186 m_isTargetItem = false;
188 m_dailyVisitCounts.clear();
189 m_weeklyVisitCounts.clear();
191 m_redirectURLs.clear();
193 m_itemSequenceNumber = generateSequenceNumber();
196 m_documentSequenceNumber = generateSequenceNumber();
199 m_formContentType = String();
202 const String& HistoryItem::urlString() const
207 // The first URL we loaded to get to where this history item points. Includes both client
208 // and server redirects.
209 const String& HistoryItem::originalURLString() const
211 return m_originalURLString;
214 const String& HistoryItem::title() const
219 const String& HistoryItem::alternateTitle() const
221 return m_displayTitle;
224 Image* HistoryItem::icon() const
226 Image* result = iconDatabase()->iconForPageURL(m_urlString, IntSize(16, 16));
227 return result ? result : iconDatabase()->defaultIcon(IntSize(16, 16));
230 double HistoryItem::lastVisitedTime() const
232 return m_lastVisitedTime;
235 KURL HistoryItem::url() const
237 return KURL(ParsedURLString, m_urlString);
240 KURL HistoryItem::originalURL() const
242 return KURL(ParsedURLString, m_originalURLString);
245 const String& HistoryItem::referrer() const
250 const String& HistoryItem::target() const
255 const String& HistoryItem::parent() const
260 void HistoryItem::setAlternateTitle(const String& alternateTitle)
262 m_displayTitle = alternateTitle;
263 notifyHistoryItemChanged(this);
266 void HistoryItem::setURLString(const String& urlString)
268 if (m_urlString != urlString) {
269 iconDatabase()->releaseIconForPageURL(m_urlString);
270 m_urlString = urlString;
271 iconDatabase()->retainIconForPageURL(m_urlString);
274 notifyHistoryItemChanged(this);
277 void HistoryItem::setURL(const KURL& url)
279 pageCache()->remove(this);
280 setURLString(url.string());
281 clearDocumentState();
284 void HistoryItem::setOriginalURLString(const String& urlString)
286 m_originalURLString = urlString;
287 notifyHistoryItemChanged(this);
290 void HistoryItem::setReferrer(const String& referrer)
292 m_referrer = referrer;
293 notifyHistoryItemChanged(this);
296 void HistoryItem::setTitle(const String& title)
299 notifyHistoryItemChanged(this);
302 void HistoryItem::setTarget(const String& target)
305 notifyHistoryItemChanged(this);
308 void HistoryItem::setParent(const String& parent)
313 static inline int timeToDay(double time)
315 static const double secondsPerDay = 60 * 60 * 24;
316 return static_cast<int>(ceil(time / secondsPerDay));
319 void HistoryItem::padDailyCountsForNewVisit(double time)
321 if (m_dailyVisitCounts.isEmpty())
322 m_dailyVisitCounts.prepend(m_visitCount);
324 int daysElapsed = timeToDay(time) - timeToDay(m_lastVisitedTime);
330 padding.fill(0, daysElapsed);
331 m_dailyVisitCounts.prepend(padding);
334 static const size_t daysPerWeek = 7;
335 static const size_t maxDailyCounts = 2 * daysPerWeek - 1;
336 static const size_t maxWeeklyCounts = 5;
338 void HistoryItem::collapseDailyVisitsToWeekly()
340 while (m_dailyVisitCounts.size() > maxDailyCounts) {
341 int oldestWeekTotal = 0;
342 for (size_t i = 0; i < daysPerWeek; i++)
343 oldestWeekTotal += m_dailyVisitCounts[m_dailyVisitCounts.size() - daysPerWeek + i];
344 m_dailyVisitCounts.shrink(m_dailyVisitCounts.size() - daysPerWeek);
345 m_weeklyVisitCounts.prepend(oldestWeekTotal);
348 if (m_weeklyVisitCounts.size() > maxWeeklyCounts)
349 m_weeklyVisitCounts.shrink(maxWeeklyCounts);
352 void HistoryItem::recordVisitAtTime(double time, VisitCountBehavior visitCountBehavior)
354 padDailyCountsForNewVisit(time);
356 m_lastVisitedTime = time;
358 if (visitCountBehavior == IncreaseVisitCount) {
360 ++m_dailyVisitCounts[0];
363 collapseDailyVisitsToWeekly();
366 void HistoryItem::setLastVisitedTime(double time)
368 if (m_lastVisitedTime != time)
369 recordVisitAtTime(time);
372 void HistoryItem::visited(const String& title, double time, VisitCountBehavior visitCountBehavior)
375 recordVisitAtTime(time, visitCountBehavior);
378 int HistoryItem::visitCount() const
383 void HistoryItem::recordInitialVisit()
385 ASSERT(!m_visitCount);
386 recordVisitAtTime(m_lastVisitedTime);
389 void HistoryItem::setVisitCount(int count)
391 m_visitCount = count;
394 void HistoryItem::adoptVisitCounts(Vector<int>& dailyCounts, Vector<int>& weeklyCounts)
396 m_dailyVisitCounts.clear();
397 m_dailyVisitCounts.swap(dailyCounts);
398 m_weeklyVisitCounts.clear();
399 m_weeklyVisitCounts.swap(weeklyCounts);
402 const IntPoint& HistoryItem::scrollPoint() const
404 return m_scrollPoint;
407 void HistoryItem::setScrollPoint(const IntPoint& point)
409 m_scrollPoint = point;
412 void HistoryItem::clearScrollPoint()
414 m_scrollPoint.setX(0);
415 m_scrollPoint.setY(0);
418 void HistoryItem::setDocumentState(const Vector<String>& state)
420 m_documentState = state;
421 #if PLATFORM(ANDROID)
422 notifyHistoryItemChanged(this);
426 const Vector<String>& HistoryItem::documentState() const
428 return m_documentState;
431 void HistoryItem::clearDocumentState()
433 m_documentState.clear();
434 #if PLATFORM(ANDROID)
435 notifyHistoryItemChanged(this);
439 bool HistoryItem::isTargetItem() const
441 return m_isTargetItem;
444 void HistoryItem::setIsTargetItem(bool flag)
446 m_isTargetItem = flag;
447 #if PLATFORM(ANDROID)
448 notifyHistoryItemChanged(this);
452 void HistoryItem::setStateObject(PassRefPtr<SerializedScriptValue> object)
454 m_stateObject = object;
457 void HistoryItem::addChildItem(PassRefPtr<HistoryItem> child)
459 ASSERT(!childItemWithTarget(child->target()));
460 m_children.append(child);
461 #if PLATFORM(ANDROID)
462 notifyHistoryItemChanged(this);
466 void HistoryItem::setChildItem(PassRefPtr<HistoryItem> child)
468 ASSERT(!child->isTargetItem());
469 unsigned size = m_children.size();
470 for (unsigned i = 0; i < size; ++i) {
471 if (m_children[i]->target() == child->target()) {
472 child->setIsTargetItem(m_children[i]->isTargetItem());
473 m_children[i] = child;
477 m_children.append(child);
480 HistoryItem* HistoryItem::childItemWithTarget(const String& target) const
482 unsigned size = m_children.size();
483 for (unsigned i = 0; i < size; ++i) {
484 if (m_children[i]->target() == target)
485 return m_children[i].get();
490 HistoryItem* HistoryItem::childItemWithDocumentSequenceNumber(long long number) const
492 unsigned size = m_children.size();
493 for (unsigned i = 0; i < size; ++i) {
494 if (m_children[i]->documentSequenceNumber() == number)
495 return m_children[i].get();
500 // <rdar://problem/4895849> HistoryItem::findTargetItem() should be replaced with a non-recursive method.
501 HistoryItem* HistoryItem::findTargetItem()
505 unsigned size = m_children.size();
506 for (unsigned i = 0; i < size; ++i) {
507 if (HistoryItem* match = m_children[i]->targetItem())
513 HistoryItem* HistoryItem::targetItem()
515 HistoryItem* foundItem = findTargetItem();
516 return foundItem ? foundItem : this;
519 const HistoryItemVector& HistoryItem::children() const
524 bool HistoryItem::hasChildren() const
526 return !m_children.isEmpty();
529 void HistoryItem::clearChildren()
534 // We do same-document navigation if going to a different item and if either of the following is true:
535 // - The other item corresponds to the same document (for history entries created via pushState or fragment changes).
536 // - The other item corresponds to the same set of documents, including frames (for history entries created via regular navigation)
537 bool HistoryItem::shouldDoSameDocumentNavigationTo(HistoryItem* otherItem) const
539 if (this == otherItem)
542 if (stateObject() || otherItem->stateObject())
543 return documentSequenceNumber() == otherItem->documentSequenceNumber();
545 if ((url().hasFragmentIdentifier() || otherItem->url().hasFragmentIdentifier()) && equalIgnoringFragmentIdentifier(url(), otherItem->url()))
546 return documentSequenceNumber() == otherItem->documentSequenceNumber();
548 return hasSameDocumentTree(otherItem);
551 // Does a recursive check that this item and its descendants have the same
552 // document sequence numbers as the other item.
553 bool HistoryItem::hasSameDocumentTree(HistoryItem* otherItem) const
555 if (documentSequenceNumber() != otherItem->documentSequenceNumber())
558 if (children().size() != otherItem->children().size())
561 for (size_t i = 0; i < children().size(); i++) {
562 HistoryItem* child = children()[i].get();
563 HistoryItem* otherChild = otherItem->childItemWithDocumentSequenceNumber(child->documentSequenceNumber());
564 if (!otherChild || !child->hasSameDocumentTree(otherChild))
571 // Does a non-recursive check that this item and its immediate children have the
572 // same frames as the other item.
573 bool HistoryItem::hasSameFrames(HistoryItem* otherItem) const
575 if (target() != otherItem->target())
578 if (children().size() != otherItem->children().size())
581 for (size_t i = 0; i < children().size(); i++) {
582 if (!otherItem->childItemWithTarget(children()[i]->target()))
589 String HistoryItem::formContentType() const
591 return m_formContentType;
594 void HistoryItem::setFormInfoFromRequest(const ResourceRequest& request)
596 m_referrer = request.httpReferrer();
598 if (equalIgnoringCase(request.httpMethod(), "POST")) {
599 // FIXME: Eventually we have to make this smart enough to handle the case where
600 // we have a stream for the body to handle the "data interspersed with files" feature.
601 m_formData = request.httpBody();
602 m_formContentType = request.httpContentType();
605 m_formContentType = String();
607 #if PLATFORM(ANDROID)
608 notifyHistoryItemChanged(this);
612 void HistoryItem::setFormData(PassRefPtr<FormData> formData)
614 m_formData = formData;
617 void HistoryItem::setFormContentType(const String& formContentType)
619 m_formContentType = formContentType;
622 FormData* HistoryItem::formData()
624 return m_formData.get();
627 bool HistoryItem::isCurrentDocument(Document* doc) const
629 // FIXME: We should find a better way to check if this is the current document.
630 return equalIgnoringFragmentIdentifier(url(), doc->url());
633 void HistoryItem::mergeAutoCompleteHints(HistoryItem* otherItem)
635 // FIXME: this is broken - we should be merging the daily counts
636 // somehow. but this is to support API that's not really used in
637 // practice so leave it broken for now.
639 if (otherItem != this)
640 m_visitCount += otherItem->m_visitCount;
643 void HistoryItem::addRedirectURL(const String& url)
646 m_redirectURLs = adoptPtr(new Vector<String>);
648 // Our API allows us to store all the URLs in the redirect chain, but for
649 // now we only have a use for the final URL.
650 (*m_redirectURLs).resize(1);
651 (*m_redirectURLs)[0] = url;
654 Vector<String>* HistoryItem::redirectURLs() const
656 return m_redirectURLs.get();
659 void HistoryItem::setRedirectURLs(PassOwnPtr<Vector<String> > redirectURLs)
661 m_redirectURLs = redirectURLs;
666 int HistoryItem::showTree() const
668 return showTreeWithIndent(0);
671 int HistoryItem::showTreeWithIndent(unsigned indentLevel) const
674 for (unsigned i = 0; i < indentLevel; ++i)
675 prefix.append(" ", 2);
676 prefix.append("\0", 1);
678 fprintf(stderr, "%s+-%s (%p)\n", prefix.data(), m_urlString.utf8().data(), this);
680 int totalSubItems = 0;
681 for (unsigned i = 0; i < m_children.size(); ++i)
682 totalSubItems += m_children[i]->showTreeWithIndent(indentLevel + 1);
683 return totalSubItems + 1;
688 } // namespace WebCore
692 int showTree(const WebCore::HistoryItem* item)
694 return item->showTree();