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