diff -r 000000000000 -r dd21522fd290 webengine/osswebengine/WebKit/win/WebHistory.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/webengine/osswebengine/WebKit/win/WebHistory.cpp Mon Mar 30 12:54:55 2009 +0300 @@ -0,0 +1,1019 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "WebKitDLL.h" +#include "WebHistory.h" + +#include "CFDictionaryPropertyBag.h" +#include "IWebURLResponse.h" +#include "MarshallingHelpers.h" +#include "WebHistoryItem.h" +#include "WebKit.h" +#include "WebNotificationCenter.h" +#include "WebPreferences.h" +#include +#include +#pragma warning( push, 0 ) +#include +#pragma warning( pop ) + +CFStringRef DatesArrayKey = CFSTR("WebHistoryDates"); +CFStringRef FileVersionKey = CFSTR("WebHistoryFileVersion"); + +const IID IID_IWebHistoryPrivate = { 0x3de04e59, 0x93f9, 0x4369, { 0x8b, 0x43, 0x97, 0x64, 0x58, 0xd7, 0xe3, 0x19 } }; + +#define currentFileVersion 1 + +class _WebCoreHistoryProvider : public WebCore::WebCoreHistoryProvider { +public: + _WebCoreHistoryProvider(IWebHistory* history); + ~_WebCoreHistoryProvider(); + + virtual bool containsItemForURLLatin1(const char* latin1, unsigned int length); + virtual bool containsItemForURLUnicode(const UChar* unicode, unsigned int length); + +private: + IWebHistory* m_history; + IWebHistoryPrivate* m_historyPrivate; +}; + +static bool areEqualOrClose(double d1, double d2) +{ + double diff = d1-d2; + return (diff < .000001 && diff > -.000001); +} + +static CFDictionaryPropertyBag* createUserInfoFromArray(BSTR notificationStr, CFArrayRef arrayItem) +{ + RetainPtr dictionary(AdoptCF, + CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); + + RetainPtr key(AdoptCF, MarshallingHelpers::BSTRToCFStringRef(notificationStr)); + CFDictionaryAddValue(dictionary.get(), key.get(), arrayItem); + + CFDictionaryPropertyBag* result = CFDictionaryPropertyBag::createInstance(); + result->setDictionary(dictionary.get()); + return result; +} + +static CFDictionaryPropertyBag* createUserInfoFromHistoryItem(BSTR notificationStr, IWebHistoryItem* item) +{ + // reference counting of item added to the array is managed by the CFArray value callbacks + RetainPtr itemList(AdoptCF, CFArrayCreate(0, (const void**) &item, 1, &MarshallingHelpers::kIUnknownArrayCallBacks)); + CFDictionaryPropertyBag* info = createUserInfoFromArray(notificationStr, itemList.get()); + return info; +} + +static void releaseUserInfo(CFDictionaryPropertyBag* userInfo) +{ + // free the dictionary + userInfo->setDictionary(0); + int result = userInfo->Release(); + (void)result; + ASSERT(result == 0); // make sure no one else holds a reference to the userInfo. +} + +// WebHistory ----------------------------------------------------------------- + +IWebHistory* WebHistory::m_optionalSharedHistory = 0; + +WebHistory::WebHistory() +: m_refCount(0) +, m_preferences(0) +{ + gClassCount++; + + m_entriesByURL.adoptCF(CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &MarshallingHelpers::kIUnknownDictionaryValueCallBacks)); + m_datesWithEntries.adoptCF(CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks)); + m_entriesByDate.adoptCF(CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks)); + + m_preferences = WebPreferences::sharedStandardPreferences(); +} + +WebHistory::~WebHistory() +{ + gClassCount--; +} + +WebHistory* WebHistory::createInstance() +{ + WebHistory* instance = new WebHistory(); + instance->AddRef(); + return instance; +} + +HRESULT WebHistory::postNotification(NotificationType notifyType, IPropertyBag* userInfo /*=0*/) +{ + IWebNotificationCenter* nc = WebNotificationCenter::defaultCenterInternal(); + HRESULT hr = nc->postNotificationName(getNotificationString(notifyType), static_cast(this), userInfo); + if (FAILED(hr)) + return hr; + + return S_OK; +} + +BSTR WebHistory::getNotificationString(NotificationType notifyType) +{ + static BSTR keys[6] = {0}; + if (!keys[0]) { + keys[0] = SysAllocString(WebHistoryItemsAddedNotification); + keys[1] = SysAllocString(WebHistoryItemsRemovedNotification); + keys[2] = SysAllocString(WebHistoryAllItemsRemovedNotification); + keys[3] = SysAllocString(WebHistoryLoadedNotification); + keys[4] = SysAllocString(WebHistoryItemsDiscardedWhileLoadingNotification); + keys[5] = SysAllocString(WebHistorySavedNotification); + } + return keys[notifyType]; +} + +// IUnknown ------------------------------------------------------------------- + +HRESULT STDMETHODCALLTYPE WebHistory::QueryInterface(REFIID riid, void** ppvObject) +{ + *ppvObject = 0; + if (IsEqualGUID(riid, CLSID_WebHistory)) + *ppvObject = this; + else if (IsEqualGUID(riid, IID_IUnknown)) + *ppvObject = static_cast(this); + else if (IsEqualGUID(riid, IID_IWebHistory)) + *ppvObject = static_cast(this); + else if (IsEqualGUID(riid, IID_IWebHistoryPrivate)) + *ppvObject = static_cast(this); + else + return E_NOINTERFACE; + + AddRef(); + return S_OK; +} + +ULONG STDMETHODCALLTYPE WebHistory::AddRef(void) +{ + return ++m_refCount; +} + +ULONG STDMETHODCALLTYPE WebHistory::Release(void) +{ + ULONG newRef = --m_refCount; + if (!newRef) + delete(this); + + return newRef; +} + +// IWebHistory ---------------------------------------------------------------- + +HRESULT STDMETHODCALLTYPE WebHistory::optionalSharedHistory( + /* [retval][out] */ IWebHistory** history) +{ + *history = m_optionalSharedHistory; + if (m_optionalSharedHistory) + m_optionalSharedHistory->AddRef(); + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE WebHistory::setOptionalSharedHistory( + /* [in] */ IWebHistory* history) +{ + if (m_optionalSharedHistory) { + m_optionalSharedHistory->Release(); + m_optionalSharedHistory = 0; + } + + _WebCoreHistoryProvider* coreHistory = 0; + m_optionalSharedHistory = history; + if (history) { + history->AddRef(); + coreHistory = new _WebCoreHistoryProvider(history); + } + WebCore::WebCoreHistory::setHistoryProvider(coreHistory); + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE WebHistory::loadFromURL( + /* [in] */ BSTR url, + /* [out] */ IWebError** error, + /* [retval][out] */ BOOL* succeeded) +{ + HRESULT hr = S_OK; + RetainPtr discardedItems(AdoptCF, + CFArrayCreateMutable(0, 0, &MarshallingHelpers::kIUnknownArrayCallBacks)); + + RetainPtr urlRef(AdoptCF, MarshallingHelpers::BSTRToCFURLRef(url)); + + hr = loadHistoryGutsFromURL(urlRef.get(), discardedItems.get(), error); + if (FAILED(hr)) + goto exit; + + hr = postNotification(kWebHistoryLoadedNotification); + if (FAILED(hr)) + goto exit; + + if (CFArrayGetCount(discardedItems.get()) > 0) { + CFDictionaryPropertyBag* userInfo = createUserInfoFromArray(getNotificationString(kWebHistoryItemsDiscardedWhileLoadingNotification), discardedItems.get()); + hr = postNotification(kWebHistoryItemsDiscardedWhileLoadingNotification, userInfo); + releaseUserInfo(userInfo); + if (FAILED(hr)) + goto exit; + } + +exit: + if (succeeded) + *succeeded = SUCCEEDED(hr); + return hr; +} + +static CFDictionaryRef createHistoryListFromStream(CFReadStreamRef stream, CFPropertyListFormat format) +{ + return (CFDictionaryRef)CFPropertyListCreateFromStream(0, stream, 0, kCFPropertyListImmutable, &format, 0); +} + +HRESULT WebHistory::loadHistoryGutsFromURL(CFURLRef url, CFMutableArrayRef discardedItems, IWebError** /*error*/) //FIXME +{ + CFPropertyListFormat format = kCFPropertyListBinaryFormat_v1_0 | kCFPropertyListXMLFormat_v1_0; + HRESULT hr = S_OK; + int numberOfItemsLoaded = 0; + + RetainPtr stream(AdoptCF, CFReadStreamCreateWithFile(0, url)); + if (!stream) + return E_FAIL; + + if (!CFReadStreamOpen(stream.get())) + return E_FAIL; + + RetainPtr historyList(AdoptCF, createHistoryListFromStream(stream.get(), format)); + CFReadStreamClose(stream.get()); + + if (!historyList) + return E_FAIL; + + CFNumberRef fileVersionObject = (CFNumberRef)CFDictionaryGetValue(historyList.get(), FileVersionKey); + int fileVersion; + if (!CFNumberGetValue(fileVersionObject, kCFNumberIntType, &fileVersion)) + return E_FAIL; + + if (fileVersion > currentFileVersion) + return E_FAIL; + + CFArrayRef datesArray = (CFArrayRef)CFDictionaryGetValue(historyList.get(), DatesArrayKey); + + int itemCountLimit; + hr = historyItemLimit(&itemCountLimit); + if (FAILED(hr)) + return hr; + + CFAbsoluteTime limitDate; + hr = ageLimitDate(&limitDate); + if (FAILED(hr)) + return hr; + + bool ageLimitPassed = false; + bool itemLimitPassed = false; + + CFIndex itemCount = CFArrayGetCount(datesArray); + for (CFIndex i = 0; i < itemCount; ++i) { + CFDictionaryRef itemAsDictionary = (CFDictionaryRef)CFArrayGetValueAtIndex(datesArray, i); + COMPtr item(AdoptCOM, WebHistoryItem::createInstance()); + hr = item->initFromDictionaryRepresentation((void*)itemAsDictionary); + if (FAILED(hr)) + return hr; + + // item without URL is useless; data on disk must have been bad; ignore + BOOL hasURL; + hr = item->hasURLString(&hasURL); + if (FAILED(hr)) + return hr; + + if (hasURL) { + // Test against date limit. Since the items are ordered newest to oldest, we can stop comparing + // once we've found the first item that's too old. + if (!ageLimitPassed) { + DATE lastVisitedTime; + hr = item->lastVisitedTimeInterval(&lastVisitedTime); + if (FAILED(hr)) + return hr; + if (timeToDate(MarshallingHelpers::DATEToCFAbsoluteTime(lastVisitedTime)) <= limitDate) + ageLimitPassed = true; + } + if (ageLimitPassed || itemLimitPassed) + CFArrayAppendValue(discardedItems, item.get()); + else { + addItem(item.get()); // ref is added inside addItem + ++numberOfItemsLoaded; + if (numberOfItemsLoaded == itemCountLimit) + itemLimitPassed = true; + } + } + } + return hr; +} + +HRESULT STDMETHODCALLTYPE WebHistory::saveToURL( + /* [in] */ BSTR url, + /* [out] */ IWebError** error, + /* [retval][out] */ BOOL* succeeded) +{ + HRESULT hr = S_OK; + RetainPtr urlRef(AdoptCF, MarshallingHelpers::BSTRToCFURLRef(url)); + + hr = saveHistoryGuts(urlRef.get(), error); + + if (succeeded) + *succeeded = SUCCEEDED(hr); + if (SUCCEEDED(hr)) + hr = postNotification(kWebHistorySavedNotification); + + return hr; +} + +HRESULT WebHistory::saveHistoryGuts(CFURLRef url, IWebError** error) +{ + HRESULT hr = S_OK; + + // FIXME: Correctly report error when new API is ready. + if (error) + *error = 0; + + RetainPtr stream(AdoptCF, CFWriteStreamCreateWithFile(kCFAllocatorDefault, url)); + if (!stream) + return E_FAIL; + + CFMutableArrayRef rawEntries; + hr = datesArray(&rawEntries); + if (FAILED(hr)) + return hr; + RetainPtr entries(AdoptCF, rawEntries); + + // create the outer dictionary + CFTypeRef keys[2]; + CFTypeRef values[2]; + keys[0] = DatesArrayKey; + values[0] = entries.get(); + keys[1] = FileVersionKey; + + int version = currentFileVersion; + RetainPtr versionCF(AdoptCF, CFNumberCreate(0, kCFNumberIntType, &version)); + values[1] = versionCF.get(); + + RetainPtr dictionary(AdoptCF, + CFDictionaryCreate(0, keys, values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); + + if (!CFWriteStreamOpen(stream.get())) + return E_FAIL; + + if (!CFPropertyListWriteToStream(dictionary.get(), stream.get(), kCFPropertyListXMLFormat_v1_0, 0)) + hr = E_FAIL; + + CFWriteStreamClose(stream.get()); + + return hr; +} + +HRESULT WebHistory::datesArray(CFMutableArrayRef* datesArray) +{ + HRESULT hr = S_OK; + + RetainPtr result(AdoptCF, CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks)); + + // for each date with entries + int dateCount = CFArrayGetCount(m_entriesByDate.get()); + for (int i = 0; i < dateCount; ++i) { + // get the entries for that date + CFArrayRef entries = (CFArrayRef)CFArrayGetValueAtIndex(m_entriesByDate.get(), i); + int entriesCount = CFArrayGetCount(entries); + for (int j = entriesCount - 1; j >= 0; --j) { + IWebHistoryItem* item = (IWebHistoryItem*) CFArrayGetValueAtIndex(entries, j); + IWebHistoryItemPrivate* webHistoryItem; + hr = item->QueryInterface(IID_IWebHistoryItemPrivate, (void**)&webHistoryItem); + if (FAILED(hr)) + return E_FAIL; + + CFDictionaryRef itemDict; + hr = webHistoryItem->dictionaryRepresentation((void**)&itemDict); + webHistoryItem->Release(); + if (FAILED(hr)) + return E_FAIL; + + CFArrayAppendValue(result.get(), itemDict); + CFRelease(itemDict); + } + } + + if (SUCCEEDED(hr)) + *datesArray = result.releaseRef(); + return hr; +} + +HRESULT STDMETHODCALLTYPE WebHistory::addItems( + /* [in] */ int itemCount, + /* [in] */ IWebHistoryItem** items) +{ + // There is no guarantee that the incoming entries are in any particular + // order, but if this is called with a set of entries that were created by + // iterating through the results of orderedLastVisitedDays and orderedItemsLastVisitedOnDay + // then they will be ordered chronologically from newest to oldest. We can make adding them + // faster (fewer compares) by inserting them from oldest to newest. + + HRESULT hr; + for (int i = itemCount - 1; i >= 0; --i) { + hr = addItem(items[i]); + if (FAILED(hr)) + return hr; + } + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE WebHistory::removeItems( + /* [in] */ int itemCount, + /* [in] */ IWebHistoryItem** items) +{ + HRESULT hr; + for (int i = 0; i < itemCount; ++i) { + hr = removeItem(items[i]); + if (FAILED(hr)) + return hr; + } + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE WebHistory::removeAllItems( void) +{ + CFArrayRemoveAllValues(m_entriesByDate.get()); + CFArrayRemoveAllValues(m_datesWithEntries.get()); + CFDictionaryRemoveAllValues(m_entriesByURL.get()); + + return postNotification(kWebHistoryAllItemsRemovedNotification); +} + +HRESULT STDMETHODCALLTYPE WebHistory::orderedLastVisitedDays( + /* [out][in] */ int* count, + /* [in] */ DATE* calendarDates) +{ + int dateCount = CFArrayGetCount(m_datesWithEntries.get()); + if (!calendarDates) { + *count = dateCount; + return S_OK; + } + + if (*count < dateCount) { + *count = dateCount; + return E_FAIL; + } + + *count = dateCount; + for (int i = 0; i < dateCount; i++) { + CFNumberRef absoluteTimeNumberRef = (CFNumberRef)CFArrayGetValueAtIndex(m_datesWithEntries.get(), i); + CFAbsoluteTime absoluteTime; + if (!CFNumberGetValue(absoluteTimeNumberRef, kCFNumberDoubleType, &absoluteTime)) + return E_FAIL; + calendarDates[i] = MarshallingHelpers::CFAbsoluteTimeToDATE(absoluteTime); + } + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE WebHistory::orderedItemsLastVisitedOnDay( + /* [out][in] */ int* count, + /* [in] */ IWebHistoryItem** items, + /* [in] */ DATE calendarDate) +{ + int index; + if (!findIndex(&index, MarshallingHelpers::DATEToCFAbsoluteTime(calendarDate))) { + *count = 0; + return 0; + } + + CFArrayRef entries = (CFArrayRef)CFArrayGetValueAtIndex(m_entriesByDate.get(), index); + if (!entries) { + *count = 0; + return 0; + } + + int newCount = CFArrayGetCount(entries); + + if (!items) { + *count = newCount; + return S_OK; + } + + if (*count < newCount) { + *count = newCount; + return E_FAIL; + } + + *count = newCount; + for (int i = 0; i < newCount; i++) { + IWebHistoryItem* item = (IWebHistoryItem*)CFArrayGetValueAtIndex(entries, i); + if (!item) + return E_FAIL; + item->AddRef(); + items[newCount-i-1] = item; // reverse when inserting to get the list sorted oldest to newest + } + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE WebHistory::setHistoryItemLimit( + /* [in] */ int limit) +{ + if (!m_preferences) + return E_FAIL; + return m_preferences->setHistoryItemLimit(limit); +} + +HRESULT STDMETHODCALLTYPE WebHistory::historyItemLimit( + /* [retval][out] */ int* limit) +{ + if (!m_preferences) + return E_FAIL; + return m_preferences->historyItemLimit(limit); +} + +HRESULT STDMETHODCALLTYPE WebHistory::setHistoryAgeInDaysLimit( + /* [in] */ int limit) +{ + if (!m_preferences) + return E_FAIL; + return m_preferences->setHistoryAgeInDaysLimit(limit); +} + +HRESULT STDMETHODCALLTYPE WebHistory::historyAgeInDaysLimit( + /* [retval][out] */ int* limit) +{ + if (!m_preferences) + return E_FAIL; + return m_preferences->historyAgeInDaysLimit(limit); +} + +HRESULT WebHistory::removeItem(IWebHistoryItem* entry) +{ + HRESULT hr = S_OK; + BSTR urlBStr = 0; + + hr = entry->URLString(&urlBStr); + if (FAILED(hr)) + return hr; + + RetainPtr urlString(AdoptCF, MarshallingHelpers::BSTRToCFStringRef(urlBStr)); + SysFreeString(urlBStr); + + // If this exact object isn't stored, then make no change. + // FIXME: Is this the right behavior if this entry isn't present, but another entry for the same URL is? + // Maybe need to change the API to make something like removeEntryForURLString public instead. + IWebHistoryItem *matchingEntry = (IWebHistoryItem*)CFDictionaryGetValue(m_entriesByURL.get(), urlString.get()); + if (matchingEntry != entry) + return E_FAIL; + + hr = removeItemForURLString(urlString.get()); + if (FAILED(hr)) + return hr; + + CFDictionaryPropertyBag* userInfo = createUserInfoFromHistoryItem( + getNotificationString(kWebHistoryItemsRemovedNotification), entry); + hr = postNotification(kWebHistoryItemsRemovedNotification, userInfo); + releaseUserInfo(userInfo); + + return hr; +} + +HRESULT WebHistory::addItem(IWebHistoryItem* entry) +{ + HRESULT hr = S_OK; + + if (!entry) + return E_FAIL; + + BSTR urlBStr = 0; + hr = entry->URLString(&urlBStr); + if (FAILED(hr)) + return hr; + + RetainPtr urlString(AdoptCF, MarshallingHelpers::BSTRToCFStringRef(urlBStr)); + SysFreeString(urlBStr); + + COMPtr oldEntry((IWebHistoryItem*) CFDictionaryGetValue( + m_entriesByURL.get(), urlString.get())); + + if (oldEntry) { + removeItemForURLString(urlString.get()); + + // If we already have an item with this URL, we need to merge info that drives the + // URL autocomplete heuristics from that item into the new one. + IWebHistoryItemPrivate* entryPriv; + hr = entry->QueryInterface(IID_IWebHistoryItemPrivate, (void**)&entryPriv); + if (SUCCEEDED(hr)) { + entryPriv->mergeAutoCompleteHints(oldEntry.get()); + entryPriv->Release(); + } + } + + hr = addItemToDateCaches(entry); + if (FAILED(hr)) + return hr; + + CFDictionarySetValue(m_entriesByURL.get(), urlString.get(), entry); + + CFDictionaryPropertyBag* userInfo = createUserInfoFromHistoryItem( + getNotificationString(kWebHistoryItemsAddedNotification), entry); + hr = postNotification(kWebHistoryItemsAddedNotification, userInfo); + releaseUserInfo(userInfo); + + return hr; +} + +HRESULT WebHistory::addItemForURL(BSTR url, BSTR title) +{ + COMPtr item(AdoptCOM, WebHistoryItem::createInstance()); + if (!item) + return E_FAIL; + + SYSTEMTIME currentTime; + GetSystemTime(¤tTime); + DATE lastVisited; + if (!SystemTimeToVariantTime(¤tTime, &lastVisited)) + return E_FAIL; + + HRESULT hr = item->initWithURLString(url, title, 0); + if (FAILED(hr)) + return hr; + + hr = item->setLastVisitedTimeInterval(lastVisited); // also increments visitedCount + if (FAILED(hr)) + return hr; + + return addItem(item.get()); +} + +HRESULT WebHistory::itemForURLString( + /* [in] */ CFStringRef urlString, + /* [retval][out] */ IWebHistoryItem** item) +{ + if (!item) + return E_FAIL; + *item = 0; + + IWebHistoryItem* foundItem = (IWebHistoryItem*) CFDictionaryGetValue(m_entriesByURL.get(), urlString); + if (!foundItem) + return E_FAIL; + + foundItem->AddRef(); + *item = foundItem; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE WebHistory::itemForURL( + /* [in] */ BSTR url, + /* [retval][out] */ IWebHistoryItem** item) +{ + RetainPtr urlString(AdoptCF, MarshallingHelpers::BSTRToCFStringRef(url)); + return itemForURLString(urlString.get(), item); +} + +HRESULT WebHistory::containsItemForURLString( + /* [in] */ void* urlCFString, + /* [retval][out] */ BOOL* contains) +{ + IWebHistoryItem* item = 0; + HRESULT hr; + if (SUCCEEDED(hr = itemForURLString((CFStringRef)urlCFString, &item))) { + *contains = TRUE; + // itemForURLString refs the returned item, so we need to balance that + item->Release(); + } else + *contains = FALSE; + + return hr; +} + +HRESULT WebHistory::removeItemForURLString(CFStringRef urlString) +{ + IWebHistoryItem* entry = (IWebHistoryItem*) CFDictionaryGetValue(m_entriesByURL.get(), urlString); + if (!entry) + return E_FAIL; + + HRESULT hr = removeItemFromDateCaches(entry); + CFDictionaryRemoveValue(m_entriesByURL.get(), urlString); + + return hr; +} + +HRESULT WebHistory::addItemToDateCaches(IWebHistoryItem* entry) +{ + HRESULT hr = S_OK; + + DATE lastVisitedCOMTime; + entry->lastVisitedTimeInterval(&lastVisitedCOMTime); + CFAbsoluteTime lastVisitedDate = timeToDate(MarshallingHelpers::DATEToCFAbsoluteTime(lastVisitedCOMTime)); + + int dateIndex; + if (findIndex(&dateIndex, lastVisitedDate)) { + // other entries already exist for this date + hr = insertItem(entry, dateIndex); + } else { + // no other entries exist for this date + RetainPtr lastVisitedDateRef(AdoptCF, CFNumberCreate(0, kCFNumberDoubleType, &lastVisitedDate)); + CFArrayInsertValueAtIndex(m_datesWithEntries.get(), dateIndex, lastVisitedDateRef.get()); + RetainPtr entryArray(AdoptCF, + CFArrayCreateMutable(0, 0, &MarshallingHelpers::kIUnknownArrayCallBacks)); + CFArrayAppendValue(entryArray.get(), entry); + CFArrayInsertValueAtIndex(m_entriesByDate.get(), dateIndex, entryArray.get()); + } + + return hr; +} + +HRESULT WebHistory::removeItemFromDateCaches(IWebHistoryItem* entry) +{ + HRESULT hr = S_OK; + + DATE lastVisitedCOMTime; + entry->lastVisitedTimeInterval(&lastVisitedCOMTime); + CFAbsoluteTime lastVisitedDate = timeToDate(MarshallingHelpers::DATEToCFAbsoluteTime(lastVisitedCOMTime)); + + int dateIndex; + if (!findIndex(&dateIndex, lastVisitedDate)) + return E_FAIL; + + CFMutableArrayRef entriesForDate = (CFMutableArrayRef) CFArrayGetValueAtIndex(m_entriesByDate.get(), dateIndex); + CFIndex count = CFArrayGetCount(entriesForDate); + for (int i = count - 1; i >= 0; --i) { + if ((IWebHistoryItem*)CFArrayGetValueAtIndex(entriesForDate, i) == entry) + CFArrayRemoveValueAtIndex(entriesForDate, i); + } + + // remove this date entirely if there are no other entries on it + if (CFArrayGetCount(entriesForDate) == 0) { + CFArrayRemoveValueAtIndex(m_entriesByDate.get(), dateIndex); + CFArrayRemoveValueAtIndex(m_datesWithEntries.get(), dateIndex); + } + + return hr; +} + +// Returns whether the day is already in the list of days, +// and fills in *index with the found or proposed index. +bool WebHistory::findIndex(int* index, CFAbsoluteTime forDay) +{ + CFAbsoluteTime forDayInDays = timeToDate(forDay); + + //FIXME: just does linear search through days; inefficient if many days + int count = CFArrayGetCount(m_datesWithEntries.get()); + for (*index = 0; *index < count; ++*index) { + CFNumberRef entryTimeNumberRef = (CFNumberRef) CFArrayGetValueAtIndex(m_datesWithEntries.get(), *index); + CFAbsoluteTime entryTime; + CFNumberGetValue(entryTimeNumberRef, kCFNumberDoubleType, &entryTime); + CFAbsoluteTime entryInDays = timeToDate(entryTime); + if (areEqualOrClose(forDayInDays, entryInDays)) + return true; + else if (forDayInDays > entryInDays) + return false; + } + return false; +} + +HRESULT WebHistory::insertItem(IWebHistoryItem* entry, int dateIndex) +{ + HRESULT hr = S_OK; + + if (!entry) + return E_FAIL; + if (dateIndex < 0 || dateIndex >= CFArrayGetCount(m_entriesByDate.get())) + return E_FAIL; + + //FIXME: just does linear search through entries; inefficient if many entries for this date + DATE entryTime; + entry->lastVisitedTimeInterval(&entryTime); + CFMutableArrayRef entriesForDate = (CFMutableArrayRef) CFArrayGetValueAtIndex(m_entriesByDate.get(), dateIndex); + int count = CFArrayGetCount(entriesForDate); + // optimized for inserting oldest to youngest + int index; + for (index = 0; index < count; ++index) { + IWebHistoryItem* indEntry = (IWebHistoryItem*) CFArrayGetValueAtIndex(entriesForDate, index); + DATE indTime; + hr = indEntry->lastVisitedTimeInterval(&indTime); + if (FAILED(hr)) + return hr; + if (entryTime < indTime) + break; + } + CFArrayInsertValueAtIndex(entriesForDate, index, entry); + return S_OK; +} + +CFAbsoluteTime WebHistory::timeToDate(CFAbsoluteTime time) +{ + // can't just divide/round since the day boundaries depend on our current time zone + const double secondsPerDay = 60 * 60 * 24; + CFTimeZoneRef timeZone = CFTimeZoneCopySystem(); + CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(time, timeZone); + date.hour = date.minute = 0; + date.second = 0.0; + CFAbsoluteTime timeInDays = CFGregorianDateGetAbsoluteTime(date, timeZone); + if (areEqualOrClose(time - timeInDays, secondsPerDay)) + timeInDays += secondsPerDay; + return timeInDays; +} + +// Return a date that marks the age limit for history entries saved to or +// loaded from disk. Any entry older than this item should be rejected. +HRESULT WebHistory::ageLimitDate(CFAbsoluteTime* time) +{ + // get the current date as a CFAbsoluteTime + CFAbsoluteTime currentDate = timeToDate(CFAbsoluteTimeGetCurrent()); + + CFGregorianUnits ageLimit = {0}; + int historyLimitDays; + HRESULT hr = historyAgeInDaysLimit(&historyLimitDays); + if (FAILED(hr)) + return hr; + ageLimit.days = -historyLimitDays; + *time = CFAbsoluteTimeAddGregorianUnits(currentDate, CFTimeZoneCopySystem(), ageLimit); + return S_OK; +} + +IWebHistoryPrivate* WebHistory::optionalSharedHistoryInternal() +{ + if (!m_optionalSharedHistory) + return 0; + + IWebHistoryPrivate* historyPrivate; + if (FAILED(m_optionalSharedHistory->QueryInterface(IID_IWebHistoryPrivate, (void**)&historyPrivate))) + return 0; + + historyPrivate->Release(); // don't add an additional ref for this internal call + return historyPrivate; +} + +// _WebCoreHistoryProvider ---------------------------------------------------------------- + +_WebCoreHistoryProvider::_WebCoreHistoryProvider(IWebHistory* history) + : m_history(history) + , m_historyPrivate(0) +{ +} + +_WebCoreHistoryProvider::~_WebCoreHistoryProvider() +{ +} + +static inline bool matchLetter(char c, char lowercaseLetter) +{ + return (c | 0x20) == lowercaseLetter; +} + +static inline bool matchUnicodeLetter(UniChar c, UniChar lowercaseLetter) +{ + return (c | 0x20) == lowercaseLetter; +} + +bool _WebCoreHistoryProvider::containsItemForURLLatin1(const char* latin1, unsigned int length) +{ + const int bufferSize = 2048; + const char *latin1Str = latin1; + char staticStrBuffer[bufferSize]; + char *strBuffer = 0; + bool needToAddSlash = false; + + if (length >= 6 && + matchLetter(latin1[0], 'h') && + matchLetter(latin1[1], 't') && + matchLetter(latin1[2], 't') && + matchLetter(latin1[3], 'p') && + (latin1[4] == ':' + || (matchLetter(latin1[4], 's') && latin1[5] == ':'))) { + int pos = latin1[4] == ':' ? 5 : 6; + // skip possible initial two slashes + if (latin1[pos] == '/' && latin1[pos + 1] == '/') { + pos += 2; + } + + const char* nextSlash = strchr(latin1 + pos, '/'); + if (!nextSlash) + needToAddSlash = true; + } + + if (needToAddSlash) { + if (length + 1 <= bufferSize) + strBuffer = staticStrBuffer; + else + strBuffer = (char*)malloc(length + 2); + memcpy(strBuffer, latin1, length + 1); + strBuffer[length] = '/'; + strBuffer[length+1] = '\0'; + length++; + + latin1Str = strBuffer; + } + + if (!m_historyPrivate) { + if (SUCCEEDED(m_history->QueryInterface(IID_IWebHistoryPrivate, (void**)&m_historyPrivate))) { + // don't hold a ref - we're owned by IWebHistory/IWebHistoryPrivate + m_historyPrivate->Release(); + } else { + if (strBuffer != staticStrBuffer) + free(strBuffer); + m_historyPrivate = 0; + return false; + } + } + + CFStringRef str = CFStringCreateWithCStringNoCopy(NULL, latin1Str, kCFStringEncodingWindowsLatin1, kCFAllocatorNull); + BOOL result = FALSE; + m_historyPrivate->containsItemForURLString((void*)str, &result); + CFRelease(str); + + if (strBuffer != staticStrBuffer) + free(strBuffer); + + return !!result; +} + +bool _WebCoreHistoryProvider::containsItemForURLUnicode(const UChar* unicode, unsigned int length) +{ + const int bufferSize = 1024; + const UChar *unicodeStr = unicode; + UChar staticStrBuffer[bufferSize]; + UChar *strBuffer = 0; + bool needToAddSlash = false; + + if (length >= 6 && + matchUnicodeLetter(unicode[0], 'h') && + matchUnicodeLetter(unicode[1], 't') && + matchUnicodeLetter(unicode[2], 't') && + matchUnicodeLetter(unicode[3], 'p') && + (unicode[4] == ':' + || (matchUnicodeLetter(unicode[4], 's') && unicode[5] == ':'))) { + + unsigned pos = unicode[4] == ':' ? 5 : 6; + + // skip possible initial two slashes + if (pos + 1 < length && unicode[pos] == '/' && unicode[pos + 1] == '/') + pos += 2; + + while (pos < length && unicode[pos] != '/') + pos++; + + if (pos == length) + needToAddSlash = true; + } + + if (needToAddSlash) { + if (length + 1 <= bufferSize) + strBuffer = staticStrBuffer; + else + strBuffer = (UChar*)malloc(sizeof(UChar) * (length + 1)); + memcpy(strBuffer, unicode, 2 * length); + strBuffer[length] = '/'; + length++; + + unicodeStr = strBuffer; + } + + if (!m_historyPrivate) { + if (SUCCEEDED(m_history->QueryInterface(IID_IWebHistoryPrivate, (void**)&m_historyPrivate))) { + // don't hold a ref - we're owned by IWebHistory/IWebHistoryPrivate + m_historyPrivate->Release(); + } else { + if (strBuffer != staticStrBuffer) + free(strBuffer); + m_historyPrivate = 0; + return false; + } + } + + CFStringRef str = CFStringCreateWithCharactersNoCopy(NULL, (const UniChar*)unicodeStr, length, kCFAllocatorNull); + BOOL result = FALSE; + m_historyPrivate->containsItemForURLString((void*)str, &result); + CFRelease(str); + + if (strBuffer != staticStrBuffer) + free(strBuffer); + + return !!result; +}