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 #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(&currentTime);
    720         DATE lastVisited;
    721         if (!SystemTimeToVariantTime(&currentTime, &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