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