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