WebKit/win/WebHistory.cpp
changeset 0 4f2f89ce4247
equal deleted inserted replaced
-1:000000000000 0:4f2f89ce4247
       
     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 }