Home | History | Annotate | Download | only in history
      1 /*
      2  * Copyright (C) 2005, 2006, 2008 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 "CString.h"
     30 #include "CachedPage.h"
     31 #include "Document.h"
     32 #include "IconDatabase.h"
     33 #include "PageCache.h"
     34 #include "ResourceRequest.h"
     35 #include <stdio.h>
     36 #include <wtf/CurrentTime.h>
     37 
     38 namespace WebCore {
     39 
     40 static long long generateDocumentSequenceNumber()
     41 {
     42     // Initialize to the current time to reduce the likelihood of generating
     43     // identifiers that overlap with those from past/future browser sessions.
     44     static long long next = static_cast<long long>(currentTime() * 1000000.0);
     45     return ++next;
     46 }
     47 
     48 static void defaultNotifyHistoryItemChanged(HistoryItem*)
     49 {
     50 }
     51 
     52 void (*notifyHistoryItemChanged)(HistoryItem*) = defaultNotifyHistoryItemChanged;
     53 
     54 HistoryItem::HistoryItem()
     55     : m_lastVisitedTime(0)
     56     , m_lastVisitWasHTTPNonGet(false)
     57     , m_lastVisitWasFailure(false)
     58     , m_isTargetItem(false)
     59     , m_visitCount(0)
     60     , m_documentSequenceNumber(generateDocumentSequenceNumber())
     61 {
     62 }
     63 
     64 HistoryItem::HistoryItem(const String& urlString, const String& title, double time)
     65     : m_urlString(urlString)
     66     , m_originalURLString(urlString)
     67     , m_title(title)
     68     , m_lastVisitedTime(time)
     69     , m_lastVisitWasHTTPNonGet(false)
     70     , m_lastVisitWasFailure(false)
     71     , m_isTargetItem(false)
     72     , m_visitCount(0)
     73     , m_documentSequenceNumber(generateDocumentSequenceNumber())
     74 {
     75     iconDatabase()->retainIconForPageURL(m_urlString);
     76 }
     77 
     78 HistoryItem::HistoryItem(const String& urlString, const String& title, const String& alternateTitle, double time)
     79     : m_urlString(urlString)
     80     , m_originalURLString(urlString)
     81     , m_title(title)
     82     , m_displayTitle(alternateTitle)
     83     , m_lastVisitedTime(time)
     84     , m_lastVisitWasHTTPNonGet(false)
     85     , m_lastVisitWasFailure(false)
     86     , m_isTargetItem(false)
     87     , m_visitCount(0)
     88     , m_documentSequenceNumber(generateDocumentSequenceNumber())
     89 {
     90     iconDatabase()->retainIconForPageURL(m_urlString);
     91 }
     92 
     93 HistoryItem::HistoryItem(const KURL& url, const String& target, const String& parent, const String& title)
     94     : m_urlString(url.string())
     95     , m_originalURLString(url.string())
     96     , m_target(target)
     97     , m_parent(parent)
     98     , m_title(title)
     99     , m_lastVisitedTime(0)
    100     , m_lastVisitWasHTTPNonGet(false)
    101     , m_lastVisitWasFailure(false)
    102     , m_isTargetItem(false)
    103     , m_visitCount(0)
    104     , m_documentSequenceNumber(generateDocumentSequenceNumber())
    105 {
    106     iconDatabase()->retainIconForPageURL(m_urlString);
    107 }
    108 
    109 HistoryItem::~HistoryItem()
    110 {
    111     ASSERT(!m_cachedPage);
    112     iconDatabase()->releaseIconForPageURL(m_urlString);
    113 #if PLATFORM(ANDROID)
    114     if (m_bridge)
    115         m_bridge->detachHistoryItem();
    116 #endif
    117 }
    118 
    119 inline HistoryItem::HistoryItem(const HistoryItem& item)
    120     : RefCounted<HistoryItem>()
    121     , m_urlString(item.m_urlString)
    122     , m_originalURLString(item.m_originalURLString)
    123     , m_referrer(item.m_referrer)
    124     , m_target(item.m_target)
    125     , m_parent(item.m_parent)
    126     , m_title(item.m_title)
    127     , m_displayTitle(item.m_displayTitle)
    128     , m_lastVisitedTime(item.m_lastVisitedTime)
    129     , m_lastVisitWasHTTPNonGet(item.m_lastVisitWasHTTPNonGet)
    130     , m_scrollPoint(item.m_scrollPoint)
    131     , m_lastVisitWasFailure(item.m_lastVisitWasFailure)
    132     , m_isTargetItem(item.m_isTargetItem)
    133     , m_visitCount(item.m_visitCount)
    134     , m_dailyVisitCounts(item.m_dailyVisitCounts)
    135     , m_weeklyVisitCounts(item.m_weeklyVisitCounts)
    136     , m_documentSequenceNumber(generateDocumentSequenceNumber())
    137     , m_formContentType(item.m_formContentType)
    138 {
    139     if (item.m_formData)
    140         m_formData = item.m_formData->copy();
    141 
    142     unsigned size = item.m_children.size();
    143     m_children.reserveInitialCapacity(size);
    144     for (unsigned i = 0; i < size; ++i)
    145         m_children.uncheckedAppend(item.m_children[i]->copy());
    146 
    147     if (item.m_redirectURLs)
    148         m_redirectURLs.set(new Vector<String>(*item.m_redirectURLs));
    149 }
    150 
    151 PassRefPtr<HistoryItem> HistoryItem::copy() const
    152 {
    153     return adoptRef(new HistoryItem(*this));
    154 }
    155 
    156 const String& HistoryItem::urlString() const
    157 {
    158     return m_urlString;
    159 }
    160 
    161 // The first URL we loaded to get to where this history item points.  Includes both client
    162 // and server redirects.
    163 const String& HistoryItem::originalURLString() const
    164 {
    165     return m_originalURLString;
    166 }
    167 
    168 const String& HistoryItem::title() const
    169 {
    170     return m_title;
    171 }
    172 
    173 const String& HistoryItem::alternateTitle() const
    174 {
    175     return m_displayTitle;
    176 }
    177 
    178 Image* HistoryItem::icon() const
    179 {
    180     Image* result = iconDatabase()->iconForPageURL(m_urlString, IntSize(16, 16));
    181     return result ? result : iconDatabase()->defaultIcon(IntSize(16, 16));
    182 }
    183 
    184 double HistoryItem::lastVisitedTime() const
    185 {
    186     return m_lastVisitedTime;
    187 }
    188 
    189 KURL HistoryItem::url() const
    190 {
    191     return KURL(ParsedURLString, m_urlString);
    192 }
    193 
    194 KURL HistoryItem::originalURL() const
    195 {
    196     return KURL(ParsedURLString, m_originalURLString);
    197 }
    198 
    199 const String& HistoryItem::referrer() const
    200 {
    201     return m_referrer;
    202 }
    203 
    204 const String& HistoryItem::target() const
    205 {
    206     return m_target;
    207 }
    208 
    209 const String& HistoryItem::parent() const
    210 {
    211     return m_parent;
    212 }
    213 
    214 void HistoryItem::setAlternateTitle(const String& alternateTitle)
    215 {
    216     m_displayTitle = alternateTitle;
    217     notifyHistoryItemChanged(this);
    218 }
    219 
    220 void HistoryItem::setURLString(const String& urlString)
    221 {
    222     if (m_urlString != urlString) {
    223         iconDatabase()->releaseIconForPageURL(m_urlString);
    224         m_urlString = urlString;
    225         iconDatabase()->retainIconForPageURL(m_urlString);
    226     }
    227 
    228     notifyHistoryItemChanged(this);
    229 }
    230 
    231 void HistoryItem::setURL(const KURL& url)
    232 {
    233     pageCache()->remove(this);
    234     setURLString(url.string());
    235     clearDocumentState();
    236 }
    237 
    238 void HistoryItem::setOriginalURLString(const String& urlString)
    239 {
    240     m_originalURLString = urlString;
    241     notifyHistoryItemChanged(this);
    242 }
    243 
    244 void HistoryItem::setReferrer(const String& referrer)
    245 {
    246     m_referrer = referrer;
    247     notifyHistoryItemChanged(this);
    248 }
    249 
    250 void HistoryItem::setTitle(const String& title)
    251 {
    252     m_title = title;
    253     notifyHistoryItemChanged(this);
    254 }
    255 
    256 void HistoryItem::setTarget(const String& target)
    257 {
    258     m_target = target;
    259     notifyHistoryItemChanged(this);
    260 }
    261 
    262 void HistoryItem::setParent(const String& parent)
    263 {
    264     m_parent = parent;
    265 }
    266 
    267 static inline int timeToDay(double time)
    268 {
    269     static const double secondsPerDay = 60 * 60 * 24;
    270     return static_cast<int>(ceil(time / secondsPerDay));
    271 }
    272 
    273 void HistoryItem::padDailyCountsForNewVisit(double time)
    274 {
    275     if (m_dailyVisitCounts.isEmpty())
    276         m_dailyVisitCounts.prepend(m_visitCount);
    277 
    278     int daysElapsed = timeToDay(time) - timeToDay(m_lastVisitedTime);
    279 
    280     if (daysElapsed < 0)
    281       daysElapsed = 0;
    282 
    283     Vector<int> padding;
    284     padding.fill(0, daysElapsed);
    285     m_dailyVisitCounts.prepend(padding);
    286 }
    287 
    288 static const size_t daysPerWeek = 7;
    289 static const size_t maxDailyCounts = 2 * daysPerWeek - 1;
    290 static const size_t maxWeeklyCounts = 5;
    291 
    292 void HistoryItem::collapseDailyVisitsToWeekly()
    293 {
    294     while (m_dailyVisitCounts.size() > maxDailyCounts) {
    295         int oldestWeekTotal = 0;
    296         for (size_t i = 0; i < daysPerWeek; i++)
    297             oldestWeekTotal += m_dailyVisitCounts[m_dailyVisitCounts.size() - daysPerWeek + i];
    298         m_dailyVisitCounts.shrink(m_dailyVisitCounts.size() - daysPerWeek);
    299         m_weeklyVisitCounts.prepend(oldestWeekTotal);
    300     }
    301 
    302     if (m_weeklyVisitCounts.size() > maxWeeklyCounts)
    303         m_weeklyVisitCounts.shrink(maxWeeklyCounts);
    304 }
    305 
    306 void HistoryItem::recordVisitAtTime(double time, VisitCountBehavior visitCountBehavior)
    307 {
    308     padDailyCountsForNewVisit(time);
    309 
    310     m_lastVisitedTime = time;
    311 
    312     if (visitCountBehavior == IncreaseVisitCount) {
    313         ++m_visitCount;
    314         ++m_dailyVisitCounts[0];
    315     }
    316 
    317     collapseDailyVisitsToWeekly();
    318 }
    319 
    320 void HistoryItem::setLastVisitedTime(double time)
    321 {
    322     if (m_lastVisitedTime != time)
    323         recordVisitAtTime(time);
    324 }
    325 
    326 void HistoryItem::visited(const String& title, double time, VisitCountBehavior visitCountBehavior)
    327 {
    328     m_title = title;
    329     recordVisitAtTime(time, visitCountBehavior);
    330 }
    331 
    332 int HistoryItem::visitCount() const
    333 {
    334     return m_visitCount;
    335 }
    336 
    337 void HistoryItem::recordInitialVisit()
    338 {
    339     ASSERT(!m_visitCount);
    340     recordVisitAtTime(m_lastVisitedTime);
    341 }
    342 
    343 void HistoryItem::setVisitCount(int count)
    344 {
    345     m_visitCount = count;
    346 }
    347 
    348 void HistoryItem::adoptVisitCounts(Vector<int>& dailyCounts, Vector<int>& weeklyCounts)
    349 {
    350     m_dailyVisitCounts.clear();
    351     m_dailyVisitCounts.swap(dailyCounts);
    352     m_weeklyVisitCounts.clear();
    353     m_weeklyVisitCounts.swap(weeklyCounts);
    354 }
    355 
    356 const IntPoint& HistoryItem::scrollPoint() const
    357 {
    358     return m_scrollPoint;
    359 }
    360 
    361 void HistoryItem::setScrollPoint(const IntPoint& point)
    362 {
    363     m_scrollPoint = point;
    364 }
    365 
    366 void HistoryItem::clearScrollPoint()
    367 {
    368     m_scrollPoint.setX(0);
    369     m_scrollPoint.setY(0);
    370 }
    371 
    372 void HistoryItem::setDocumentState(const Vector<String>& state)
    373 {
    374     m_documentState = state;
    375 #if PLATFORM(ANDROID)
    376     notifyHistoryItemChanged(this);
    377 #endif
    378 }
    379 
    380 const Vector<String>& HistoryItem::documentState() const
    381 {
    382     return m_documentState;
    383 }
    384 
    385 void HistoryItem::clearDocumentState()
    386 {
    387     m_documentState.clear();
    388 #if PLATFORM(ANDROID)
    389     notifyHistoryItemChanged(this);
    390 #endif
    391 }
    392 
    393 bool HistoryItem::isTargetItem() const
    394 {
    395     return m_isTargetItem;
    396 }
    397 
    398 void HistoryItem::setIsTargetItem(bool flag)
    399 {
    400     m_isTargetItem = flag;
    401 #if PLATFORM(ANDROID)
    402     notifyHistoryItemChanged(this);
    403 #endif
    404 }
    405 
    406 void HistoryItem::setStateObject(PassRefPtr<SerializedScriptValue> object)
    407 {
    408     m_stateObject = object;
    409 }
    410 
    411 void HistoryItem::addChildItem(PassRefPtr<HistoryItem> child)
    412 {
    413     ASSERT(!childItemWithTarget(child->target()));
    414     m_children.append(child);
    415 #if PLATFORM(ANDROID)
    416     notifyHistoryItemChanged(this);
    417 #endif
    418 }
    419 
    420 void HistoryItem::setChildItem(PassRefPtr<HistoryItem> child)
    421 {
    422     ASSERT(!child->isTargetItem());
    423     unsigned size = m_children.size();
    424     for (unsigned i = 0; i < size; ++i)  {
    425         if (m_children[i]->target() == child->target()) {
    426             child->setIsTargetItem(m_children[i]->isTargetItem());
    427             m_children[i] = child;
    428             return;
    429         }
    430     }
    431     m_children.append(child);
    432 }
    433 
    434 HistoryItem* HistoryItem::childItemWithTarget(const String& target) const
    435 {
    436     unsigned size = m_children.size();
    437     for (unsigned i = 0; i < size; ++i) {
    438         if (m_children[i]->target() == target)
    439             return m_children[i].get();
    440     }
    441     return 0;
    442 }
    443 
    444 // <rdar://problem/4895849> HistoryItem::findTargetItem() should be replaced with a non-recursive method.
    445 HistoryItem* HistoryItem::findTargetItem()
    446 {
    447     if (m_isTargetItem)
    448         return this;
    449     unsigned size = m_children.size();
    450     for (unsigned i = 0; i < size; ++i) {
    451         if (HistoryItem* match = m_children[i]->targetItem())
    452             return match;
    453     }
    454     return 0;
    455 }
    456 
    457 HistoryItem* HistoryItem::targetItem()
    458 {
    459     HistoryItem* foundItem = findTargetItem();
    460     return foundItem ? foundItem : this;
    461 }
    462 
    463 const HistoryItemVector& HistoryItem::children() const
    464 {
    465     return m_children;
    466 }
    467 
    468 bool HistoryItem::hasChildren() const
    469 {
    470     return !m_children.isEmpty();
    471 }
    472 
    473 void HistoryItem::clearChildren()
    474 {
    475     m_children.clear();
    476 }
    477 
    478 String HistoryItem::formContentType() const
    479 {
    480     return m_formContentType;
    481 }
    482 
    483 void HistoryItem::setFormInfoFromRequest(const ResourceRequest& request)
    484 {
    485     m_referrer = request.httpReferrer();
    486 
    487     if (equalIgnoringCase(request.httpMethod(), "POST")) {
    488         // FIXME: Eventually we have to make this smart enough to handle the case where
    489         // we have a stream for the body to handle the "data interspersed with files" feature.
    490         m_formData = request.httpBody();
    491         m_formContentType = request.httpContentType();
    492     } else {
    493         m_formData = 0;
    494         m_formContentType = String();
    495     }
    496 #if PLATFORM(ANDROID)
    497     notifyHistoryItemChanged(this);
    498 #endif
    499 }
    500 
    501 void HistoryItem::setFormData(PassRefPtr<FormData> formData)
    502 {
    503     m_formData = formData;
    504 }
    505 
    506 void HistoryItem::setFormContentType(const String& formContentType)
    507 {
    508     m_formContentType = formContentType;
    509 }
    510 
    511 FormData* HistoryItem::formData()
    512 {
    513     return m_formData.get();
    514 }
    515 
    516 bool HistoryItem::isCurrentDocument(Document* doc) const
    517 {
    518     // FIXME: We should find a better way to check if this is the current document.
    519     return equalIgnoringFragmentIdentifier(url(), doc->url());
    520 }
    521 
    522 void HistoryItem::mergeAutoCompleteHints(HistoryItem* otherItem)
    523 {
    524     // FIXME: this is broken - we should be merging the daily counts
    525     // somehow.  but this is to support API that's not really used in
    526     // practice so leave it broken for now.
    527     ASSERT(otherItem);
    528     if (otherItem != this)
    529         m_visitCount += otherItem->m_visitCount;
    530 }
    531 
    532 void HistoryItem::addRedirectURL(const String& url)
    533 {
    534     if (!m_redirectURLs)
    535         m_redirectURLs.set(new Vector<String>);
    536 
    537     // Our API allows us to store all the URLs in the redirect chain, but for
    538     // now we only have a use for the final URL.
    539     (*m_redirectURLs).resize(1);
    540     (*m_redirectURLs)[0] = url;
    541 }
    542 
    543 Vector<String>* HistoryItem::redirectURLs() const
    544 {
    545     return m_redirectURLs.get();
    546 }
    547 
    548 void HistoryItem::setRedirectURLs(PassOwnPtr<Vector<String> > redirectURLs)
    549 {
    550     m_redirectURLs = redirectURLs;
    551 }
    552 
    553 #ifndef NDEBUG
    554 
    555 int HistoryItem::showTree() const
    556 {
    557     return showTreeWithIndent(0);
    558 }
    559 
    560 int HistoryItem::showTreeWithIndent(unsigned indentLevel) const
    561 {
    562     Vector<char> prefix;
    563     for (unsigned i = 0; i < indentLevel; ++i)
    564         prefix.append("  ", 2);
    565     prefix.append("\0", 1);
    566 
    567     fprintf(stderr, "%s+-%s (%p)\n", prefix.data(), m_urlString.utf8().data(), this);
    568 
    569     int totalSubItems = 0;
    570     for (unsigned i = 0; i < m_children.size(); ++i)
    571         totalSubItems += m_children[i]->showTreeWithIndent(indentLevel + 1);
    572     return totalSubItems + 1;
    573 }
    574 
    575 #endif
    576 
    577 } // namespace WebCore
    578 
    579 #ifndef NDEBUG
    580 
    581 int showTree(const WebCore::HistoryItem* item)
    582 {
    583     return item->showTree();
    584 }
    585 
    586 #endif
    587