blob: 22b0846a906b7828656b8d957c10a0dfaff8b301 [file] [log] [blame]
/*
* 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(&currentTime);
DATE lastVisited;
if (!SystemTimeToVariantTime(&currentTime, &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);
}