1 /* 2 * Copyright (C) 2006, 2007 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 "WebKitDLL.h" 28 #include "WebHistory.h" 29 30 #include "CFDictionaryPropertyBag.h" 31 #include "MemoryStream.h" 32 #include "WebKit.h" 33 #include "MarshallingHelpers.h" 34 #include "WebHistoryItem.h" 35 #include "WebKit.h" 36 #include "WebNotificationCenter.h" 37 #include "WebPreferences.h" 38 #include <CoreFoundation/CoreFoundation.h> 39 #pragma warning( push, 0 ) 40 #include <WebCore/HistoryItem.h> 41 #include <WebCore/HistoryPropertyList.h> 42 #include <WebCore/KURL.h> 43 #include <WebCore/PageGroup.h> 44 #include <WebCore/SharedBuffer.h> 45 #pragma warning( pop ) 46 #include <functional> 47 #include <wtf/StdLibExtras.h> 48 #include <wtf/Vector.h> 49 50 using namespace WebCore; 51 using namespace std; 52 53 CFStringRef DatesArrayKey = CFSTR("WebHistoryDates"); 54 CFStringRef FileVersionKey = CFSTR("WebHistoryFileVersion"); 55 56 #define currentFileVersion 1 57 58 class WebHistoryWriter : public HistoryPropertyListWriter { 59 public: 60 WebHistoryWriter(const WebHistory::DateToEntriesMap&); 61 62 private: 63 virtual void writeHistoryItems(BinaryPropertyListObjectStream&); 64 65 const WebHistory::DateToEntriesMap& m_entriesByDate; 66 Vector<WebHistory::DateKey> m_dateKeys; 67 }; 68 69 WebHistoryWriter::WebHistoryWriter(const WebHistory::DateToEntriesMap& entriesByDate) 70 : m_entriesByDate(entriesByDate) 71 { 72 copyKeysToVector(m_entriesByDate, m_dateKeys); 73 sort(m_dateKeys.begin(), m_dateKeys.end()); 74 } 75 76 void WebHistoryWriter::writeHistoryItems(BinaryPropertyListObjectStream& stream) 77 { 78 for (int dateIndex = m_dateKeys.size() - 1; dateIndex >= 0; --dateIndex) { 79 // get the entries for that date 80 CFArrayRef entries = m_entriesByDate.get(m_dateKeys[dateIndex]).get(); 81 CFIndex entriesCount = CFArrayGetCount(entries); 82 for (CFIndex j = entriesCount - 1; j >= 0; --j) { 83 IWebHistoryItem* item = (IWebHistoryItem*) CFArrayGetValueAtIndex(entries, j); 84 COMPtr<WebHistoryItem> webItem(Query, item); 85 if (!webItem) 86 continue; 87 88 writeHistoryItem(stream, webItem->historyItem()); 89 } 90 } 91 } 92 93 static bool areEqualOrClose(double d1, double d2) 94 { 95 double diff = d1-d2; 96 return (diff < .000001 && diff > -.000001); 97 } 98 99 static COMPtr<CFDictionaryPropertyBag> createUserInfoFromArray(BSTR notificationStr, CFArrayRef arrayItem) 100 { 101 RetainPtr<CFMutableDictionaryRef> dictionary(AdoptCF, 102 CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); 103 104 RetainPtr<CFStringRef> key(AdoptCF, MarshallingHelpers::BSTRToCFStringRef(notificationStr)); 105 CFDictionaryAddValue(dictionary.get(), key.get(), arrayItem); 106 107 COMPtr<CFDictionaryPropertyBag> result = CFDictionaryPropertyBag::createInstance(); 108 result->setDictionary(dictionary.get()); 109 return result; 110 } 111 112 static COMPtr<CFDictionaryPropertyBag> createUserInfoFromHistoryItem(BSTR notificationStr, IWebHistoryItem* item) 113 { 114 // reference counting of item added to the array is managed by the CFArray value callbacks 115 RetainPtr<CFArrayRef> itemList(AdoptCF, CFArrayCreate(0, (const void**) &item, 1, &MarshallingHelpers::kIUnknownArrayCallBacks)); 116 COMPtr<CFDictionaryPropertyBag> info = createUserInfoFromArray(notificationStr, itemList.get()); 117 return info; 118 } 119 120 // WebHistory ----------------------------------------------------------------- 121 122 WebHistory::WebHistory() 123 : m_refCount(0) 124 , m_preferences(0) 125 { 126 gClassCount++; 127 gClassNameCount.add("WebHistory"); 128 129 m_entriesByURL.adoptCF(CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &MarshallingHelpers::kIUnknownDictionaryValueCallBacks)); 130 131 m_preferences = WebPreferences::sharedStandardPreferences(); 132 } 133 134 WebHistory::~WebHistory() 135 { 136 gClassCount--; 137 gClassNameCount.remove("WebHistory"); 138 } 139 140 WebHistory* WebHistory::createInstance() 141 { 142 WebHistory* instance = new WebHistory(); 143 instance->AddRef(); 144 return instance; 145 } 146 147 HRESULT WebHistory::postNotification(NotificationType notifyType, IPropertyBag* userInfo /*=0*/) 148 { 149 IWebNotificationCenter* nc = WebNotificationCenter::defaultCenterInternal(); 150 HRESULT hr = nc->postNotificationName(getNotificationString(notifyType), static_cast<IWebHistory*>(this), userInfo); 151 if (FAILED(hr)) 152 return hr; 153 154 return S_OK; 155 } 156 157 BSTR WebHistory::getNotificationString(NotificationType notifyType) 158 { 159 static BSTR keys[6] = {0}; 160 if (!keys[0]) { 161 keys[0] = SysAllocString(WebHistoryItemsAddedNotification); 162 keys[1] = SysAllocString(WebHistoryItemsRemovedNotification); 163 keys[2] = SysAllocString(WebHistoryAllItemsRemovedNotification); 164 keys[3] = SysAllocString(WebHistoryLoadedNotification); 165 keys[4] = SysAllocString(WebHistoryItemsDiscardedWhileLoadingNotification); 166 keys[5] = SysAllocString(WebHistorySavedNotification); 167 } 168 return keys[notifyType]; 169 } 170 171 // IUnknown ------------------------------------------------------------------- 172 173 HRESULT STDMETHODCALLTYPE WebHistory::QueryInterface(REFIID riid, void** ppvObject) 174 { 175 *ppvObject = 0; 176 if (IsEqualGUID(riid, CLSID_WebHistory)) 177 *ppvObject = this; 178 else if (IsEqualGUID(riid, IID_IUnknown)) 179 *ppvObject = static_cast<IWebHistory*>(this); 180 else if (IsEqualGUID(riid, IID_IWebHistory)) 181 *ppvObject = static_cast<IWebHistory*>(this); 182 else if (IsEqualGUID(riid, IID_IWebHistoryPrivate)) 183 *ppvObject = static_cast<IWebHistoryPrivate*>(this); 184 else 185 return E_NOINTERFACE; 186 187 AddRef(); 188 return S_OK; 189 } 190 191 ULONG STDMETHODCALLTYPE WebHistory::AddRef(void) 192 { 193 return ++m_refCount; 194 } 195 196 ULONG STDMETHODCALLTYPE WebHistory::Release(void) 197 { 198 ULONG newRef = --m_refCount; 199 if (!newRef) 200 delete(this); 201 202 return newRef; 203 } 204 205 // IWebHistory ---------------------------------------------------------------- 206 207 static inline COMPtr<WebHistory>& sharedHistoryStorage() 208 { 209 DEFINE_STATIC_LOCAL(COMPtr<WebHistory>, sharedHistory, ()); 210 return sharedHistory; 211 } 212 213 WebHistory* WebHistory::sharedHistory() 214 { 215 return sharedHistoryStorage().get(); 216 } 217 218 HRESULT STDMETHODCALLTYPE WebHistory::optionalSharedHistory( 219 /* [retval][out] */ IWebHistory** history) 220 { 221 *history = sharedHistory(); 222 if (*history) 223 (*history)->AddRef(); 224 return S_OK; 225 } 226 227 HRESULT STDMETHODCALLTYPE WebHistory::setOptionalSharedHistory( 228 /* [in] */ IWebHistory* history) 229 { 230 if (sharedHistoryStorage() == history) 231 return S_OK; 232 sharedHistoryStorage().query(history); 233 PageGroup::setShouldTrackVisitedLinks(sharedHistoryStorage()); 234 PageGroup::removeAllVisitedLinks(); 235 return S_OK; 236 } 237 238 HRESULT STDMETHODCALLTYPE WebHistory::loadFromURL( 239 /* [in] */ BSTR url, 240 /* [out] */ IWebError** error, 241 /* [retval][out] */ BOOL* succeeded) 242 { 243 HRESULT hr = S_OK; 244 RetainPtr<CFMutableArrayRef> discardedItems(AdoptCF, 245 CFArrayCreateMutable(0, 0, &MarshallingHelpers::kIUnknownArrayCallBacks)); 246 247 RetainPtr<CFURLRef> urlRef(AdoptCF, MarshallingHelpers::BSTRToCFURLRef(url)); 248 249 hr = loadHistoryGutsFromURL(urlRef.get(), discardedItems.get(), error); 250 if (FAILED(hr)) 251 goto exit; 252 253 hr = postNotification(kWebHistoryLoadedNotification); 254 if (FAILED(hr)) 255 goto exit; 256 257 if (CFArrayGetCount(discardedItems.get()) > 0) { 258 COMPtr<CFDictionaryPropertyBag> userInfo = createUserInfoFromArray(getNotificationString(kWebHistoryItemsDiscardedWhileLoadingNotification), discardedItems.get()); 259 hr = postNotification(kWebHistoryItemsDiscardedWhileLoadingNotification, userInfo.get()); 260 if (FAILED(hr)) 261 goto exit; 262 } 263 264 exit: 265 if (succeeded) 266 *succeeded = SUCCEEDED(hr); 267 return hr; 268 } 269 270 static CFDictionaryRef createHistoryListFromStream(CFReadStreamRef stream, CFPropertyListFormat format) 271 { 272 return (CFDictionaryRef)CFPropertyListCreateFromStream(0, stream, 0, kCFPropertyListImmutable, &format, 0); 273 } 274 275 HRESULT WebHistory::loadHistoryGutsFromURL(CFURLRef url, CFMutableArrayRef discardedItems, IWebError** /*error*/) //FIXME 276 { 277 CFPropertyListFormat format = kCFPropertyListBinaryFormat_v1_0 | kCFPropertyListXMLFormat_v1_0; 278 HRESULT hr = S_OK; 279 int numberOfItemsLoaded = 0; 280 281 RetainPtr<CFReadStreamRef> stream(AdoptCF, CFReadStreamCreateWithFile(0, url)); 282 if (!stream) 283 return E_FAIL; 284 285 if (!CFReadStreamOpen(stream.get())) 286 return E_FAIL; 287 288 RetainPtr<CFDictionaryRef> historyList(AdoptCF, createHistoryListFromStream(stream.get(), format)); 289 CFReadStreamClose(stream.get()); 290 291 if (!historyList) 292 return E_FAIL; 293 294 CFNumberRef fileVersionObject = (CFNumberRef)CFDictionaryGetValue(historyList.get(), FileVersionKey); 295 int fileVersion; 296 if (!CFNumberGetValue(fileVersionObject, kCFNumberIntType, &fileVersion)) 297 return E_FAIL; 298 299 if (fileVersion > currentFileVersion) 300 return E_FAIL; 301 302 CFArrayRef datesArray = (CFArrayRef)CFDictionaryGetValue(historyList.get(), DatesArrayKey); 303 304 int itemCountLimit; 305 hr = historyItemLimit(&itemCountLimit); 306 if (FAILED(hr)) 307 return hr; 308 309 CFAbsoluteTime limitDate; 310 hr = ageLimitDate(&limitDate); 311 if (FAILED(hr)) 312 return hr; 313 314 bool ageLimitPassed = false; 315 bool itemLimitPassed = false; 316 317 CFIndex itemCount = CFArrayGetCount(datesArray); 318 for (CFIndex i = 0; i < itemCount; ++i) { 319 CFDictionaryRef itemAsDictionary = (CFDictionaryRef)CFArrayGetValueAtIndex(datesArray, i); 320 COMPtr<WebHistoryItem> item(AdoptCOM, WebHistoryItem::createInstance()); 321 hr = item->initFromDictionaryRepresentation((void*)itemAsDictionary); 322 if (FAILED(hr)) 323 return hr; 324 325 // item without URL is useless; data on disk must have been bad; ignore 326 BOOL hasURL; 327 hr = item->hasURLString(&hasURL); 328 if (FAILED(hr)) 329 return hr; 330 331 if (hasURL) { 332 // Test against date limit. Since the items are ordered newest to oldest, we can stop comparing 333 // once we've found the first item that's too old. 334 if (!ageLimitPassed) { 335 DATE lastVisitedTime; 336 hr = item->lastVisitedTimeInterval(&lastVisitedTime); 337 if (FAILED(hr)) 338 return hr; 339 if (timeToDate(MarshallingHelpers::DATEToCFAbsoluteTime(lastVisitedTime)) <= limitDate) 340 ageLimitPassed = true; 341 } 342 if (ageLimitPassed || itemLimitPassed) 343 CFArrayAppendValue(discardedItems, item.get()); 344 else { 345 bool added; 346 addItem(item.get(), true, &added); // ref is added inside addItem 347 if (added) 348 ++numberOfItemsLoaded; 349 if (numberOfItemsLoaded == itemCountLimit) 350 itemLimitPassed = true; 351 } 352 } 353 } 354 return hr; 355 } 356 357 HRESULT STDMETHODCALLTYPE WebHistory::saveToURL( 358 /* [in] */ BSTR url, 359 /* [out] */ IWebError** error, 360 /* [retval][out] */ BOOL* succeeded) 361 { 362 HRESULT hr = S_OK; 363 RetainPtr<CFURLRef> urlRef(AdoptCF, MarshallingHelpers::BSTRToCFURLRef(url)); 364 365 hr = saveHistoryGuts(urlRef.get(), error); 366 367 if (succeeded) 368 *succeeded = SUCCEEDED(hr); 369 if (SUCCEEDED(hr)) 370 hr = postNotification(kWebHistorySavedNotification); 371 372 return hr; 373 } 374 375 HRESULT WebHistory::saveHistoryGuts(CFURLRef url, IWebError** error) 376 { 377 HRESULT hr = S_OK; 378 379 // FIXME: Correctly report error when new API is ready. 380 if (error) 381 *error = 0; 382 383 RetainPtr<CFDataRef> data = this->data(); 384 385 RetainPtr<CFWriteStreamRef> stream(AdoptCF, CFWriteStreamCreateWithFile(kCFAllocatorDefault, url)); 386 if (!stream) 387 return E_FAIL; 388 389 if (!CFWriteStreamOpen(stream.get())) 390 return E_FAIL; 391 392 const UInt8* dataPtr = CFDataGetBytePtr(data.get()); 393 CFIndex length = CFDataGetLength(data.get()); 394 395 while (length) { 396 CFIndex bytesWritten = CFWriteStreamWrite(stream.get(), dataPtr, length); 397 if (bytesWritten <= 0) { 398 hr = E_FAIL; 399 break; 400 } 401 dataPtr += bytesWritten; 402 length -= bytesWritten; 403 } 404 405 CFWriteStreamClose(stream.get()); 406 407 return hr; 408 } 409 410 HRESULT STDMETHODCALLTYPE WebHistory::addItems( 411 /* [in] */ int itemCount, 412 /* [in] */ IWebHistoryItem** items) 413 { 414 // There is no guarantee that the incoming entries are in any particular 415 // order, but if this is called with a set of entries that were created by 416 // iterating through the results of orderedLastVisitedDays and orderedItemsLastVisitedOnDay 417 // then they will be ordered chronologically from newest to oldest. We can make adding them 418 // faster (fewer compares) by inserting them from oldest to newest. 419 420 HRESULT hr; 421 for (int i = itemCount - 1; i >= 0; --i) { 422 hr = addItem(items[i], false, 0); 423 if (FAILED(hr)) 424 return hr; 425 } 426 427 return S_OK; 428 } 429 430 HRESULT STDMETHODCALLTYPE WebHistory::removeItems( 431 /* [in] */ int itemCount, 432 /* [in] */ IWebHistoryItem** items) 433 { 434 HRESULT hr; 435 for (int i = 0; i < itemCount; ++i) { 436 hr = removeItem(items[i]); 437 if (FAILED(hr)) 438 return hr; 439 } 440 441 return S_OK; 442 } 443 444 HRESULT STDMETHODCALLTYPE WebHistory::removeAllItems( void) 445 { 446 m_entriesByDate.clear(); 447 m_orderedLastVisitedDays.clear(); 448 449 CFIndex itemCount = CFDictionaryGetCount(m_entriesByURL.get()); 450 Vector<IWebHistoryItem*> itemsVector(itemCount); 451 CFDictionaryGetKeysAndValues(m_entriesByURL.get(), 0, (const void**)itemsVector.data()); 452 RetainPtr<CFArrayRef> allItems(AdoptCF, CFArrayCreate(kCFAllocatorDefault, (const void**)itemsVector.data(), itemCount, &MarshallingHelpers::kIUnknownArrayCallBacks)); 453 454 CFDictionaryRemoveAllValues(m_entriesByURL.get()); 455 456 PageGroup::removeAllVisitedLinks(); 457 458 COMPtr<CFDictionaryPropertyBag> userInfo = createUserInfoFromArray(getNotificationString(kWebHistoryAllItemsRemovedNotification), allItems.get()); 459 return postNotification(kWebHistoryAllItemsRemovedNotification, userInfo.get()); 460 } 461 462 HRESULT STDMETHODCALLTYPE WebHistory::orderedLastVisitedDays( 463 /* [out][in] */ int* count, 464 /* [in] */ DATE* calendarDates) 465 { 466 int dateCount = m_entriesByDate.size(); 467 if (!calendarDates) { 468 *count = dateCount; 469 return S_OK; 470 } 471 472 if (*count < dateCount) { 473 *count = dateCount; 474 return E_FAIL; 475 } 476 477 *count = dateCount; 478 if (!m_orderedLastVisitedDays) { 479 m_orderedLastVisitedDays.set(new DATE[dateCount]); 480 DateToEntriesMap::const_iterator::Keys end = m_entriesByDate.end().keys(); 481 int i = 0; 482 for (DateToEntriesMap::const_iterator::Keys it = m_entriesByDate.begin().keys(); it != end; ++it, ++i) 483 m_orderedLastVisitedDays[i] = MarshallingHelpers::CFAbsoluteTimeToDATE(*it); 484 // Use std::greater to sort the days in descending order (i.e., most-recent first). 485 sort(m_orderedLastVisitedDays.get(), m_orderedLastVisitedDays.get() + dateCount, greater<DATE>()); 486 } 487 488 memcpy(calendarDates, m_orderedLastVisitedDays.get(), dateCount * sizeof(DATE)); 489 return S_OK; 490 } 491 492 HRESULT STDMETHODCALLTYPE WebHistory::orderedItemsLastVisitedOnDay( 493 /* [out][in] */ int* count, 494 /* [in] */ IWebHistoryItem** items, 495 /* [in] */ DATE calendarDate) 496 { 497 DateKey dateKey; 498 if (!findKey(&dateKey, MarshallingHelpers::DATEToCFAbsoluteTime(calendarDate))) { 499 *count = 0; 500 return 0; 501 } 502 503 CFArrayRef entries = m_entriesByDate.get(dateKey).get(); 504 if (!entries) { 505 *count = 0; 506 return 0; 507 } 508 509 int newCount = CFArrayGetCount(entries); 510 511 if (!items) { 512 *count = newCount; 513 return S_OK; 514 } 515 516 if (*count < newCount) { 517 *count = newCount; 518 return E_FAIL; 519 } 520 521 *count = newCount; 522 for (int i = 0; i < newCount; i++) { 523 IWebHistoryItem* item = (IWebHistoryItem*)CFArrayGetValueAtIndex(entries, i); 524 item->AddRef(); 525 items[i] = item; 526 } 527 528 return S_OK; 529 } 530 531 HRESULT STDMETHODCALLTYPE WebHistory::allItems( 532 /* [out][in] */ int* count, 533 /* [out][retval] */ IWebHistoryItem** items) 534 { 535 int entriesByURLCount = CFDictionaryGetCount(m_entriesByURL.get()); 536 537 if (!items) { 538 *count = entriesByURLCount; 539 return S_OK; 540 } 541 542 if (*count < entriesByURLCount) { 543 *count = entriesByURLCount; 544 return E_FAIL; 545 } 546 547 *count = entriesByURLCount; 548 CFDictionaryGetKeysAndValues(m_entriesByURL.get(), 0, (const void**)items); 549 for (int i = 0; i < entriesByURLCount; i++) 550 items[i]->AddRef(); 551 552 return S_OK; 553 } 554 555 HRESULT WebHistory::data(IStream** stream) 556 { 557 if (!stream) 558 return E_POINTER; 559 560 *stream = 0; 561 562 RetainPtr<CFDataRef> historyData = data(); 563 if (!historyData) 564 return S_OK; 565 566 COMPtr<MemoryStream> result = MemoryStream::createInstance(SharedBuffer::wrapCFData(historyData.get())); 567 return result.copyRefTo(stream); 568 } 569 570 HRESULT WebHistory::setVisitedLinkTrackingEnabled(BOOL visitedLinkTrackingEnabled) 571 { 572 PageGroup::setShouldTrackVisitedLinks(visitedLinkTrackingEnabled); 573 return S_OK; 574 } 575 576 HRESULT WebHistory::removeAllVisitedLinks() 577 { 578 PageGroup::removeAllVisitedLinks(); 579 return S_OK; 580 } 581 582 HRESULT STDMETHODCALLTYPE WebHistory::setHistoryItemLimit( 583 /* [in] */ int limit) 584 { 585 if (!m_preferences) 586 return E_FAIL; 587 return m_preferences->setHistoryItemLimit(limit); 588 } 589 590 HRESULT STDMETHODCALLTYPE WebHistory::historyItemLimit( 591 /* [retval][out] */ int* limit) 592 { 593 if (!m_preferences) 594 return E_FAIL; 595 return m_preferences->historyItemLimit(limit); 596 } 597 598 HRESULT STDMETHODCALLTYPE WebHistory::setHistoryAgeInDaysLimit( 599 /* [in] */ int limit) 600 { 601 if (!m_preferences) 602 return E_FAIL; 603 return m_preferences->setHistoryAgeInDaysLimit(limit); 604 } 605 606 HRESULT STDMETHODCALLTYPE WebHistory::historyAgeInDaysLimit( 607 /* [retval][out] */ int* limit) 608 { 609 if (!m_preferences) 610 return E_FAIL; 611 return m_preferences->historyAgeInDaysLimit(limit); 612 } 613 614 HRESULT WebHistory::removeItem(IWebHistoryItem* entry) 615 { 616 HRESULT hr = S_OK; 617 BSTR urlBStr = 0; 618 619 hr = entry->URLString(&urlBStr); 620 if (FAILED(hr)) 621 return hr; 622 623 RetainPtr<CFStringRef> urlString(AdoptCF, MarshallingHelpers::BSTRToCFStringRef(urlBStr)); 624 SysFreeString(urlBStr); 625 626 // If this exact object isn't stored, then make no change. 627 // FIXME: Is this the right behavior if this entry isn't present, but another entry for the same URL is? 628 // Maybe need to change the API to make something like removeEntryForURLString public instead. 629 IWebHistoryItem *matchingEntry = (IWebHistoryItem*)CFDictionaryGetValue(m_entriesByURL.get(), urlString.get()); 630 if (matchingEntry != entry) 631 return E_FAIL; 632 633 hr = removeItemForURLString(urlString.get()); 634 if (FAILED(hr)) 635 return hr; 636 637 COMPtr<CFDictionaryPropertyBag> userInfo = createUserInfoFromHistoryItem( 638 getNotificationString(kWebHistoryItemsRemovedNotification), entry); 639 hr = postNotification(kWebHistoryItemsRemovedNotification, userInfo.get()); 640 641 return hr; 642 } 643 644 HRESULT WebHistory::addItem(IWebHistoryItem* entry, bool discardDuplicate, bool* added) 645 { 646 HRESULT hr = S_OK; 647 648 if (!entry) 649 return E_FAIL; 650 651 BSTR urlBStr = 0; 652 hr = entry->URLString(&urlBStr); 653 if (FAILED(hr)) 654 return hr; 655 656 RetainPtr<CFStringRef> urlString(AdoptCF, MarshallingHelpers::BSTRToCFStringRef(urlBStr)); 657 SysFreeString(urlBStr); 658 659 COMPtr<IWebHistoryItem> oldEntry((IWebHistoryItem*) CFDictionaryGetValue( 660 m_entriesByURL.get(), urlString.get())); 661 662 if (oldEntry) { 663 if (discardDuplicate) { 664 if (added) 665 *added = false; 666 return S_OK; 667 } 668 669 removeItemForURLString(urlString.get()); 670 671 // If we already have an item with this URL, we need to merge info that drives the 672 // URL autocomplete heuristics from that item into the new one. 673 IWebHistoryItemPrivate* entryPriv; 674 hr = entry->QueryInterface(IID_IWebHistoryItemPrivate, (void**)&entryPriv); 675 if (SUCCEEDED(hr)) { 676 entryPriv->mergeAutoCompleteHints(oldEntry.get()); 677 entryPriv->Release(); 678 } 679 } 680 681 hr = addItemToDateCaches(entry); 682 if (FAILED(hr)) 683 return hr; 684 685 CFDictionarySetValue(m_entriesByURL.get(), urlString.get(), entry); 686 687 COMPtr<CFDictionaryPropertyBag> userInfo = createUserInfoFromHistoryItem( 688 getNotificationString(kWebHistoryItemsAddedNotification), entry); 689 hr = postNotification(kWebHistoryItemsAddedNotification, userInfo.get()); 690 691 if (added) 692 *added = true; 693 694 return hr; 695 } 696 697 void WebHistory::visitedURL(const KURL& url, const String& title, const String& httpMethod, bool wasFailure, bool increaseVisitCount) 698 { 699 RetainPtr<CFStringRef> urlString(AdoptCF, url.string().createCFString()); 700 701 IWebHistoryItem* entry = (IWebHistoryItem*) CFDictionaryGetValue(m_entriesByURL.get(), urlString.get()); 702 if (entry) { 703 COMPtr<IWebHistoryItemPrivate> entryPrivate(Query, entry); 704 if (!entryPrivate) 705 return; 706 707 // Remove the item from date caches before changing its last visited date. Otherwise we might get duplicate entries 708 // as seen in <rdar://problem/6570573>. 709 removeItemFromDateCaches(entry); 710 entryPrivate->visitedWithTitle(BString(title), increaseVisitCount); 711 } else { 712 COMPtr<WebHistoryItem> item(AdoptCOM, WebHistoryItem::createInstance()); 713 if (!item) 714 return; 715 716 entry = item.get(); 717 718 SYSTEMTIME currentTime; 719 GetSystemTime(¤tTime); 720 DATE lastVisited; 721 if (!SystemTimeToVariantTime(¤tTime, &lastVisited)) 722 return; 723 724 if (FAILED(entry->initWithURLString(BString(url.string()), BString(title), lastVisited))) 725 return; 726 727 item->recordInitialVisit(); 728 729 CFDictionarySetValue(m_entriesByURL.get(), urlString.get(), entry); 730 } 731 732 addItemToDateCaches(entry); 733 734 COMPtr<IWebHistoryItemPrivate> entryPrivate(Query, entry); 735 if (!entryPrivate) 736 return; 737 738 entryPrivate->setLastVisitWasFailure(wasFailure); 739 if (!httpMethod.isEmpty()) 740 entryPrivate->setLastVisitWasHTTPNonGet(!equalIgnoringCase(httpMethod, "GET") && url.protocolInHTTPFamily()); 741 742 COMPtr<WebHistoryItem> item(Query, entry); 743 item->historyItem()->setRedirectURLs(0); 744 745 COMPtr<CFDictionaryPropertyBag> userInfo = createUserInfoFromHistoryItem( 746 getNotificationString(kWebHistoryItemsAddedNotification), entry); 747 postNotification(kWebHistoryItemsAddedNotification, userInfo.get()); 748 } 749 750 HRESULT WebHistory::itemForURLString( 751 /* [in] */ CFStringRef urlString, 752 /* [retval][out] */ IWebHistoryItem** item) const 753 { 754 if (!item) 755 return E_FAIL; 756 *item = 0; 757 758 IWebHistoryItem* foundItem = (IWebHistoryItem*) CFDictionaryGetValue(m_entriesByURL.get(), urlString); 759 if (!foundItem) 760 return E_FAIL; 761 762 foundItem->AddRef(); 763 *item = foundItem; 764 return S_OK; 765 } 766 767 HRESULT STDMETHODCALLTYPE WebHistory::itemForURL( 768 /* [in] */ BSTR url, 769 /* [retval][out] */ IWebHistoryItem** item) 770 { 771 RetainPtr<CFStringRef> urlString(AdoptCF, MarshallingHelpers::BSTRToCFStringRef(url)); 772 return itemForURLString(urlString.get(), item); 773 } 774 775 HRESULT WebHistory::removeItemForURLString(CFStringRef urlString) 776 { 777 IWebHistoryItem* entry = (IWebHistoryItem*) CFDictionaryGetValue(m_entriesByURL.get(), urlString); 778 if (!entry) 779 return E_FAIL; 780 781 HRESULT hr = removeItemFromDateCaches(entry); 782 CFDictionaryRemoveValue(m_entriesByURL.get(), urlString); 783 784 if (!CFDictionaryGetCount(m_entriesByURL.get())) 785 PageGroup::removeAllVisitedLinks(); 786 787 return hr; 788 } 789 790 COMPtr<IWebHistoryItem> WebHistory::itemForURLString(const String& urlString) const 791 { 792 RetainPtr<CFStringRef> urlCFString(AdoptCF, urlString.createCFString()); 793 if (!urlCFString) 794 return 0; 795 COMPtr<IWebHistoryItem> item; 796 if (FAILED(itemForURLString(urlCFString.get(), &item))) 797 return 0; 798 return item; 799 } 800 801 HRESULT WebHistory::addItemToDateCaches(IWebHistoryItem* entry) 802 { 803 HRESULT hr = S_OK; 804 805 DATE lastVisitedCOMTime; 806 entry->lastVisitedTimeInterval(&lastVisitedCOMTime); 807 808 DateKey dateKey; 809 if (findKey(&dateKey, MarshallingHelpers::DATEToCFAbsoluteTime(lastVisitedCOMTime))) { 810 // other entries already exist for this date 811 hr = insertItem(entry, dateKey); 812 } else { 813 ASSERT(!m_entriesByDate.contains(dateKey)); 814 // no other entries exist for this date 815 RetainPtr<CFMutableArrayRef> entryArray(AdoptCF, 816 CFArrayCreateMutable(0, 0, &MarshallingHelpers::kIUnknownArrayCallBacks)); 817 CFArrayAppendValue(entryArray.get(), entry); 818 m_entriesByDate.set(dateKey, entryArray); 819 // Clear m_orderedLastVisitedDays so it will be regenerated when next requested. 820 m_orderedLastVisitedDays.clear(); 821 } 822 823 return hr; 824 } 825 826 HRESULT WebHistory::removeItemFromDateCaches(IWebHistoryItem* entry) 827 { 828 HRESULT hr = S_OK; 829 830 DATE lastVisitedCOMTime; 831 entry->lastVisitedTimeInterval(&lastVisitedCOMTime); 832 833 DateKey dateKey; 834 if (!findKey(&dateKey, MarshallingHelpers::DATEToCFAbsoluteTime(lastVisitedCOMTime))) 835 return E_FAIL; 836 837 DateToEntriesMap::iterator found = m_entriesByDate.find(dateKey); 838 ASSERT(found != m_entriesByDate.end()); 839 CFMutableArrayRef entriesForDate = found->second.get(); 840 841 CFIndex count = CFArrayGetCount(entriesForDate); 842 for (int i = count - 1; i >= 0; --i) { 843 if ((IWebHistoryItem*)CFArrayGetValueAtIndex(entriesForDate, i) == entry) 844 CFArrayRemoveValueAtIndex(entriesForDate, i); 845 } 846 847 // remove this date entirely if there are no other entries on it 848 if (CFArrayGetCount(entriesForDate) == 0) { 849 m_entriesByDate.remove(found); 850 // Clear m_orderedLastVisitedDays so it will be regenerated when next requested. 851 m_orderedLastVisitedDays.clear(); 852 } 853 854 return hr; 855 } 856 857 static void getDayBoundaries(CFAbsoluteTime day, CFAbsoluteTime& beginningOfDay, CFAbsoluteTime& beginningOfNextDay) 858 { 859 RetainPtr<CFTimeZoneRef> timeZone(AdoptCF, CFTimeZoneCopyDefault()); 860 CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(day, timeZone.get()); 861 date.hour = 0; 862 date.minute = 0; 863 date.second = 0; 864 beginningOfDay = CFGregorianDateGetAbsoluteTime(date, timeZone.get()); 865 date.day += 1; 866 beginningOfNextDay = CFGregorianDateGetAbsoluteTime(date, timeZone.get()); 867 } 868 869 static inline CFAbsoluteTime beginningOfDay(CFAbsoluteTime date) 870 { 871 static CFAbsoluteTime cachedBeginningOfDay = numeric_limits<CFAbsoluteTime>::quiet_NaN(); 872 static CFAbsoluteTime cachedBeginningOfNextDay; 873 if (!(date >= cachedBeginningOfDay && date < cachedBeginningOfNextDay)) 874 getDayBoundaries(date, cachedBeginningOfDay, cachedBeginningOfNextDay); 875 return cachedBeginningOfDay; 876 } 877 878 static inline WebHistory::DateKey dateKey(CFAbsoluteTime date) 879 { 880 // Converting from double (CFAbsoluteTime) to int64_t (WebHistoryDateKey) is 881 // safe here because all sensible dates are in the range -2**48 .. 2**47 which 882 // safely fits in an int64_t. 883 return beginningOfDay(date); 884 } 885 886 // Returns whether the day is already in the list of days, 887 // and fills in *key with the found or proposed key. 888 bool WebHistory::findKey(DateKey* key, CFAbsoluteTime forDay) 889 { 890 ASSERT_ARG(key, key); 891 892 *key = dateKey(forDay); 893 return m_entriesByDate.contains(*key); 894 } 895 896 HRESULT WebHistory::insertItem(IWebHistoryItem* entry, DateKey dateKey) 897 { 898 ASSERT_ARG(entry, entry); 899 ASSERT_ARG(dateKey, m_entriesByDate.contains(dateKey)); 900 901 HRESULT hr = S_OK; 902 903 if (!entry) 904 return E_FAIL; 905 906 DATE entryTime; 907 entry->lastVisitedTimeInterval(&entryTime); 908 CFMutableArrayRef entriesForDate = m_entriesByDate.get(dateKey).get(); 909 unsigned count = CFArrayGetCount(entriesForDate); 910 911 // The entries for each day are stored in a sorted array with the most recent entry first 912 // Check for the common cases of the entry being newer than all existing entries or the first entry of the day 913 bool isNewerThanAllEntries = false; 914 if (count) { 915 IWebHistoryItem* item = const_cast<IWebHistoryItem*>(static_cast<const IWebHistoryItem*>(CFArrayGetValueAtIndex(entriesForDate, 0))); 916 DATE itemTime; 917 isNewerThanAllEntries = SUCCEEDED(item->lastVisitedTimeInterval(&itemTime)) && itemTime < entryTime; 918 } 919 if (!count || isNewerThanAllEntries) { 920 CFArrayInsertValueAtIndex(entriesForDate, 0, entry); 921 return S_OK; 922 } 923 924 // .. or older than all existing entries 925 bool isOlderThanAllEntries = false; 926 if (count > 0) { 927 IWebHistoryItem* item = const_cast<IWebHistoryItem*>(static_cast<const IWebHistoryItem*>(CFArrayGetValueAtIndex(entriesForDate, count - 1))); 928 DATE itemTime; 929 isOlderThanAllEntries = SUCCEEDED(item->lastVisitedTimeInterval(&itemTime)) && itemTime >= entryTime; 930 } 931 if (isOlderThanAllEntries) { 932 CFArrayInsertValueAtIndex(entriesForDate, count, entry); 933 return S_OK; 934 } 935 936 unsigned low = 0; 937 unsigned high = count; 938 while (low < high) { 939 unsigned mid = low + (high - low) / 2; 940 IWebHistoryItem* item = const_cast<IWebHistoryItem*>(static_cast<const IWebHistoryItem*>(CFArrayGetValueAtIndex(entriesForDate, mid))); 941 DATE itemTime; 942 if (FAILED(item->lastVisitedTimeInterval(&itemTime))) 943 return E_FAIL; 944 945 if (itemTime >= entryTime) 946 low = mid + 1; 947 else 948 high = mid; 949 } 950 951 // low is now the index of the first entry that is older than entryDate 952 CFArrayInsertValueAtIndex(entriesForDate, low, entry); 953 return S_OK; 954 } 955 956 CFAbsoluteTime WebHistory::timeToDate(CFAbsoluteTime time) 957 { 958 // can't just divide/round since the day boundaries depend on our current time zone 959 const double secondsPerDay = 60 * 60 * 24; 960 CFTimeZoneRef timeZone = CFTimeZoneCopySystem(); 961 CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(time, timeZone); 962 date.hour = date.minute = 0; 963 date.second = 0.0; 964 CFAbsoluteTime timeInDays = CFGregorianDateGetAbsoluteTime(date, timeZone); 965 if (areEqualOrClose(time - timeInDays, secondsPerDay)) 966 timeInDays += secondsPerDay; 967 return timeInDays; 968 } 969 970 // Return a date that marks the age limit for history entries saved to or 971 // loaded from disk. Any entry older than this item should be rejected. 972 HRESULT WebHistory::ageLimitDate(CFAbsoluteTime* time) 973 { 974 // get the current date as a CFAbsoluteTime 975 CFAbsoluteTime currentDate = timeToDate(CFAbsoluteTimeGetCurrent()); 976 977 CFGregorianUnits ageLimit = {0}; 978 int historyLimitDays; 979 HRESULT hr = historyAgeInDaysLimit(&historyLimitDays); 980 if (FAILED(hr)) 981 return hr; 982 ageLimit.days = -historyLimitDays; 983 *time = CFAbsoluteTimeAddGregorianUnits(currentDate, CFTimeZoneCopySystem(), ageLimit); 984 return S_OK; 985 } 986 987 static void addVisitedLinkToPageGroup(const void* key, const void*, void* context) 988 { 989 CFStringRef url = static_cast<CFStringRef>(key); 990 PageGroup* group = static_cast<PageGroup*>(context); 991 992 CFIndex length = CFStringGetLength(url); 993 const UChar* characters = reinterpret_cast<const UChar*>(CFStringGetCharactersPtr(url)); 994 if (characters) 995 group->addVisitedLink(characters, length); 996 else { 997 Vector<UChar, 512> buffer(length); 998 CFStringGetCharacters(url, CFRangeMake(0, length), reinterpret_cast<UniChar*>(buffer.data())); 999 group->addVisitedLink(buffer.data(), length); 1000 } 1001 } 1002 1003 void WebHistory::addVisitedLinksToPageGroup(PageGroup& group) 1004 { 1005 CFDictionaryApplyFunction(m_entriesByURL.get(), addVisitedLinkToPageGroup, &group); 1006 } 1007 1008 RetainPtr<CFDataRef> WebHistory::data() const 1009 { 1010 if (m_entriesByDate.isEmpty()) 1011 return 0; 1012 1013 WebHistoryWriter writer(m_entriesByDate); 1014 writer.writePropertyList(); 1015 return writer.releaseData(); 1016 } 1017