| /* |
| * Copyright (C) 2006-2007, 2014-2015 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 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 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 "WebKitDLL.h" |
| #include "WebHistory.h" |
| |
| #include "MemoryStream.h" |
| #include "WebKit.h" |
| #include "MarshallingHelpers.h" |
| #include "WebHistoryItem.h" |
| #include "WebKit.h" |
| #include "WebNotificationCenter.h" |
| #include "WebPreferences.h" |
| #include "WebVisitedLinkStore.h" |
| #include <WebCore/BString.h> |
| #include <WebCore/HistoryItem.h> |
| #include <WebCore/URL.h> |
| #include <WebCore/PageGroup.h> |
| #include <WebCore/SharedBuffer.h> |
| #include <functional> |
| #include <wtf/DateMath.h> |
| #include <wtf/StdLibExtras.h> |
| #include <wtf/Vector.h> |
| |
| #if USE(CF) |
| #include "CFDictionaryPropertyBag.h" |
| #include <CoreFoundation/CoreFoundation.h> |
| #else |
| #include "COMPropertyBag.h" |
| #endif |
| |
| using namespace WebCore; |
| using namespace std; |
| |
| static bool areEqualOrClose(double d1, double d2) |
| { |
| double diff = d1-d2; |
| return (diff < .000001 && diff > -.000001); |
| } |
| |
| static COMPtr<IPropertyBag> createUserInfoFromArray(BSTR notificationStr, IWebHistoryItem** data, size_t size) |
| { |
| #if USE(CF) |
| RetainPtr<CFArrayRef> arrayItem = adoptCF(CFArrayCreate(kCFAllocatorDefault, (const void**)data, size, &MarshallingHelpers::kIUnknownArrayCallBacks)); |
| |
| RetainPtr<CFMutableDictionaryRef> dictionary = adoptCF( |
| CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); |
| |
| RetainPtr<CFStringRef> key = adoptCF(MarshallingHelpers::BSTRToCFStringRef(notificationStr)); |
| CFDictionaryAddValue(dictionary.get(), key.get(), arrayItem.get()); |
| |
| COMPtr<CFDictionaryPropertyBag> result = CFDictionaryPropertyBag::createInstance(); |
| result->setDictionary(dictionary.get()); |
| return COMPtr<IPropertyBag>(AdoptCOM, result.leakRef()); |
| #else |
| Vector<COMPtr<IWebHistoryItem>, 1> arrayItem; |
| arrayItem.reserveInitialCapacity(size); |
| for (size_t i = 0; i < size; ++i) |
| arrayItem[i] = data[i]; |
| |
| HashMap<String, Vector<COMPtr<IWebHistoryItem>>> dictionary; |
| String key(notificationStr, ::SysStringLen(notificationStr)); |
| dictionary.set(key, WTFMove(arrayItem)); |
| return COMPtr<IPropertyBag>(AdoptCOM, COMPropertyBag<Vector<COMPtr<IWebHistoryItem>>>::adopt(dictionary)); |
| #endif |
| } |
| |
| static inline COMPtr<IPropertyBag> createUserInfoFromHistoryItem(BSTR notificationStr, IWebHistoryItem* item) |
| { |
| return createUserInfoFromArray(notificationStr, &item, 1); |
| } |
| |
| static inline void addDayToSystemTime(SYSTEMTIME& systemTime) |
| { |
| systemTime.wDay += 1; |
| if (systemTime.wDay > 31) { |
| systemTime.wDay = 1; |
| systemTime.wMonth += 1; |
| } |
| if (systemTime.wMonth > 12) { |
| systemTime.wMonth = 1; |
| systemTime.wYear += 1; |
| } |
| |
| // Convert to and from VariantTime to fix invalid dates like 2001-04-31. |
| DATE date = 0.0; |
| ::SystemTimeToVariantTime(&systemTime, &date); |
| ::VariantTimeToSystemTime(date, &systemTime); |
| } |
| |
| static void getDayBoundaries(DATE day, DATE& beginningOfDay, DATE& beginningOfNextDay) |
| { |
| SYSTEMTIME systemTime; |
| ::VariantTimeToSystemTime(day, &systemTime); |
| |
| SYSTEMTIME beginningLocalTime; |
| ::SystemTimeToTzSpecificLocalTime(0, &systemTime, &beginningLocalTime); |
| beginningLocalTime.wHour = 0; |
| beginningLocalTime.wMinute = 0; |
| beginningLocalTime.wSecond = 0; |
| beginningLocalTime.wMilliseconds = 0; |
| |
| SYSTEMTIME beginningOfNextDayLocalTime = beginningLocalTime; |
| addDayToSystemTime(beginningOfNextDayLocalTime); |
| |
| SYSTEMTIME beginningSystemTime; |
| if (::TzSpecificLocalTimeToSystemTime(0, &beginningLocalTime, &beginningSystemTime)) |
| ::SystemTimeToVariantTime(&beginningSystemTime, &beginningOfDay); |
| |
| SYSTEMTIME beginningOfNextDaySystemTime; |
| if (::TzSpecificLocalTimeToSystemTime(0, &beginningOfNextDayLocalTime, &beginningOfNextDaySystemTime)) |
| ::SystemTimeToVariantTime(&beginningOfNextDaySystemTime, &beginningOfNextDay); |
| } |
| |
| static inline DATE beginningOfDay(DATE date) |
| { |
| static DATE cachedBeginningOfDay = std::numeric_limits<DATE>::quiet_NaN(); |
| static DATE cachedBeginningOfNextDay; |
| if (!(date >= cachedBeginningOfDay && date < cachedBeginningOfNextDay)) |
| getDayBoundaries(date, cachedBeginningOfDay, cachedBeginningOfNextDay); |
| return cachedBeginningOfDay; |
| } |
| |
| static inline WebHistory::DateKey dateKey(DATE date) |
| { |
| // Converting from double (DATE) to int64_t (WebHistoryDateKey) is safe |
| // here because all sensible dates are in the range -2**48 .. 2**47 which |
| // safely fits in an int64_t. |
| return beginningOfDay(date) * secondsPerDay; |
| } |
| |
| // WebHistory ----------------------------------------------------------------- |
| |
| WebHistory::WebHistory() |
| { |
| gClassCount++; |
| gClassNameCount().add("WebHistory"); |
| |
| m_preferences = WebPreferences::sharedStandardPreferences(); |
| } |
| |
| WebHistory::~WebHistory() |
| { |
| gClassCount--; |
| gClassNameCount().remove("WebHistory"); |
| } |
| |
| 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<IWebHistory*>(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 WebHistory::QueryInterface(_In_ REFIID riid, _COM_Outptr_ void** ppvObject) |
| { |
| if (!ppvObject) |
| return E_POINTER; |
| *ppvObject = nullptr; |
| if (IsEqualGUID(riid, CLSID_WebHistory)) |
| *ppvObject = this; |
| else if (IsEqualGUID(riid, IID_IUnknown)) |
| *ppvObject = static_cast<IWebHistory*>(this); |
| else if (IsEqualGUID(riid, IID_IWebHistory)) |
| *ppvObject = static_cast<IWebHistory*>(this); |
| else if (IsEqualGUID(riid, IID_IWebHistoryPrivate)) |
| *ppvObject = static_cast<IWebHistoryPrivate*>(this); |
| else |
| return E_NOINTERFACE; |
| |
| AddRef(); |
| return S_OK; |
| } |
| |
| ULONG WebHistory::AddRef() |
| { |
| return ++m_refCount; |
| } |
| |
| ULONG WebHistory::Release() |
| { |
| ULONG newRef = --m_refCount; |
| if (!newRef) |
| delete(this); |
| |
| return newRef; |
| } |
| |
| // IWebHistory ---------------------------------------------------------------- |
| |
| static COMPtr<WebHistory>& sharedHistoryStorage() |
| { |
| static NeverDestroyed<COMPtr<WebHistory>> sharedHistory; |
| return sharedHistory; |
| } |
| |
| WebHistory* WebHistory::sharedHistory() |
| { |
| return sharedHistoryStorage().get(); |
| } |
| |
| HRESULT WebHistory::optionalSharedHistory(_COM_Outptr_opt_ IWebHistory** history) |
| { |
| if (!history) |
| return E_POINTER; |
| *history = sharedHistory(); |
| if (*history) |
| (*history)->AddRef(); |
| return S_OK; |
| } |
| |
| HRESULT WebHistory::setOptionalSharedHistory(_In_opt_ IWebHistory* history) |
| { |
| if (sharedHistoryStorage() == history) |
| return S_OK; |
| sharedHistoryStorage().query(history); |
| WebVisitedLinkStore::setShouldTrackVisitedLinks(sharedHistoryStorage()); |
| WebVisitedLinkStore::removeAllVisitedLinks(); |
| return S_OK; |
| } |
| |
| HRESULT WebHistory::unused1() |
| { |
| ASSERT_NOT_REACHED(); |
| return E_FAIL; |
| } |
| |
| HRESULT WebHistory::unused2() |
| { |
| ASSERT_NOT_REACHED(); |
| return E_FAIL; |
| } |
| |
| HRESULT WebHistory::addItems(int itemCount, __deref_in_ecount_opt(itemCount) 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], false, 0); |
| if (FAILED(hr)) |
| return hr; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT WebHistory::removeItems(int itemCount, __deref_in_ecount_opt(itemCount) IWebHistoryItem** items) |
| { |
| HRESULT hr; |
| for (int i = 0; i < itemCount; ++i) { |
| hr = removeItem(items[i]); |
| if (FAILED(hr)) |
| return hr; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT WebHistory::removeAllItems() |
| { |
| Vector<IWebHistoryItem*> itemsVector; |
| itemsVector.reserveInitialCapacity(m_entriesByURL.size()); |
| for (auto it = m_entriesByURL.begin(); it != m_entriesByURL.end(); ++it) |
| itemsVector.append(it->value.get()); |
| COMPtr<IPropertyBag> userInfo = createUserInfoFromArray(getNotificationString(kWebHistoryAllItemsRemovedNotification), itemsVector.data(), itemsVector.size()); |
| |
| m_entriesByURL.clear(); |
| |
| WebVisitedLinkStore::removeAllVisitedLinks(); |
| |
| return postNotification(kWebHistoryAllItemsRemovedNotification, userInfo.get()); |
| } |
| |
| // FIXME: This function should be removed from the IWebHistory interface. |
| HRESULT WebHistory::orderedLastVisitedDays(_Inout_ int* count, _In_ DATE* calendarDates) |
| { |
| return E_NOTIMPL; |
| } |
| |
| // FIXME: This function should be removed from the IWebHistory interface. |
| HRESULT WebHistory::orderedItemsLastVisitedOnDay(_Inout_ int* count, __deref_in_opt IWebHistoryItem** items, DATE calendarDate) |
| { |
| return E_NOTIMPL; |
| } |
| |
| HRESULT WebHistory::allItems(_Inout_ int* count, __deref_opt_out IWebHistoryItem** items) |
| { |
| int entriesByURLCount = m_entriesByURL.size(); |
| |
| if (!items) { |
| *count = entriesByURLCount; |
| return S_OK; |
| } |
| |
| if (*count < entriesByURLCount) { |
| *count = entriesByURLCount; |
| return E_NOT_SUFFICIENT_BUFFER; |
| } |
| |
| *count = entriesByURLCount; |
| int i = 0; |
| for (auto it = m_entriesByURL.begin(); it != m_entriesByURL.end(); ++i, ++it) { |
| items[i] = it->value.get(); |
| items[i]->AddRef(); |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT WebHistory::setVisitedLinkTrackingEnabled(BOOL visitedLinkTrackingEnabled) |
| { |
| WebVisitedLinkStore::setShouldTrackVisitedLinks(visitedLinkTrackingEnabled); |
| return S_OK; |
| } |
| |
| HRESULT WebHistory::removeAllVisitedLinks() |
| { |
| WebVisitedLinkStore::removeAllVisitedLinks(); |
| return S_OK; |
| } |
| |
| HRESULT WebHistory::setHistoryItemLimit(int limit) |
| { |
| if (!m_preferences) |
| return E_FAIL; |
| return m_preferences->setHistoryItemLimit(limit); |
| } |
| |
| HRESULT WebHistory::historyItemLimit(_Out_ int* limit) |
| { |
| if (!limit) |
| return E_POINTER; |
| *limit = 0; |
| if (!m_preferences) |
| return E_FAIL; |
| return m_preferences->historyItemLimit(limit); |
| } |
| |
| HRESULT WebHistory::setHistoryAgeInDaysLimit(int limit) |
| { |
| if (!m_preferences) |
| return E_FAIL; |
| return m_preferences->setHistoryAgeInDaysLimit(limit); |
| } |
| |
| HRESULT WebHistory::historyAgeInDaysLimit(_Out_ int* limit) |
| { |
| if (!limit) |
| return E_POINTER; |
| *limit = 0; |
| if (!m_preferences) |
| return E_FAIL; |
| return m_preferences->historyAgeInDaysLimit(limit); |
| } |
| |
| HRESULT WebHistory::removeItem(IWebHistoryItem* entry) |
| { |
| HRESULT hr = S_OK; |
| BString urlBStr; |
| |
| hr = entry->URLString(&urlBStr); |
| if (FAILED(hr)) |
| return hr; |
| |
| String urlString(urlBStr, SysStringLen(urlBStr)); |
| if (urlString.isEmpty()) |
| return E_FAIL; |
| |
| auto it = m_entriesByURL.find(urlString); |
| if (it == m_entriesByURL.end()) |
| return E_FAIL; |
| |
| // 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. |
| if (it->value.get() != entry) |
| return E_FAIL; |
| |
| hr = removeItemForURLString(urlString); |
| if (FAILED(hr)) |
| return hr; |
| |
| COMPtr<IPropertyBag> userInfo = createUserInfoFromHistoryItem( |
| getNotificationString(kWebHistoryItemsRemovedNotification), entry); |
| hr = postNotification(kWebHistoryItemsRemovedNotification, userInfo.get()); |
| |
| return hr; |
| } |
| |
| HRESULT WebHistory::addItem(IWebHistoryItem* entry, bool discardDuplicate, bool* added) |
| { |
| HRESULT hr = S_OK; |
| |
| if (!entry) |
| return E_FAIL; |
| |
| BString urlBStr; |
| hr = entry->URLString(&urlBStr); |
| if (FAILED(hr)) |
| return hr; |
| |
| String urlString(urlBStr, SysStringLen(urlBStr)); |
| if (urlString.isEmpty()) |
| return E_FAIL; |
| |
| COMPtr<IWebHistoryItem> oldEntry(m_entriesByURL.get(urlString)); |
| |
| if (oldEntry) { |
| if (discardDuplicate) { |
| if (added) |
| *added = false; |
| return S_OK; |
| } |
| |
| removeItemForURLString(urlString); |
| |
| // 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(); |
| } |
| } |
| |
| m_entriesByURL.set(urlString, entry); |
| |
| COMPtr<IPropertyBag> userInfo = createUserInfoFromHistoryItem( |
| getNotificationString(kWebHistoryItemsAddedNotification), entry); |
| hr = postNotification(kWebHistoryItemsAddedNotification, userInfo.get()); |
| |
| if (added) |
| *added = true; |
| |
| return hr; |
| } |
| |
| void WebHistory::visitedURL(const URL& url, const String& title, const String& httpMethod, bool wasFailure, bool increaseVisitCount) |
| { |
| const String& urlString = url.string(); |
| if (urlString.isEmpty()) |
| return; |
| |
| IWebHistoryItem* entry = m_entriesByURL.get(urlString); |
| if (!entry) { |
| COMPtr<WebHistoryItem> item(AdoptCOM, WebHistoryItem::createInstance()); |
| if (!item) |
| return; |
| |
| entry = item.get(); |
| |
| SYSTEMTIME currentTime; |
| GetSystemTime(¤tTime); |
| DATE lastVisited; |
| if (!SystemTimeToVariantTime(¤tTime, &lastVisited)) |
| return; |
| |
| if (FAILED(entry->initWithURLString(BString(urlString), BString(title), lastVisited))) |
| return; |
| |
| m_entriesByURL.set(urlString, entry); |
| } |
| |
| COMPtr<IWebHistoryItemPrivate> entryPrivate(Query, entry); |
| if (!entryPrivate) |
| return; |
| |
| entryPrivate->setLastVisitWasFailure(wasFailure); |
| |
| COMPtr<WebHistoryItem> item(Query, entry); |
| |
| COMPtr<IPropertyBag> userInfo = createUserInfoFromHistoryItem( |
| getNotificationString(kWebHistoryItemsAddedNotification), entry); |
| postNotification(kWebHistoryItemsAddedNotification, userInfo.get()); |
| } |
| |
| HRESULT WebHistory::itemForURL(_In_ BSTR urlBStr, _COM_Outptr_opt_ IWebHistoryItem** item) |
| { |
| if (!item) |
| return E_POINTER; |
| *item = nullptr; |
| |
| String urlString(urlBStr, SysStringLen(urlBStr)); |
| if (urlString.isEmpty()) |
| return E_FAIL; |
| |
| auto it = m_entriesByURL.find(urlString); |
| if (it == m_entriesByURL.end()) |
| return E_FAIL; |
| |
| it->value.copyRefTo(item); |
| return S_OK; |
| } |
| |
| HRESULT WebHistory::removeItemForURLString(const WTF::String& urlString) |
| { |
| if (urlString.isEmpty()) |
| return E_FAIL; |
| |
| auto it = m_entriesByURL.find(urlString); |
| if (it == m_entriesByURL.end()) |
| return E_FAIL; |
| |
| if (!m_entriesByURL.size()) |
| WebVisitedLinkStore::removeAllVisitedLinks(); |
| |
| return S_OK; |
| } |
| |
| COMPtr<IWebHistoryItem> WebHistory::itemForURLString(const String& urlString) const |
| { |
| if (urlString.isEmpty()) |
| return nullptr; |
| return m_entriesByURL.get(urlString); |
| } |
| |
| void WebHistory::addVisitedLinksToVisitedLinkStore(WebVisitedLinkStore& visitedLinkStore) |
| { |
| for (auto& url : m_entriesByURL.keys()) |
| visitedLinkStore.addVisitedLink(url); |
| } |