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