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