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