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
generateDocumentSequenceNumber()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
defaultNotifyHistoryItemChanged(HistoryItem *)48 static void defaultNotifyHistoryItemChanged(HistoryItem*)
49 {
50 }
51
52 void (*notifyHistoryItemChanged)(HistoryItem*) = defaultNotifyHistoryItemChanged;
53
HistoryItem()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
HistoryItem(const String & urlString,const String & title,double time)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
HistoryItem(const String & urlString,const String & title,const String & alternateTitle,double time)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
HistoryItem(const KURL & url,const String & target,const String & parent,const String & title)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
~HistoryItem()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
HistoryItem(const HistoryItem & item)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
copy() const151 PassRefPtr<HistoryItem> HistoryItem::copy() const
152 {
153 return adoptRef(new HistoryItem(*this));
154 }
155
urlString() const156 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.
originalURLString() const163 const String& HistoryItem::originalURLString() const
164 {
165 return m_originalURLString;
166 }
167
title() const168 const String& HistoryItem::title() const
169 {
170 return m_title;
171 }
172
alternateTitle() const173 const String& HistoryItem::alternateTitle() const
174 {
175 return m_displayTitle;
176 }
177
icon() const178 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
lastVisitedTime() const184 double HistoryItem::lastVisitedTime() const
185 {
186 return m_lastVisitedTime;
187 }
188
url() const189 KURL HistoryItem::url() const
190 {
191 return KURL(ParsedURLString, m_urlString);
192 }
193
originalURL() const194 KURL HistoryItem::originalURL() const
195 {
196 return KURL(ParsedURLString, m_originalURLString);
197 }
198
referrer() const199 const String& HistoryItem::referrer() const
200 {
201 return m_referrer;
202 }
203
target() const204 const String& HistoryItem::target() const
205 {
206 return m_target;
207 }
208
parent() const209 const String& HistoryItem::parent() const
210 {
211 return m_parent;
212 }
213
setAlternateTitle(const String & alternateTitle)214 void HistoryItem::setAlternateTitle(const String& alternateTitle)
215 {
216 m_displayTitle = alternateTitle;
217 notifyHistoryItemChanged(this);
218 }
219
setURLString(const String & urlString)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
setURL(const KURL & url)231 void HistoryItem::setURL(const KURL& url)
232 {
233 pageCache()->remove(this);
234 setURLString(url.string());
235 clearDocumentState();
236 }
237
setOriginalURLString(const String & urlString)238 void HistoryItem::setOriginalURLString(const String& urlString)
239 {
240 m_originalURLString = urlString;
241 notifyHistoryItemChanged(this);
242 }
243
setReferrer(const String & referrer)244 void HistoryItem::setReferrer(const String& referrer)
245 {
246 m_referrer = referrer;
247 notifyHistoryItemChanged(this);
248 }
249
setTitle(const String & title)250 void HistoryItem::setTitle(const String& title)
251 {
252 m_title = title;
253 notifyHistoryItemChanged(this);
254 }
255
setTarget(const String & target)256 void HistoryItem::setTarget(const String& target)
257 {
258 m_target = target;
259 notifyHistoryItemChanged(this);
260 }
261
setParent(const String & parent)262 void HistoryItem::setParent(const String& parent)
263 {
264 m_parent = parent;
265 }
266
timeToDay(double time)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
padDailyCountsForNewVisit(double time)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
collapseDailyVisitsToWeekly()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
recordVisitAtTime(double time,VisitCountBehavior visitCountBehavior)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
setLastVisitedTime(double time)320 void HistoryItem::setLastVisitedTime(double time)
321 {
322 if (m_lastVisitedTime != time)
323 recordVisitAtTime(time);
324 }
325
visited(const String & title,double time,VisitCountBehavior visitCountBehavior)326 void HistoryItem::visited(const String& title, double time, VisitCountBehavior visitCountBehavior)
327 {
328 m_title = title;
329 recordVisitAtTime(time, visitCountBehavior);
330 }
331
visitCount() const332 int HistoryItem::visitCount() const
333 {
334 return m_visitCount;
335 }
336
recordInitialVisit()337 void HistoryItem::recordInitialVisit()
338 {
339 ASSERT(!m_visitCount);
340 recordVisitAtTime(m_lastVisitedTime);
341 }
342
setVisitCount(int count)343 void HistoryItem::setVisitCount(int count)
344 {
345 m_visitCount = count;
346 }
347
adoptVisitCounts(Vector<int> & dailyCounts,Vector<int> & weeklyCounts)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
scrollPoint() const356 const IntPoint& HistoryItem::scrollPoint() const
357 {
358 return m_scrollPoint;
359 }
360
setScrollPoint(const IntPoint & point)361 void HistoryItem::setScrollPoint(const IntPoint& point)
362 {
363 m_scrollPoint = point;
364 }
365
clearScrollPoint()366 void HistoryItem::clearScrollPoint()
367 {
368 m_scrollPoint.setX(0);
369 m_scrollPoint.setY(0);
370 }
371
setDocumentState(const Vector<String> & state)372 void HistoryItem::setDocumentState(const Vector<String>& state)
373 {
374 m_documentState = state;
375 #if PLATFORM(ANDROID)
376 notifyHistoryItemChanged(this);
377 #endif
378 }
379
documentState() const380 const Vector<String>& HistoryItem::documentState() const
381 {
382 return m_documentState;
383 }
384
clearDocumentState()385 void HistoryItem::clearDocumentState()
386 {
387 m_documentState.clear();
388 #if PLATFORM(ANDROID)
389 notifyHistoryItemChanged(this);
390 #endif
391 }
392
isTargetItem() const393 bool HistoryItem::isTargetItem() const
394 {
395 return m_isTargetItem;
396 }
397
setIsTargetItem(bool flag)398 void HistoryItem::setIsTargetItem(bool flag)
399 {
400 m_isTargetItem = flag;
401 #if PLATFORM(ANDROID)
402 notifyHistoryItemChanged(this);
403 #endif
404 }
405
setStateObject(PassRefPtr<SerializedScriptValue> object)406 void HistoryItem::setStateObject(PassRefPtr<SerializedScriptValue> object)
407 {
408 m_stateObject = object;
409 }
410
addChildItem(PassRefPtr<HistoryItem> child)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
setChildItem(PassRefPtr<HistoryItem> child)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
childItemWithTarget(const String & target) const434 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.
findTargetItem()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
targetItem()457 HistoryItem* HistoryItem::targetItem()
458 {
459 HistoryItem* foundItem = findTargetItem();
460 return foundItem ? foundItem : this;
461 }
462
children() const463 const HistoryItemVector& HistoryItem::children() const
464 {
465 return m_children;
466 }
467
hasChildren() const468 bool HistoryItem::hasChildren() const
469 {
470 return !m_children.isEmpty();
471 }
472
clearChildren()473 void HistoryItem::clearChildren()
474 {
475 m_children.clear();
476 }
477
formContentType() const478 String HistoryItem::formContentType() const
479 {
480 return m_formContentType;
481 }
482
setFormInfoFromRequest(const ResourceRequest & request)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
setFormData(PassRefPtr<FormData> formData)501 void HistoryItem::setFormData(PassRefPtr<FormData> formData)
502 {
503 m_formData = formData;
504 }
505
setFormContentType(const String & formContentType)506 void HistoryItem::setFormContentType(const String& formContentType)
507 {
508 m_formContentType = formContentType;
509 }
510
formData()511 FormData* HistoryItem::formData()
512 {
513 return m_formData.get();
514 }
515
isCurrentDocument(Document * doc) const516 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
mergeAutoCompleteHints(HistoryItem * otherItem)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
addRedirectURL(const String & url)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
redirectURLs() const543 Vector<String>* HistoryItem::redirectURLs() const
544 {
545 return m_redirectURLs.get();
546 }
547
setRedirectURLs(PassOwnPtr<Vector<String>> redirectURLs)548 void HistoryItem::setRedirectURLs(PassOwnPtr<Vector<String> > redirectURLs)
549 {
550 m_redirectURLs = redirectURLs;
551 }
552
553 #ifndef NDEBUG
554
showTree() const555 int HistoryItem::showTree() const
556 {
557 return showTreeWithIndent(0);
558 }
559
showTreeWithIndent(unsigned indentLevel) const560 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
showTree(const WebCore::HistoryItem * item)581 int showTree(const WebCore::HistoryItem* item)
582 {
583 return item->showTree();
584 }
585
586 #endif
587