• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
WebHistoryWriter(const WebHistory::DateToEntriesMap & entriesByDate)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 
writeHistoryItems(BinaryPropertyListObjectStream & stream)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 
areEqualOrClose(double d1,double d2)93 static bool areEqualOrClose(double d1, double d2)
94 {
95     double diff = d1-d2;
96     return (diff < .000001 && diff > -.000001);
97 }
98 
createUserInfoFromArray(BSTR notificationStr,CFArrayRef arrayItem)99 static 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     CFDictionaryPropertyBag* result = CFDictionaryPropertyBag::createInstance();
108     result->setDictionary(dictionary.get());
109     return result;
110 }
111 
createUserInfoFromHistoryItem(BSTR notificationStr,IWebHistoryItem * item)112 static 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     CFDictionaryPropertyBag* info = createUserInfoFromArray(notificationStr, itemList.get());
117     return info;
118 }
119 
releaseUserInfo(CFDictionaryPropertyBag * userInfo)120 static void releaseUserInfo(CFDictionaryPropertyBag* userInfo)
121 {
122     // free the dictionary
123     userInfo->setDictionary(0);
124     int result = userInfo->Release();
125     (void)result;
126     ASSERT(result == 0);   // make sure no one else holds a reference to the userInfo.
127 }
128 
129 // WebHistory -----------------------------------------------------------------
130 
WebHistory()131 WebHistory::WebHistory()
132 : m_refCount(0)
133 , m_preferences(0)
134 {
135     gClassCount++;
136     gClassNameCount.add("WebHistory");
137 
138     m_entriesByURL.adoptCF(CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &MarshallingHelpers::kIUnknownDictionaryValueCallBacks));
139 
140     m_preferences = WebPreferences::sharedStandardPreferences();
141 }
142 
~WebHistory()143 WebHistory::~WebHistory()
144 {
145     gClassCount--;
146     gClassNameCount.remove("WebHistory");
147 }
148 
createInstance()149 WebHistory* WebHistory::createInstance()
150 {
151     WebHistory* instance = new WebHistory();
152     instance->AddRef();
153     return instance;
154 }
155 
postNotification(NotificationType notifyType,IPropertyBag * userInfo)156 HRESULT WebHistory::postNotification(NotificationType notifyType, IPropertyBag* userInfo /*=0*/)
157 {
158     IWebNotificationCenter* nc = WebNotificationCenter::defaultCenterInternal();
159     HRESULT hr = nc->postNotificationName(getNotificationString(notifyType), static_cast<IWebHistory*>(this), userInfo);
160     if (FAILED(hr))
161         return hr;
162 
163     return S_OK;
164 }
165 
getNotificationString(NotificationType notifyType)166 BSTR WebHistory::getNotificationString(NotificationType notifyType)
167 {
168     static BSTR keys[6] = {0};
169     if (!keys[0]) {
170         keys[0] = SysAllocString(WebHistoryItemsAddedNotification);
171         keys[1] = SysAllocString(WebHistoryItemsRemovedNotification);
172         keys[2] = SysAllocString(WebHistoryAllItemsRemovedNotification);
173         keys[3] = SysAllocString(WebHistoryLoadedNotification);
174         keys[4] = SysAllocString(WebHistoryItemsDiscardedWhileLoadingNotification);
175         keys[5] = SysAllocString(WebHistorySavedNotification);
176     }
177     return keys[notifyType];
178 }
179 
180 // IUnknown -------------------------------------------------------------------
181 
QueryInterface(REFIID riid,void ** ppvObject)182 HRESULT STDMETHODCALLTYPE WebHistory::QueryInterface(REFIID riid, void** ppvObject)
183 {
184     *ppvObject = 0;
185     if (IsEqualGUID(riid, CLSID_WebHistory))
186         *ppvObject = this;
187     else if (IsEqualGUID(riid, IID_IUnknown))
188         *ppvObject = static_cast<IWebHistory*>(this);
189     else if (IsEqualGUID(riid, IID_IWebHistory))
190         *ppvObject = static_cast<IWebHistory*>(this);
191     else if (IsEqualGUID(riid, IID_IWebHistoryPrivate))
192         *ppvObject = static_cast<IWebHistoryPrivate*>(this);
193     else
194         return E_NOINTERFACE;
195 
196     AddRef();
197     return S_OK;
198 }
199 
AddRef(void)200 ULONG STDMETHODCALLTYPE WebHistory::AddRef(void)
201 {
202     return ++m_refCount;
203 }
204 
Release(void)205 ULONG STDMETHODCALLTYPE WebHistory::Release(void)
206 {
207     ULONG newRef = --m_refCount;
208     if (!newRef)
209         delete(this);
210 
211     return newRef;
212 }
213 
214 // IWebHistory ----------------------------------------------------------------
215 
sharedHistoryStorage()216 static inline COMPtr<WebHistory>& sharedHistoryStorage()
217 {
218     DEFINE_STATIC_LOCAL(COMPtr<WebHistory>, sharedHistory, ());
219     return sharedHistory;
220 }
221 
sharedHistory()222 WebHistory* WebHistory::sharedHistory()
223 {
224     return sharedHistoryStorage().get();
225 }
226 
optionalSharedHistory(IWebHistory ** history)227 HRESULT STDMETHODCALLTYPE WebHistory::optionalSharedHistory(
228     /* [retval][out] */ IWebHistory** history)
229 {
230     *history = sharedHistory();
231     if (*history)
232         (*history)->AddRef();
233     return S_OK;
234 }
235 
setOptionalSharedHistory(IWebHistory * history)236 HRESULT STDMETHODCALLTYPE WebHistory::setOptionalSharedHistory(
237     /* [in] */ IWebHistory* history)
238 {
239     if (sharedHistoryStorage() == history)
240         return S_OK;
241     sharedHistoryStorage().query(history);
242     PageGroup::setShouldTrackVisitedLinks(sharedHistoryStorage());
243     PageGroup::removeAllVisitedLinks();
244     return S_OK;
245 }
246 
loadFromURL(BSTR url,IWebError ** error,BOOL * succeeded)247 HRESULT STDMETHODCALLTYPE WebHistory::loadFromURL(
248     /* [in] */ BSTR url,
249     /* [out] */ IWebError** error,
250     /* [retval][out] */ BOOL* succeeded)
251 {
252     HRESULT hr = S_OK;
253     RetainPtr<CFMutableArrayRef> discardedItems(AdoptCF,
254         CFArrayCreateMutable(0, 0, &MarshallingHelpers::kIUnknownArrayCallBacks));
255 
256     RetainPtr<CFURLRef> urlRef(AdoptCF, MarshallingHelpers::BSTRToCFURLRef(url));
257 
258     hr = loadHistoryGutsFromURL(urlRef.get(), discardedItems.get(), error);
259     if (FAILED(hr))
260         goto exit;
261 
262     hr = postNotification(kWebHistoryLoadedNotification);
263     if (FAILED(hr))
264         goto exit;
265 
266     if (CFArrayGetCount(discardedItems.get()) > 0) {
267         CFDictionaryPropertyBag* userInfo = createUserInfoFromArray(getNotificationString(kWebHistoryItemsDiscardedWhileLoadingNotification), discardedItems.get());
268         hr = postNotification(kWebHistoryItemsDiscardedWhileLoadingNotification, userInfo);
269         releaseUserInfo(userInfo);
270         if (FAILED(hr))
271             goto exit;
272     }
273 
274 exit:
275     if (succeeded)
276         *succeeded = SUCCEEDED(hr);
277     return hr;
278 }
279 
createHistoryListFromStream(CFReadStreamRef stream,CFPropertyListFormat format)280 static CFDictionaryRef createHistoryListFromStream(CFReadStreamRef stream, CFPropertyListFormat format)
281 {
282     return (CFDictionaryRef)CFPropertyListCreateFromStream(0, stream, 0, kCFPropertyListImmutable, &format, 0);
283 }
284 
loadHistoryGutsFromURL(CFURLRef url,CFMutableArrayRef discardedItems,IWebError **)285 HRESULT WebHistory::loadHistoryGutsFromURL(CFURLRef url, CFMutableArrayRef discardedItems, IWebError** /*error*/) //FIXME
286 {
287     CFPropertyListFormat format = kCFPropertyListBinaryFormat_v1_0 | kCFPropertyListXMLFormat_v1_0;
288     HRESULT hr = S_OK;
289     int numberOfItemsLoaded = 0;
290 
291     RetainPtr<CFReadStreamRef> stream(AdoptCF, CFReadStreamCreateWithFile(0, url));
292     if (!stream)
293         return E_FAIL;
294 
295     if (!CFReadStreamOpen(stream.get()))
296         return E_FAIL;
297 
298     RetainPtr<CFDictionaryRef> historyList(AdoptCF, createHistoryListFromStream(stream.get(), format));
299     CFReadStreamClose(stream.get());
300 
301     if (!historyList)
302         return E_FAIL;
303 
304     CFNumberRef fileVersionObject = (CFNumberRef)CFDictionaryGetValue(historyList.get(), FileVersionKey);
305     int fileVersion;
306     if (!CFNumberGetValue(fileVersionObject, kCFNumberIntType, &fileVersion))
307         return E_FAIL;
308 
309     if (fileVersion > currentFileVersion)
310         return E_FAIL;
311 
312     CFArrayRef datesArray = (CFArrayRef)CFDictionaryGetValue(historyList.get(), DatesArrayKey);
313 
314     int itemCountLimit;
315     hr = historyItemLimit(&itemCountLimit);
316     if (FAILED(hr))
317         return hr;
318 
319     CFAbsoluteTime limitDate;
320     hr = ageLimitDate(&limitDate);
321     if (FAILED(hr))
322         return hr;
323 
324     bool ageLimitPassed = false;
325     bool itemLimitPassed = false;
326 
327     CFIndex itemCount = CFArrayGetCount(datesArray);
328     for (CFIndex i = 0; i < itemCount; ++i) {
329         CFDictionaryRef itemAsDictionary = (CFDictionaryRef)CFArrayGetValueAtIndex(datesArray, i);
330         COMPtr<WebHistoryItem> item(AdoptCOM, WebHistoryItem::createInstance());
331         hr = item->initFromDictionaryRepresentation((void*)itemAsDictionary);
332         if (FAILED(hr))
333             return hr;
334 
335         // item without URL is useless; data on disk must have been bad; ignore
336         BOOL hasURL;
337         hr = item->hasURLString(&hasURL);
338         if (FAILED(hr))
339             return hr;
340 
341         if (hasURL) {
342             // Test against date limit. Since the items are ordered newest to oldest, we can stop comparing
343             // once we've found the first item that's too old.
344             if (!ageLimitPassed) {
345                 DATE lastVisitedTime;
346                 hr = item->lastVisitedTimeInterval(&lastVisitedTime);
347                 if (FAILED(hr))
348                     return hr;
349                 if (timeToDate(MarshallingHelpers::DATEToCFAbsoluteTime(lastVisitedTime)) <= limitDate)
350                     ageLimitPassed = true;
351             }
352             if (ageLimitPassed || itemLimitPassed)
353                 CFArrayAppendValue(discardedItems, item.get());
354             else {
355                 bool added;
356                 addItem(item.get(), true, &added); // ref is added inside addItem
357                 if (added)
358                     ++numberOfItemsLoaded;
359                 if (numberOfItemsLoaded == itemCountLimit)
360                     itemLimitPassed = true;
361             }
362         }
363     }
364     return hr;
365 }
366 
saveToURL(BSTR url,IWebError ** error,BOOL * succeeded)367 HRESULT STDMETHODCALLTYPE WebHistory::saveToURL(
368     /* [in] */ BSTR url,
369     /* [out] */ IWebError** error,
370     /* [retval][out] */ BOOL* succeeded)
371 {
372     HRESULT hr = S_OK;
373     RetainPtr<CFURLRef> urlRef(AdoptCF, MarshallingHelpers::BSTRToCFURLRef(url));
374 
375     hr = saveHistoryGuts(urlRef.get(), error);
376 
377     if (succeeded)
378         *succeeded = SUCCEEDED(hr);
379     if (SUCCEEDED(hr))
380         hr = postNotification(kWebHistorySavedNotification);
381 
382     return hr;
383 }
384 
saveHistoryGuts(CFURLRef url,IWebError ** error)385 HRESULT WebHistory::saveHistoryGuts(CFURLRef url, IWebError** error)
386 {
387     HRESULT hr = S_OK;
388 
389     // FIXME: Correctly report error when new API is ready.
390     if (error)
391         *error = 0;
392 
393     RetainPtr<CFDataRef> data = this->data();
394 
395     RetainPtr<CFWriteStreamRef> stream(AdoptCF, CFWriteStreamCreateWithFile(kCFAllocatorDefault, url));
396     if (!stream)
397         return E_FAIL;
398 
399     if (!CFWriteStreamOpen(stream.get()))
400         return E_FAIL;
401 
402     const UInt8* dataPtr = CFDataGetBytePtr(data.get());
403     CFIndex length = CFDataGetLength(data.get());
404 
405     while (length) {
406         CFIndex bytesWritten = CFWriteStreamWrite(stream.get(), dataPtr, length);
407         if (bytesWritten <= 0) {
408             hr = E_FAIL;
409             break;
410         }
411         dataPtr += bytesWritten;
412         length -= bytesWritten;
413     }
414 
415     CFWriteStreamClose(stream.get());
416 
417     return hr;
418 }
419 
addItems(int itemCount,IWebHistoryItem ** items)420 HRESULT STDMETHODCALLTYPE WebHistory::addItems(
421     /* [in] */ int itemCount,
422     /* [in] */ IWebHistoryItem** items)
423 {
424     // There is no guarantee that the incoming entries are in any particular
425     // order, but if this is called with a set of entries that were created by
426     // iterating through the results of orderedLastVisitedDays and orderedItemsLastVisitedOnDay
427     // then they will be ordered chronologically from newest to oldest. We can make adding them
428     // faster (fewer compares) by inserting them from oldest to newest.
429 
430     HRESULT hr;
431     for (int i = itemCount - 1; i >= 0; --i) {
432         hr = addItem(items[i], false, 0);
433         if (FAILED(hr))
434             return hr;
435     }
436 
437     return S_OK;
438 }
439 
removeItems(int itemCount,IWebHistoryItem ** items)440 HRESULT STDMETHODCALLTYPE WebHistory::removeItems(
441     /* [in] */ int itemCount,
442     /* [in] */ IWebHistoryItem** items)
443 {
444     HRESULT hr;
445     for (int i = 0; i < itemCount; ++i) {
446         hr = removeItem(items[i]);
447         if (FAILED(hr))
448             return hr;
449     }
450 
451     return S_OK;
452 }
453 
removeAllItems(void)454 HRESULT STDMETHODCALLTYPE WebHistory::removeAllItems( void)
455 {
456     m_entriesByDate.clear();
457     m_orderedLastVisitedDays.clear();
458 
459     CFIndex itemCount = CFDictionaryGetCount(m_entriesByURL.get());
460     Vector<IWebHistoryItem*> itemsVector(itemCount);
461     CFDictionaryGetKeysAndValues(m_entriesByURL.get(), 0, (const void**)itemsVector.data());
462     RetainPtr<CFArrayRef> allItems(AdoptCF, CFArrayCreate(kCFAllocatorDefault, (const void**)itemsVector.data(), itemCount, &MarshallingHelpers::kIUnknownArrayCallBacks));
463 
464     CFDictionaryRemoveAllValues(m_entriesByURL.get());
465 
466     PageGroup::removeAllVisitedLinks();
467 
468     CFDictionaryPropertyBag* userInfo = createUserInfoFromArray(getNotificationString(kWebHistoryAllItemsRemovedNotification), allItems.get());
469     return postNotification(kWebHistoryAllItemsRemovedNotification, userInfo);
470 }
471 
orderedLastVisitedDays(int * count,DATE * calendarDates)472 HRESULT STDMETHODCALLTYPE WebHistory::orderedLastVisitedDays(
473     /* [out][in] */ int* count,
474     /* [in] */ DATE* calendarDates)
475 {
476     int dateCount = m_entriesByDate.size();
477     if (!calendarDates) {
478         *count = dateCount;
479         return S_OK;
480     }
481 
482     if (*count < dateCount) {
483         *count = dateCount;
484         return E_FAIL;
485     }
486 
487     *count = dateCount;
488     if (!m_orderedLastVisitedDays) {
489         m_orderedLastVisitedDays.set(new DATE[dateCount]);
490         DateToEntriesMap::const_iterator::Keys end = m_entriesByDate.end().keys();
491         int i = 0;
492         for (DateToEntriesMap::const_iterator::Keys it = m_entriesByDate.begin().keys(); it != end; ++it, ++i)
493             m_orderedLastVisitedDays[i] = MarshallingHelpers::CFAbsoluteTimeToDATE(*it);
494         // Use std::greater to sort the days in descending order (i.e., most-recent first).
495         sort(m_orderedLastVisitedDays.get(), m_orderedLastVisitedDays.get() + dateCount, greater<DATE>());
496     }
497 
498     memcpy(calendarDates, m_orderedLastVisitedDays.get(), dateCount * sizeof(DATE));
499     return S_OK;
500 }
501 
orderedItemsLastVisitedOnDay(int * count,IWebHistoryItem ** items,DATE calendarDate)502 HRESULT STDMETHODCALLTYPE WebHistory::orderedItemsLastVisitedOnDay(
503     /* [out][in] */ int* count,
504     /* [in] */ IWebHistoryItem** items,
505     /* [in] */ DATE calendarDate)
506 {
507     DateKey dateKey;
508     if (!findKey(&dateKey, MarshallingHelpers::DATEToCFAbsoluteTime(calendarDate))) {
509         *count = 0;
510         return 0;
511     }
512 
513     CFArrayRef entries = m_entriesByDate.get(dateKey).get();
514     if (!entries) {
515         *count = 0;
516         return 0;
517     }
518 
519     int newCount = CFArrayGetCount(entries);
520 
521     if (!items) {
522         *count = newCount;
523         return S_OK;
524     }
525 
526     if (*count < newCount) {
527         *count = newCount;
528         return E_FAIL;
529     }
530 
531     *count = newCount;
532     for (int i = 0; i < newCount; i++) {
533         IWebHistoryItem* item = (IWebHistoryItem*)CFArrayGetValueAtIndex(entries, i);
534         item->AddRef();
535         items[i] = item;
536     }
537 
538     return S_OK;
539 }
540 
allItems(int * count,IWebHistoryItem ** items)541 HRESULT STDMETHODCALLTYPE WebHistory::allItems(
542     /* [out][in] */ int* count,
543     /* [out][retval] */ IWebHistoryItem** items)
544 {
545     int entriesByURLCount = CFDictionaryGetCount(m_entriesByURL.get());
546 
547     if (!items) {
548         *count = entriesByURLCount;
549         return S_OK;
550     }
551 
552     if (*count < entriesByURLCount) {
553         *count = entriesByURLCount;
554         return E_FAIL;
555     }
556 
557     *count = entriesByURLCount;
558     CFDictionaryGetKeysAndValues(m_entriesByURL.get(), 0, (const void**)items);
559     for (int i = 0; i < entriesByURLCount; i++)
560         items[i]->AddRef();
561 
562     return S_OK;
563 }
564 
data(IStream ** stream)565 HRESULT WebHistory::data(IStream** stream)
566 {
567     if (!stream)
568         return E_POINTER;
569 
570     *stream = 0;
571 
572     RetainPtr<CFDataRef> historyData = data();
573     if (!historyData)
574         return S_OK;
575 
576     COMPtr<MemoryStream> result = MemoryStream::createInstance(SharedBuffer::wrapCFData(historyData.get()));
577     return result.copyRefTo(stream);
578 }
579 
setHistoryItemLimit(int limit)580 HRESULT STDMETHODCALLTYPE WebHistory::setHistoryItemLimit(
581     /* [in] */ int limit)
582 {
583     if (!m_preferences)
584         return E_FAIL;
585     return m_preferences->setHistoryItemLimit(limit);
586 }
587 
historyItemLimit(int * limit)588 HRESULT STDMETHODCALLTYPE WebHistory::historyItemLimit(
589     /* [retval][out] */ int* limit)
590 {
591     if (!m_preferences)
592         return E_FAIL;
593     return m_preferences->historyItemLimit(limit);
594 }
595 
setHistoryAgeInDaysLimit(int limit)596 HRESULT STDMETHODCALLTYPE WebHistory::setHistoryAgeInDaysLimit(
597     /* [in] */ int limit)
598 {
599     if (!m_preferences)
600         return E_FAIL;
601     return m_preferences->setHistoryAgeInDaysLimit(limit);
602 }
603 
historyAgeInDaysLimit(int * limit)604 HRESULT STDMETHODCALLTYPE WebHistory::historyAgeInDaysLimit(
605     /* [retval][out] */ int* limit)
606 {
607     if (!m_preferences)
608         return E_FAIL;
609     return m_preferences->historyAgeInDaysLimit(limit);
610 }
611 
removeItem(IWebHistoryItem * entry)612 HRESULT WebHistory::removeItem(IWebHistoryItem* entry)
613 {
614     HRESULT hr = S_OK;
615     BSTR urlBStr = 0;
616 
617     hr = entry->URLString(&urlBStr);
618     if (FAILED(hr))
619         return hr;
620 
621     RetainPtr<CFStringRef> urlString(AdoptCF, MarshallingHelpers::BSTRToCFStringRef(urlBStr));
622     SysFreeString(urlBStr);
623 
624     // If this exact object isn't stored, then make no change.
625     // FIXME: Is this the right behavior if this entry isn't present, but another entry for the same URL is?
626     // Maybe need to change the API to make something like removeEntryForURLString public instead.
627     IWebHistoryItem *matchingEntry = (IWebHistoryItem*)CFDictionaryGetValue(m_entriesByURL.get(), urlString.get());
628     if (matchingEntry != entry)
629         return E_FAIL;
630 
631     hr = removeItemForURLString(urlString.get());
632     if (FAILED(hr))
633         return hr;
634 
635     CFDictionaryPropertyBag* userInfo = createUserInfoFromHistoryItem(
636         getNotificationString(kWebHistoryItemsRemovedNotification), entry);
637     hr = postNotification(kWebHistoryItemsRemovedNotification, userInfo);
638     releaseUserInfo(userInfo);
639 
640     return hr;
641 }
642 
addItem(IWebHistoryItem * entry,bool discardDuplicate,bool * added)643 HRESULT WebHistory::addItem(IWebHistoryItem* entry, bool discardDuplicate, bool* added)
644 {
645     HRESULT hr = S_OK;
646 
647     if (!entry)
648         return E_FAIL;
649 
650     BSTR urlBStr = 0;
651     hr = entry->URLString(&urlBStr);
652     if (FAILED(hr))
653         return hr;
654 
655     RetainPtr<CFStringRef> urlString(AdoptCF, MarshallingHelpers::BSTRToCFStringRef(urlBStr));
656     SysFreeString(urlBStr);
657 
658     COMPtr<IWebHistoryItem> oldEntry((IWebHistoryItem*) CFDictionaryGetValue(
659         m_entriesByURL.get(), urlString.get()));
660 
661     if (oldEntry) {
662         if (discardDuplicate) {
663             if (added)
664                 *added = false;
665             return S_OK;
666         }
667 
668         removeItemForURLString(urlString.get());
669 
670         // If we already have an item with this URL, we need to merge info that drives the
671         // URL autocomplete heuristics from that item into the new one.
672         IWebHistoryItemPrivate* entryPriv;
673         hr = entry->QueryInterface(IID_IWebHistoryItemPrivate, (void**)&entryPriv);
674         if (SUCCEEDED(hr)) {
675             entryPriv->mergeAutoCompleteHints(oldEntry.get());
676             entryPriv->Release();
677         }
678     }
679 
680     hr = addItemToDateCaches(entry);
681     if (FAILED(hr))
682         return hr;
683 
684     CFDictionarySetValue(m_entriesByURL.get(), urlString.get(), entry);
685 
686     CFDictionaryPropertyBag* userInfo = createUserInfoFromHistoryItem(
687         getNotificationString(kWebHistoryItemsAddedNotification), entry);
688     hr = postNotification(kWebHistoryItemsAddedNotification, userInfo);
689     releaseUserInfo(userInfo);
690 
691     if (added)
692         *added = true;
693 
694     return hr;
695 }
696 
visitedURL(const KURL & url,const String & title,const String & httpMethod,bool wasFailure,bool increaseVisitCount)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     CFDictionaryPropertyBag* userInfo = createUserInfoFromHistoryItem(
746         getNotificationString(kWebHistoryItemsAddedNotification), entry);
747     postNotification(kWebHistoryItemsAddedNotification, userInfo);
748     releaseUserInfo(userInfo);
749 }
750 
itemForURLString(CFStringRef urlString,IWebHistoryItem ** item) const751 HRESULT WebHistory::itemForURLString(
752     /* [in] */ CFStringRef urlString,
753     /* [retval][out] */ IWebHistoryItem** item) const
754 {
755     if (!item)
756         return E_FAIL;
757     *item = 0;
758 
759     IWebHistoryItem* foundItem = (IWebHistoryItem*) CFDictionaryGetValue(m_entriesByURL.get(), urlString);
760     if (!foundItem)
761         return E_FAIL;
762 
763     foundItem->AddRef();
764     *item = foundItem;
765     return S_OK;
766 }
767 
itemForURL(BSTR url,IWebHistoryItem ** item)768 HRESULT STDMETHODCALLTYPE WebHistory::itemForURL(
769     /* [in] */ BSTR url,
770     /* [retval][out] */ IWebHistoryItem** item)
771 {
772     RetainPtr<CFStringRef> urlString(AdoptCF, MarshallingHelpers::BSTRToCFStringRef(url));
773     return itemForURLString(urlString.get(), item);
774 }
775 
removeItemForURLString(CFStringRef urlString)776 HRESULT WebHistory::removeItemForURLString(CFStringRef urlString)
777 {
778     IWebHistoryItem* entry = (IWebHistoryItem*) CFDictionaryGetValue(m_entriesByURL.get(), urlString);
779     if (!entry)
780         return E_FAIL;
781 
782     HRESULT hr = removeItemFromDateCaches(entry);
783     CFDictionaryRemoveValue(m_entriesByURL.get(), urlString);
784 
785     if (!CFDictionaryGetCount(m_entriesByURL.get()))
786         PageGroup::removeAllVisitedLinks();
787 
788     return hr;
789 }
790 
itemForURLString(const String & urlString) const791 COMPtr<IWebHistoryItem> WebHistory::itemForURLString(const String& urlString) const
792 {
793     RetainPtr<CFStringRef> urlCFString(AdoptCF, urlString.createCFString());
794     if (!urlCFString)
795         return 0;
796     COMPtr<IWebHistoryItem> item;
797     if (FAILED(itemForURLString(urlCFString.get(), &item)))
798         return 0;
799     return item;
800 }
801 
addItemToDateCaches(IWebHistoryItem * entry)802 HRESULT WebHistory::addItemToDateCaches(IWebHistoryItem* entry)
803 {
804     HRESULT hr = S_OK;
805 
806     DATE lastVisitedCOMTime;
807     entry->lastVisitedTimeInterval(&lastVisitedCOMTime);
808 
809     DateKey dateKey;
810     if (findKey(&dateKey, MarshallingHelpers::DATEToCFAbsoluteTime(lastVisitedCOMTime))) {
811         // other entries already exist for this date
812         hr = insertItem(entry, dateKey);
813     } else {
814         ASSERT(!m_entriesByDate.contains(dateKey));
815         // no other entries exist for this date
816         RetainPtr<CFMutableArrayRef> entryArray(AdoptCF,
817             CFArrayCreateMutable(0, 0, &MarshallingHelpers::kIUnknownArrayCallBacks));
818         CFArrayAppendValue(entryArray.get(), entry);
819         m_entriesByDate.set(dateKey, entryArray);
820         // Clear m_orderedLastVisitedDays so it will be regenerated when next requested.
821         m_orderedLastVisitedDays.clear();
822     }
823 
824     return hr;
825 }
826 
removeItemFromDateCaches(IWebHistoryItem * entry)827 HRESULT WebHistory::removeItemFromDateCaches(IWebHistoryItem* entry)
828 {
829     HRESULT hr = S_OK;
830 
831     DATE lastVisitedCOMTime;
832     entry->lastVisitedTimeInterval(&lastVisitedCOMTime);
833 
834     DateKey dateKey;
835     if (!findKey(&dateKey, MarshallingHelpers::DATEToCFAbsoluteTime(lastVisitedCOMTime)))
836         return E_FAIL;
837 
838     DateToEntriesMap::iterator found = m_entriesByDate.find(dateKey);
839     ASSERT(found != m_entriesByDate.end());
840     CFMutableArrayRef entriesForDate = found->second.get();
841 
842     CFIndex count = CFArrayGetCount(entriesForDate);
843     for (int i = count - 1; i >= 0; --i) {
844         if ((IWebHistoryItem*)CFArrayGetValueAtIndex(entriesForDate, i) == entry)
845             CFArrayRemoveValueAtIndex(entriesForDate, i);
846     }
847 
848     // remove this date entirely if there are no other entries on it
849     if (CFArrayGetCount(entriesForDate) == 0) {
850         m_entriesByDate.remove(found);
851         // Clear m_orderedLastVisitedDays so it will be regenerated when next requested.
852         m_orderedLastVisitedDays.clear();
853     }
854 
855     return hr;
856 }
857 
timeIntervalForBeginningOfDay(CFAbsoluteTime day)858 WebHistory::DateKey timeIntervalForBeginningOfDay(CFAbsoluteTime day)
859 {
860     RetainPtr<CFTimeZoneRef> timeZone(AdoptCF, CFTimeZoneCopyDefault());
861     CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(day, timeZone.get());
862     date.hour = 0;
863     date.minute = 0;
864     date.second = 0;
865     CFAbsoluteTime result = CFGregorianDateGetAbsoluteTime(date, timeZone.get());
866 
867     // Converting from double to int64_t is safe here as NSDate's useful range
868     // is -2**48 .. 2**47 which will safely fit in an int64_t.
869     return static_cast<WebHistory::DateKey>(result);
870 }
871 
872 // Returns whether the day is already in the list of days,
873 // and fills in *key with the found or proposed key.
findKey(DateKey * key,CFAbsoluteTime forDay)874 bool WebHistory::findKey(DateKey* key, CFAbsoluteTime forDay)
875 {
876     ASSERT_ARG(key, key);
877 
878     *key = timeIntervalForBeginningOfDay(forDay);
879     return m_entriesByDate.contains(*key);
880 }
881 
insertItem(IWebHistoryItem * entry,DateKey dateKey)882 HRESULT WebHistory::insertItem(IWebHistoryItem* entry, DateKey dateKey)
883 {
884     ASSERT_ARG(entry, entry);
885     ASSERT_ARG(dateKey, m_entriesByDate.contains(dateKey));
886 
887     HRESULT hr = S_OK;
888 
889     if (!entry)
890         return E_FAIL;
891 
892     DATE entryTime;
893     entry->lastVisitedTimeInterval(&entryTime);
894     CFMutableArrayRef entriesForDate = m_entriesByDate.get(dateKey).get();
895     unsigned count = CFArrayGetCount(entriesForDate);
896 
897     // The entries for each day are stored in a sorted array with the most recent entry first
898     // Check for the common cases of the entry being newer than all existing entries or the first entry of the day
899     bool isNewerThanAllEntries = false;
900     if (count) {
901         IWebHistoryItem* item = const_cast<IWebHistoryItem*>(static_cast<const IWebHistoryItem*>(CFArrayGetValueAtIndex(entriesForDate, 0)));
902         DATE itemTime;
903         isNewerThanAllEntries = SUCCEEDED(item->lastVisitedTimeInterval(&itemTime)) && itemTime < entryTime;
904     }
905     if (!count || isNewerThanAllEntries) {
906         CFArrayInsertValueAtIndex(entriesForDate, 0, entry);
907         return S_OK;
908     }
909 
910     // .. or older than all existing entries
911     bool isOlderThanAllEntries = false;
912     if (count > 0) {
913         IWebHistoryItem* item = const_cast<IWebHistoryItem*>(static_cast<const IWebHistoryItem*>(CFArrayGetValueAtIndex(entriesForDate, count - 1)));
914         DATE itemTime;
915         isOlderThanAllEntries = SUCCEEDED(item->lastVisitedTimeInterval(&itemTime)) && itemTime >= entryTime;
916     }
917     if (isOlderThanAllEntries) {
918         CFArrayInsertValueAtIndex(entriesForDate, count, entry);
919         return S_OK;
920     }
921 
922     unsigned low = 0;
923     unsigned high = count;
924     while (low < high) {
925         unsigned mid = low + (high - low) / 2;
926         IWebHistoryItem* item = const_cast<IWebHistoryItem*>(static_cast<const IWebHistoryItem*>(CFArrayGetValueAtIndex(entriesForDate, mid)));
927         DATE itemTime;
928         if (FAILED(item->lastVisitedTimeInterval(&itemTime)))
929             return E_FAIL;
930 
931         if (itemTime >= entryTime)
932             low = mid + 1;
933         else
934             high = mid;
935     }
936 
937     // low is now the index of the first entry that is older than entryDate
938     CFArrayInsertValueAtIndex(entriesForDate, low, entry);
939     return S_OK;
940 }
941 
timeToDate(CFAbsoluteTime time)942 CFAbsoluteTime WebHistory::timeToDate(CFAbsoluteTime time)
943 {
944     // can't just divide/round since the day boundaries depend on our current time zone
945     const double secondsPerDay = 60 * 60 * 24;
946     CFTimeZoneRef timeZone = CFTimeZoneCopySystem();
947     CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(time, timeZone);
948     date.hour = date.minute = 0;
949     date.second = 0.0;
950     CFAbsoluteTime timeInDays = CFGregorianDateGetAbsoluteTime(date, timeZone);
951     if (areEqualOrClose(time - timeInDays, secondsPerDay))
952         timeInDays += secondsPerDay;
953     return timeInDays;
954 }
955 
956 // Return a date that marks the age limit for history entries saved to or
957 // loaded from disk. Any entry older than this item should be rejected.
ageLimitDate(CFAbsoluteTime * time)958 HRESULT WebHistory::ageLimitDate(CFAbsoluteTime* time)
959 {
960     // get the current date as a CFAbsoluteTime
961     CFAbsoluteTime currentDate = timeToDate(CFAbsoluteTimeGetCurrent());
962 
963     CFGregorianUnits ageLimit = {0};
964     int historyLimitDays;
965     HRESULT hr = historyAgeInDaysLimit(&historyLimitDays);
966     if (FAILED(hr))
967         return hr;
968     ageLimit.days = -historyLimitDays;
969     *time = CFAbsoluteTimeAddGregorianUnits(currentDate, CFTimeZoneCopySystem(), ageLimit);
970     return S_OK;
971 }
972 
addVisitedLinkToPageGroup(const void * key,const void *,void * context)973 static void addVisitedLinkToPageGroup(const void* key, const void*, void* context)
974 {
975     CFStringRef url = static_cast<CFStringRef>(key);
976     PageGroup* group = static_cast<PageGroup*>(context);
977 
978     CFIndex length = CFStringGetLength(url);
979     const UChar* characters = reinterpret_cast<const UChar*>(CFStringGetCharactersPtr(url));
980     if (characters)
981         group->addVisitedLink(characters, length);
982     else {
983         Vector<UChar, 512> buffer(length);
984         CFStringGetCharacters(url, CFRangeMake(0, length), reinterpret_cast<UniChar*>(buffer.data()));
985         group->addVisitedLink(buffer.data(), length);
986     }
987 }
988 
addVisitedLinksToPageGroup(PageGroup & group)989 void WebHistory::addVisitedLinksToPageGroup(PageGroup& group)
990 {
991     CFDictionaryApplyFunction(m_entriesByURL.get(), addVisitedLinkToPageGroup, &group);
992 }
993 
data() const994 RetainPtr<CFDataRef> WebHistory::data() const
995 {
996     if (m_entriesByDate.isEmpty())
997         return 0;
998 
999     WebHistoryWriter writer(m_entriesByDate);
1000     writer.writePropertyList();
1001     return writer.releaseData();
1002 }
1003