Move URL from WebCore to WTF
[WebKit-https.git] / Source / WebKitLegacy / win / WebHistory.cpp
1 /*
2  * Copyright (C) 2006-2007, 2014-2015 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 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 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 "WebKitDLL.h"
27 #include "WebHistory.h"
28
29 #include "MemoryStream.h"
30 #include "WebKit.h"
31 #include "MarshallingHelpers.h"
32 #include "WebHistoryItem.h"
33 #include "WebKit.h"
34 #include "WebNotificationCenter.h"
35 #include "WebPreferences.h"
36 #include "WebVisitedLinkStore.h"
37 #include <WebCore/BString.h>
38 #include <WebCore/HistoryItem.h>
39 #include <WebCore/PageGroup.h>
40 #include <WebCore/SharedBuffer.h>
41 #include <functional>
42 #include <wtf/DateMath.h>
43 #include <wtf/StdLibExtras.h>
44 #include <wtf/URL.h>
45 #include <wtf/Vector.h>
46
47 #if USE(CF)
48 #include "CFDictionaryPropertyBag.h"
49 #include <CoreFoundation/CoreFoundation.h>
50 #else
51 #include "COMPropertyBag.h"
52 #endif
53
54 using namespace WebCore;
55 using namespace std;
56
57 static bool areEqualOrClose(double d1, double d2)
58 {
59     double diff = d1-d2;
60     return (diff < .000001 && diff > -.000001);
61 }
62
63 static COMPtr<IPropertyBag> createUserInfoFromArray(BSTR notificationStr, IWebHistoryItem** data, size_t size)
64 {
65 #if USE(CF)
66     RetainPtr<CFArrayRef> arrayItem = adoptCF(CFArrayCreate(kCFAllocatorDefault, (const void**)data, size, &MarshallingHelpers::kIUnknownArrayCallBacks));
67
68     RetainPtr<CFMutableDictionaryRef> dictionary = adoptCF(
69         CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
70
71     RetainPtr<CFStringRef> key = adoptCF(MarshallingHelpers::BSTRToCFStringRef(notificationStr));
72     CFDictionaryAddValue(dictionary.get(), key.get(), arrayItem.get());
73
74     COMPtr<CFDictionaryPropertyBag> result = CFDictionaryPropertyBag::createInstance();
75     result->setDictionary(dictionary.get());
76     return COMPtr<IPropertyBag>(AdoptCOM, result.leakRef());
77 #else
78     Vector<COMPtr<IWebHistoryItem>, 1> arrayItem;
79     arrayItem.reserveInitialCapacity(size);
80     for (size_t i = 0; i < size; ++i)
81         arrayItem[i] = data[i];
82
83     HashMap<String, Vector<COMPtr<IWebHistoryItem>>> dictionary;
84     String key(notificationStr, ::SysStringLen(notificationStr));
85     dictionary.set(key, WTFMove(arrayItem));
86     return COMPtr<IPropertyBag>(AdoptCOM, COMPropertyBag<Vector<COMPtr<IWebHistoryItem>>>::adopt(dictionary));
87 #endif
88 }
89
90 static inline COMPtr<IPropertyBag> createUserInfoFromHistoryItem(BSTR notificationStr, IWebHistoryItem* item)
91 {
92     return createUserInfoFromArray(notificationStr, &item, 1);
93 }
94
95 static inline void addDayToSystemTime(SYSTEMTIME& systemTime)
96 {
97     systemTime.wDay += 1;
98     if (systemTime.wDay > 31) {
99         systemTime.wDay = 1;
100         systemTime.wMonth += 1;
101     }
102     if (systemTime.wMonth > 12) {
103         systemTime.wMonth = 1;
104         systemTime.wYear += 1;
105     }
106
107     // Convert to and from VariantTime to fix invalid dates like 2001-04-31.
108     DATE date = 0.0;
109     ::SystemTimeToVariantTime(&systemTime, &date);
110     ::VariantTimeToSystemTime(date, &systemTime);
111 }
112
113 static void getDayBoundaries(DATE day, DATE& beginningOfDay, DATE& beginningOfNextDay)
114 {
115     SYSTEMTIME systemTime;
116     ::VariantTimeToSystemTime(day, &systemTime);
117
118     SYSTEMTIME beginningLocalTime;
119     ::SystemTimeToTzSpecificLocalTime(0, &systemTime, &beginningLocalTime);
120     beginningLocalTime.wHour = 0;
121     beginningLocalTime.wMinute = 0;
122     beginningLocalTime.wSecond = 0;
123     beginningLocalTime.wMilliseconds = 0;
124
125     SYSTEMTIME beginningOfNextDayLocalTime = beginningLocalTime;
126     addDayToSystemTime(beginningOfNextDayLocalTime);
127
128     SYSTEMTIME beginningSystemTime;
129     if (::TzSpecificLocalTimeToSystemTime(0, &beginningLocalTime, &beginningSystemTime))
130         ::SystemTimeToVariantTime(&beginningSystemTime, &beginningOfDay);
131
132     SYSTEMTIME beginningOfNextDaySystemTime;
133     if (::TzSpecificLocalTimeToSystemTime(0, &beginningOfNextDayLocalTime, &beginningOfNextDaySystemTime))
134         ::SystemTimeToVariantTime(&beginningOfNextDaySystemTime, &beginningOfNextDay);
135 }
136
137 static inline DATE beginningOfDay(DATE date)
138 {
139     static DATE cachedBeginningOfDay = std::numeric_limits<DATE>::quiet_NaN();
140     static DATE cachedBeginningOfNextDay;
141     if (!(date >= cachedBeginningOfDay && date < cachedBeginningOfNextDay))
142         getDayBoundaries(date, cachedBeginningOfDay, cachedBeginningOfNextDay);
143     return cachedBeginningOfDay;
144 }
145
146 static inline WebHistory::DateKey dateKey(DATE date)
147 {
148     // Converting from double (DATE) to int64_t (WebHistoryDateKey) is safe
149     // here because all sensible dates are in the range -2**48 .. 2**47 which
150     // safely fits in an int64_t.
151     return beginningOfDay(date) * secondsPerDay;
152 }
153
154 // WebHistory -----------------------------------------------------------------
155
156 WebHistory::WebHistory()
157 {
158     gClassCount++;
159     gClassNameCount().add("WebHistory");
160
161     m_preferences = WebPreferences::sharedStandardPreferences();
162 }
163
164 WebHistory::~WebHistory()
165 {
166     gClassCount--;
167     gClassNameCount().remove("WebHistory");
168 }
169
170 WebHistory* WebHistory::createInstance()
171 {
172     WebHistory* instance = new WebHistory();
173     instance->AddRef();
174     return instance;
175 }
176
177 HRESULT WebHistory::postNotification(NotificationType notifyType, IPropertyBag* userInfo /*=0*/)
178 {
179     IWebNotificationCenter* nc = WebNotificationCenter::defaultCenterInternal();
180     HRESULT hr = nc->postNotificationName(getNotificationString(notifyType), static_cast<IWebHistory*>(this), userInfo);
181     if (FAILED(hr))
182         return hr;
183
184     return S_OK;
185 }
186
187 BSTR WebHistory::getNotificationString(NotificationType notifyType)
188 {
189     static BSTR keys[6] = {0};
190     if (!keys[0]) {
191         keys[0] = SysAllocString(WebHistoryItemsAddedNotification);
192         keys[1] = SysAllocString(WebHistoryItemsRemovedNotification);
193         keys[2] = SysAllocString(WebHistoryAllItemsRemovedNotification);
194         keys[3] = SysAllocString(WebHistoryLoadedNotification);
195         keys[4] = SysAllocString(WebHistoryItemsDiscardedWhileLoadingNotification);
196         keys[5] = SysAllocString(WebHistorySavedNotification);
197     }
198     return keys[notifyType];
199 }
200
201 // IUnknown -------------------------------------------------------------------
202
203 HRESULT WebHistory::QueryInterface(_In_ REFIID riid, _COM_Outptr_ void** ppvObject)
204 {
205     if (!ppvObject)
206         return E_POINTER;
207     *ppvObject = nullptr;
208     if (IsEqualGUID(riid, CLSID_WebHistory))
209         *ppvObject = this;
210     else if (IsEqualGUID(riid, IID_IUnknown))
211         *ppvObject = static_cast<IWebHistory*>(this);
212     else if (IsEqualGUID(riid, IID_IWebHistory))
213         *ppvObject = static_cast<IWebHistory*>(this);
214     else if (IsEqualGUID(riid, IID_IWebHistoryPrivate))
215         *ppvObject = static_cast<IWebHistoryPrivate*>(this);
216     else
217         return E_NOINTERFACE;
218
219     AddRef();
220     return S_OK;
221 }
222
223 ULONG WebHistory::AddRef()
224 {
225     return ++m_refCount;
226 }
227
228 ULONG WebHistory::Release()
229 {
230     ULONG newRef = --m_refCount;
231     if (!newRef)
232         delete(this);
233
234     return newRef;
235 }
236
237 // IWebHistory ----------------------------------------------------------------
238
239 static COMPtr<WebHistory>& sharedHistoryStorage()
240 {
241     static NeverDestroyed<COMPtr<WebHistory>> sharedHistory;
242     return sharedHistory;
243 }
244
245 WebHistory* WebHistory::sharedHistory()
246 {
247     return sharedHistoryStorage().get();
248 }
249
250 HRESULT WebHistory::optionalSharedHistory(_COM_Outptr_opt_ IWebHistory** history)
251 {
252     if (!history)
253         return E_POINTER;
254     *history = sharedHistory();
255     if (*history)
256         (*history)->AddRef();
257     return S_OK;
258 }
259
260 HRESULT WebHistory::setOptionalSharedHistory(_In_opt_ IWebHistory* history)
261 {
262     if (sharedHistoryStorage() == history)
263         return S_OK;
264     sharedHistoryStorage().query(history);
265     WebVisitedLinkStore::setShouldTrackVisitedLinks(sharedHistoryStorage());
266     WebVisitedLinkStore::removeAllVisitedLinks();
267     return S_OK;
268 }
269
270 HRESULT WebHistory::unused1()
271 {
272     ASSERT_NOT_REACHED();
273     return E_FAIL;
274 }
275
276 HRESULT WebHistory::unused2()
277 {
278     ASSERT_NOT_REACHED();
279     return E_FAIL;
280 }
281
282 HRESULT WebHistory::addItems(int itemCount, __deref_in_ecount_opt(itemCount) IWebHistoryItem** items)
283 {
284     // There is no guarantee that the incoming entries are in any particular
285     // order, but if this is called with a set of entries that were created by
286     // iterating through the results of orderedLastVisitedDays and orderedItemsLastVisitedOnDay
287     // then they will be ordered chronologically from newest to oldest. We can make adding them
288     // faster (fewer compares) by inserting them from oldest to newest.
289
290     HRESULT hr;
291     for (int i = itemCount - 1; i >= 0; --i) {
292         hr = addItem(items[i], false, 0);
293         if (FAILED(hr))
294             return hr;
295     }
296
297     return S_OK;
298 }
299
300 HRESULT WebHistory::removeItems(int itemCount, __deref_in_ecount_opt(itemCount) IWebHistoryItem** items)
301 {
302     HRESULT hr;
303     for (int i = 0; i < itemCount; ++i) {
304         hr = removeItem(items[i]);
305         if (FAILED(hr))
306             return hr;
307     }
308
309     return S_OK;
310 }
311
312 HRESULT WebHistory::removeAllItems()
313 {
314     Vector<IWebHistoryItem*> itemsVector;
315     itemsVector.reserveInitialCapacity(m_entriesByURL.size());
316     for (auto it = m_entriesByURL.begin(); it != m_entriesByURL.end(); ++it)
317         itemsVector.append(it->value.get());
318     COMPtr<IPropertyBag> userInfo = createUserInfoFromArray(getNotificationString(kWebHistoryAllItemsRemovedNotification), itemsVector.data(), itemsVector.size());
319
320     m_entriesByURL.clear();
321
322     WebVisitedLinkStore::removeAllVisitedLinks();
323
324     return postNotification(kWebHistoryAllItemsRemovedNotification, userInfo.get());
325 }
326
327 // FIXME: This function should be removed from the IWebHistory interface.
328 HRESULT WebHistory::orderedLastVisitedDays(_Inout_ int* count, _In_ DATE* calendarDates)
329 {
330     return E_NOTIMPL;
331 }
332
333 // FIXME: This function should be removed from the IWebHistory interface.
334 HRESULT WebHistory::orderedItemsLastVisitedOnDay(_Inout_ int* count, __deref_in_opt IWebHistoryItem** items, DATE calendarDate)
335 {
336     return E_NOTIMPL;
337 }
338
339 HRESULT WebHistory::allItems(_Inout_ int* count, __deref_opt_out IWebHistoryItem** items)
340 {
341     int entriesByURLCount = m_entriesByURL.size();
342
343     if (!items) {
344         *count = entriesByURLCount;
345         return S_OK;
346     }
347
348     if (*count < entriesByURLCount) {
349         *count = entriesByURLCount;
350         return E_NOT_SUFFICIENT_BUFFER;
351     }
352
353     *count = entriesByURLCount;
354     int i = 0;
355     for (auto it = m_entriesByURL.begin(); it != m_entriesByURL.end(); ++i, ++it) {
356         items[i] = it->value.get();
357         items[i]->AddRef();
358     }
359
360     return S_OK;
361 }
362
363 HRESULT WebHistory::setVisitedLinkTrackingEnabled(BOOL visitedLinkTrackingEnabled)
364 {
365     WebVisitedLinkStore::setShouldTrackVisitedLinks(visitedLinkTrackingEnabled);
366     return S_OK;
367 }
368
369 HRESULT WebHistory::removeAllVisitedLinks()
370 {
371     WebVisitedLinkStore::removeAllVisitedLinks();
372     return S_OK;
373 }
374
375 HRESULT WebHistory::setHistoryItemLimit(int limit)
376 {
377     if (!m_preferences)
378         return E_FAIL;
379     return m_preferences->setHistoryItemLimit(limit);
380 }
381
382 HRESULT WebHistory::historyItemLimit(_Out_ int* limit)
383 {
384     if (!limit)
385         return E_POINTER;
386     *limit = 0;
387     if (!m_preferences)
388         return E_FAIL;
389     return m_preferences->historyItemLimit(limit);
390 }
391
392 HRESULT WebHistory::setHistoryAgeInDaysLimit(int limit)
393 {
394     if (!m_preferences)
395         return E_FAIL;
396     return m_preferences->setHistoryAgeInDaysLimit(limit);
397 }
398
399 HRESULT WebHistory::historyAgeInDaysLimit(_Out_ int* limit)
400 {
401     if (!limit)
402         return E_POINTER;
403     *limit = 0;
404     if (!m_preferences)
405         return E_FAIL;
406     return m_preferences->historyAgeInDaysLimit(limit);
407 }
408
409 HRESULT WebHistory::removeItem(IWebHistoryItem* entry)
410 {
411     HRESULT hr = S_OK;
412     BString urlBStr;
413
414     hr = entry->URLString(&urlBStr);
415     if (FAILED(hr))
416         return hr;
417
418     String urlString(urlBStr, SysStringLen(urlBStr));
419     if (urlString.isEmpty())
420         return E_FAIL;
421
422     auto it = m_entriesByURL.find(urlString);
423     if (it == m_entriesByURL.end())
424         return E_FAIL;
425
426     // If this exact object isn't stored, then make no change.
427     // FIXME: Is this the right behavior if this entry isn't present, but another entry for the same URL is?
428     // Maybe need to change the API to make something like removeEntryForURLString public instead.
429     if (it->value.get() != entry)
430         return E_FAIL;
431
432     hr = removeItemForURLString(urlString);
433     if (FAILED(hr))
434         return hr;
435
436     COMPtr<IPropertyBag> userInfo = createUserInfoFromHistoryItem(
437         getNotificationString(kWebHistoryItemsRemovedNotification), entry);
438     hr = postNotification(kWebHistoryItemsRemovedNotification, userInfo.get());
439
440     return hr;
441 }
442
443 HRESULT WebHistory::addItem(IWebHistoryItem* entry, bool discardDuplicate, bool* added)
444 {
445     HRESULT hr = S_OK;
446
447     if (!entry)
448         return E_FAIL;
449
450     BString urlBStr;
451     hr = entry->URLString(&urlBStr);
452     if (FAILED(hr))
453         return hr;
454
455     String urlString(urlBStr, SysStringLen(urlBStr));
456     if (urlString.isEmpty())
457         return E_FAIL;
458
459     COMPtr<IWebHistoryItem> oldEntry(m_entriesByURL.get(urlString));
460
461     if (oldEntry) {
462         if (discardDuplicate) {
463             if (added)
464                 *added = false;
465             return S_OK;
466         }
467
468         removeItemForURLString(urlString);
469
470         // If we already have an item with this URL, we need to merge info that drives the
471         // URL autocomplete heuristics from that item into the new one.
472         IWebHistoryItemPrivate* entryPriv;
473         hr = entry->QueryInterface(IID_IWebHistoryItemPrivate, (void**)&entryPriv);
474         if (SUCCEEDED(hr)) {
475             entryPriv->mergeAutoCompleteHints(oldEntry.get());
476             entryPriv->Release();
477         }
478     }
479
480     m_entriesByURL.set(urlString, entry);
481
482     COMPtr<IPropertyBag> userInfo = createUserInfoFromHistoryItem(
483         getNotificationString(kWebHistoryItemsAddedNotification), entry);
484     hr = postNotification(kWebHistoryItemsAddedNotification, userInfo.get());
485
486     if (added)
487         *added = true;
488
489     return hr;
490 }
491
492 void WebHistory::visitedURL(const URL& url, const String& title, const String& httpMethod, bool wasFailure, bool increaseVisitCount)
493 {
494     const String& urlString = url.string();
495     if (urlString.isEmpty())
496         return;
497
498     IWebHistoryItem* entry = m_entriesByURL.get(urlString);
499     if (!entry) {
500         COMPtr<WebHistoryItem> item(AdoptCOM, WebHistoryItem::createInstance());
501         if (!item)
502             return;
503
504         entry = item.get();
505
506         SYSTEMTIME currentTime;
507         GetSystemTime(&currentTime);
508         DATE lastVisited;
509         if (!SystemTimeToVariantTime(&currentTime, &lastVisited))
510             return;
511
512         if (FAILED(entry->initWithURLString(BString(urlString), BString(title), lastVisited)))
513             return;
514         
515         m_entriesByURL.set(urlString, entry);
516     }
517
518     COMPtr<IWebHistoryItemPrivate> entryPrivate(Query, entry);
519     if (!entryPrivate)
520         return;
521
522     entryPrivate->setLastVisitWasFailure(wasFailure);
523
524     COMPtr<WebHistoryItem> item(Query, entry);
525
526     COMPtr<IPropertyBag> userInfo = createUserInfoFromHistoryItem(
527         getNotificationString(kWebHistoryItemsAddedNotification), entry);
528     postNotification(kWebHistoryItemsAddedNotification, userInfo.get());
529 }
530
531 HRESULT WebHistory::itemForURL(_In_ BSTR urlBStr, _COM_Outptr_opt_ IWebHistoryItem** item)
532 {
533     if (!item)
534         return E_POINTER;
535     *item = nullptr;
536
537     String urlString(urlBStr, SysStringLen(urlBStr));
538     if (urlString.isEmpty())
539         return E_FAIL;
540
541     auto it = m_entriesByURL.find(urlString);
542     if (it == m_entriesByURL.end())
543         return E_FAIL;
544
545     it->value.copyRefTo(item);
546     return S_OK;
547 }
548
549 HRESULT WebHistory::removeItemForURLString(const WTF::String& urlString)
550 {
551     if (urlString.isEmpty())
552         return E_FAIL;
553
554     auto it = m_entriesByURL.find(urlString);
555     if (it == m_entriesByURL.end())
556         return E_FAIL;
557
558     if (!m_entriesByURL.size())
559         WebVisitedLinkStore::removeAllVisitedLinks();
560
561     return S_OK;
562 }
563
564 COMPtr<IWebHistoryItem> WebHistory::itemForURLString(const String& urlString) const
565 {
566     if (urlString.isEmpty())
567         return nullptr;
568     return m_entriesByURL.get(urlString);
569 }
570
571 void WebHistory::addVisitedLinksToVisitedLinkStore(WebVisitedLinkStore& visitedLinkStore)
572 {
573     for (auto& url : m_entriesByURL.keys())
574         visitedLinkStore.addVisitedLink(url);
575 }