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