• 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 "WebKit.h"
32 #include "MarshallingHelpers.h"
33 #include "WebHistoryItem.h"
34 #include "WebKit.h"
35 #include "WebNotificationCenter.h"
36 #include "WebPreferences.h"
37 #include <CoreFoundation/CoreFoundation.h>
38 #pragma warning( push, 0 )
39 #include <wtf/Vector.h>
40 #include <WebCore/KURL.h>
41 #include <WebCore/PageGroup.h>
42 #include <WebCore/HistoryItem.h>
43 #pragma warning( pop )
44 
45 using namespace WebCore;
46 
47 CFStringRef DatesArrayKey = CFSTR("WebHistoryDates");
48 CFStringRef FileVersionKey = CFSTR("WebHistoryFileVersion");
49 
50 #define currentFileVersion 1
51 
areEqualOrClose(double d1,double d2)52 static bool areEqualOrClose(double d1, double d2)
53 {
54     double diff = d1-d2;
55     return (diff < .000001 && diff > -.000001);
56 }
57 
createUserInfoFromArray(BSTR notificationStr,CFArrayRef arrayItem)58 static CFDictionaryPropertyBag* createUserInfoFromArray(BSTR notificationStr, CFArrayRef arrayItem)
59 {
60     RetainPtr<CFMutableDictionaryRef> dictionary(AdoptCF,
61         CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
62 
63     RetainPtr<CFStringRef> key(AdoptCF, MarshallingHelpers::BSTRToCFStringRef(notificationStr));
64     CFDictionaryAddValue(dictionary.get(), key.get(), arrayItem);
65 
66     CFDictionaryPropertyBag* result = CFDictionaryPropertyBag::createInstance();
67     result->setDictionary(dictionary.get());
68     return result;
69 }
70 
createUserInfoFromHistoryItem(BSTR notificationStr,IWebHistoryItem * item)71 static CFDictionaryPropertyBag* createUserInfoFromHistoryItem(BSTR notificationStr, IWebHistoryItem* item)
72 {
73     // reference counting of item added to the array is managed by the CFArray value callbacks
74     RetainPtr<CFArrayRef> itemList(AdoptCF, CFArrayCreate(0, (const void**) &item, 1, &MarshallingHelpers::kIUnknownArrayCallBacks));
75     CFDictionaryPropertyBag* info = createUserInfoFromArray(notificationStr, itemList.get());
76     return info;
77 }
78 
releaseUserInfo(CFDictionaryPropertyBag * userInfo)79 static void releaseUserInfo(CFDictionaryPropertyBag* userInfo)
80 {
81     // free the dictionary
82     userInfo->setDictionary(0);
83     int result = userInfo->Release();
84     (void)result;
85     ASSERT(result == 0);   // make sure no one else holds a reference to the userInfo.
86 }
87 
88 // WebHistory -----------------------------------------------------------------
89 
WebHistory()90 WebHistory::WebHistory()
91 : m_refCount(0)
92 , m_preferences(0)
93 {
94     gClassCount++;
95     gClassNameCount.add("WebHistory");
96 
97     m_entriesByURL.adoptCF(CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &MarshallingHelpers::kIUnknownDictionaryValueCallBacks));
98     m_datesWithEntries.adoptCF(CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks));
99     m_entriesByDate.adoptCF(CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks));
100 
101     m_preferences = WebPreferences::sharedStandardPreferences();
102 }
103 
~WebHistory()104 WebHistory::~WebHistory()
105 {
106     gClassCount--;
107     gClassNameCount.remove("WebHistory");
108 }
109 
createInstance()110 WebHistory* WebHistory::createInstance()
111 {
112     WebHistory* instance = new WebHistory();
113     instance->AddRef();
114     return instance;
115 }
116 
postNotification(NotificationType notifyType,IPropertyBag * userInfo)117 HRESULT WebHistory::postNotification(NotificationType notifyType, IPropertyBag* userInfo /*=0*/)
118 {
119     IWebNotificationCenter* nc = WebNotificationCenter::defaultCenterInternal();
120     HRESULT hr = nc->postNotificationName(getNotificationString(notifyType), static_cast<IWebHistory*>(this), userInfo);
121     if (FAILED(hr))
122         return hr;
123 
124     return S_OK;
125 }
126 
getNotificationString(NotificationType notifyType)127 BSTR WebHistory::getNotificationString(NotificationType notifyType)
128 {
129     static BSTR keys[6] = {0};
130     if (!keys[0]) {
131         keys[0] = SysAllocString(WebHistoryItemsAddedNotification);
132         keys[1] = SysAllocString(WebHistoryItemsRemovedNotification);
133         keys[2] = SysAllocString(WebHistoryAllItemsRemovedNotification);
134         keys[3] = SysAllocString(WebHistoryLoadedNotification);
135         keys[4] = SysAllocString(WebHistoryItemsDiscardedWhileLoadingNotification);
136         keys[5] = SysAllocString(WebHistorySavedNotification);
137     }
138     return keys[notifyType];
139 }
140 
141 // IUnknown -------------------------------------------------------------------
142 
QueryInterface(REFIID riid,void ** ppvObject)143 HRESULT STDMETHODCALLTYPE WebHistory::QueryInterface(REFIID riid, void** ppvObject)
144 {
145     *ppvObject = 0;
146     if (IsEqualGUID(riid, CLSID_WebHistory))
147         *ppvObject = this;
148     else if (IsEqualGUID(riid, IID_IUnknown))
149         *ppvObject = static_cast<IWebHistory*>(this);
150     else if (IsEqualGUID(riid, IID_IWebHistory))
151         *ppvObject = static_cast<IWebHistory*>(this);
152     else if (IsEqualGUID(riid, IID_IWebHistoryPrivate))
153         *ppvObject = static_cast<IWebHistoryPrivate*>(this);
154     else
155         return E_NOINTERFACE;
156 
157     AddRef();
158     return S_OK;
159 }
160 
AddRef(void)161 ULONG STDMETHODCALLTYPE WebHistory::AddRef(void)
162 {
163     return ++m_refCount;
164 }
165 
Release(void)166 ULONG STDMETHODCALLTYPE WebHistory::Release(void)
167 {
168     ULONG newRef = --m_refCount;
169     if (!newRef)
170         delete(this);
171 
172     return newRef;
173 }
174 
175 // IWebHistory ----------------------------------------------------------------
176 
sharedHistoryStorage()177 static inline COMPtr<WebHistory>& sharedHistoryStorage()
178 {
179     static COMPtr<WebHistory> sharedHistory;
180     return sharedHistory;
181 }
182 
sharedHistory()183 WebHistory* WebHistory::sharedHistory()
184 {
185     return sharedHistoryStorage().get();
186 }
187 
optionalSharedHistory(IWebHistory ** history)188 HRESULT STDMETHODCALLTYPE WebHistory::optionalSharedHistory(
189     /* [retval][out] */ IWebHistory** history)
190 {
191     *history = sharedHistory();
192     if (*history)
193         (*history)->AddRef();
194     return S_OK;
195 }
196 
setOptionalSharedHistory(IWebHistory * history)197 HRESULT STDMETHODCALLTYPE WebHistory::setOptionalSharedHistory(
198     /* [in] */ IWebHistory* history)
199 {
200     if (sharedHistoryStorage() == history)
201         return S_OK;
202     sharedHistoryStorage().query(history);
203     PageGroup::setShouldTrackVisitedLinks(sharedHistoryStorage());
204     PageGroup::removeAllVisitedLinks();
205     return S_OK;
206 }
207 
loadFromURL(BSTR url,IWebError ** error,BOOL * succeeded)208 HRESULT STDMETHODCALLTYPE WebHistory::loadFromURL(
209     /* [in] */ BSTR url,
210     /* [out] */ IWebError** error,
211     /* [retval][out] */ BOOL* succeeded)
212 {
213     HRESULT hr = S_OK;
214     RetainPtr<CFMutableArrayRef> discardedItems(AdoptCF,
215         CFArrayCreateMutable(0, 0, &MarshallingHelpers::kIUnknownArrayCallBacks));
216 
217     RetainPtr<CFURLRef> urlRef(AdoptCF, MarshallingHelpers::BSTRToCFURLRef(url));
218 
219     hr = loadHistoryGutsFromURL(urlRef.get(), discardedItems.get(), error);
220     if (FAILED(hr))
221         goto exit;
222 
223     hr = postNotification(kWebHistoryLoadedNotification);
224     if (FAILED(hr))
225         goto exit;
226 
227     if (CFArrayGetCount(discardedItems.get()) > 0) {
228         CFDictionaryPropertyBag* userInfo = createUserInfoFromArray(getNotificationString(kWebHistoryItemsDiscardedWhileLoadingNotification), discardedItems.get());
229         hr = postNotification(kWebHistoryItemsDiscardedWhileLoadingNotification, userInfo);
230         releaseUserInfo(userInfo);
231         if (FAILED(hr))
232             goto exit;
233     }
234 
235 exit:
236     if (succeeded)
237         *succeeded = SUCCEEDED(hr);
238     return hr;
239 }
240 
createHistoryListFromStream(CFReadStreamRef stream,CFPropertyListFormat format)241 static CFDictionaryRef createHistoryListFromStream(CFReadStreamRef stream, CFPropertyListFormat format)
242 {
243     return (CFDictionaryRef)CFPropertyListCreateFromStream(0, stream, 0, kCFPropertyListImmutable, &format, 0);
244 }
245 
loadHistoryGutsFromURL(CFURLRef url,CFMutableArrayRef discardedItems,IWebError **)246 HRESULT WebHistory::loadHistoryGutsFromURL(CFURLRef url, CFMutableArrayRef discardedItems, IWebError** /*error*/) //FIXME
247 {
248     CFPropertyListFormat format = kCFPropertyListBinaryFormat_v1_0 | kCFPropertyListXMLFormat_v1_0;
249     HRESULT hr = S_OK;
250     int numberOfItemsLoaded = 0;
251 
252     RetainPtr<CFReadStreamRef> stream(AdoptCF, CFReadStreamCreateWithFile(0, url));
253     if (!stream)
254         return E_FAIL;
255 
256     if (!CFReadStreamOpen(stream.get()))
257         return E_FAIL;
258 
259     RetainPtr<CFDictionaryRef> historyList(AdoptCF, createHistoryListFromStream(stream.get(), format));
260     CFReadStreamClose(stream.get());
261 
262     if (!historyList)
263         return E_FAIL;
264 
265     CFNumberRef fileVersionObject = (CFNumberRef)CFDictionaryGetValue(historyList.get(), FileVersionKey);
266     int fileVersion;
267     if (!CFNumberGetValue(fileVersionObject, kCFNumberIntType, &fileVersion))
268         return E_FAIL;
269 
270     if (fileVersion > currentFileVersion)
271         return E_FAIL;
272 
273     CFArrayRef datesArray = (CFArrayRef)CFDictionaryGetValue(historyList.get(), DatesArrayKey);
274 
275     int itemCountLimit;
276     hr = historyItemLimit(&itemCountLimit);
277     if (FAILED(hr))
278         return hr;
279 
280     CFAbsoluteTime limitDate;
281     hr = ageLimitDate(&limitDate);
282     if (FAILED(hr))
283         return hr;
284 
285     bool ageLimitPassed = false;
286     bool itemLimitPassed = false;
287 
288     CFIndex itemCount = CFArrayGetCount(datesArray);
289     for (CFIndex i = 0; i < itemCount; ++i) {
290         CFDictionaryRef itemAsDictionary = (CFDictionaryRef)CFArrayGetValueAtIndex(datesArray, i);
291         COMPtr<WebHistoryItem> item(AdoptCOM, WebHistoryItem::createInstance());
292         hr = item->initFromDictionaryRepresentation((void*)itemAsDictionary);
293         if (FAILED(hr))
294             return hr;
295 
296         // item without URL is useless; data on disk must have been bad; ignore
297         BOOL hasURL;
298         hr = item->hasURLString(&hasURL);
299         if (FAILED(hr))
300             return hr;
301 
302         if (hasURL) {
303             // Test against date limit. Since the items are ordered newest to oldest, we can stop comparing
304             // once we've found the first item that's too old.
305             if (!ageLimitPassed) {
306                 DATE lastVisitedTime;
307                 hr = item->lastVisitedTimeInterval(&lastVisitedTime);
308                 if (FAILED(hr))
309                     return hr;
310                 if (timeToDate(MarshallingHelpers::DATEToCFAbsoluteTime(lastVisitedTime)) <= limitDate)
311                     ageLimitPassed = true;
312             }
313             if (ageLimitPassed || itemLimitPassed)
314                 CFArrayAppendValue(discardedItems, item.get());
315             else {
316                 bool added;
317                 addItem(item.get(), true, &added); // ref is added inside addItem
318                 if (added)
319                     ++numberOfItemsLoaded;
320                 if (numberOfItemsLoaded == itemCountLimit)
321                     itemLimitPassed = true;
322             }
323         }
324     }
325     return hr;
326 }
327 
saveToURL(BSTR url,IWebError ** error,BOOL * succeeded)328 HRESULT STDMETHODCALLTYPE WebHistory::saveToURL(
329     /* [in] */ BSTR url,
330     /* [out] */ IWebError** error,
331     /* [retval][out] */ BOOL* succeeded)
332 {
333     HRESULT hr = S_OK;
334     RetainPtr<CFURLRef> urlRef(AdoptCF, MarshallingHelpers::BSTRToCFURLRef(url));
335 
336     hr = saveHistoryGuts(urlRef.get(), error);
337 
338     if (succeeded)
339         *succeeded = SUCCEEDED(hr);
340     if (SUCCEEDED(hr))
341         hr = postNotification(kWebHistorySavedNotification);
342 
343     return hr;
344 }
345 
saveHistoryGuts(CFURLRef url,IWebError ** error)346 HRESULT WebHistory::saveHistoryGuts(CFURLRef url, IWebError** error)
347 {
348     HRESULT hr = S_OK;
349 
350     // FIXME:  Correctly report error when new API is ready.
351     if (error)
352         *error = 0;
353 
354     RetainPtr<CFWriteStreamRef> stream(AdoptCF, CFWriteStreamCreateWithFile(kCFAllocatorDefault, url));
355     if (!stream)
356         return E_FAIL;
357 
358     CFMutableArrayRef rawEntries;
359     hr = datesArray(&rawEntries);
360     if (FAILED(hr))
361         return hr;
362     RetainPtr<CFMutableArrayRef> entries(AdoptCF, rawEntries);
363 
364     // create the outer dictionary
365     CFTypeRef keys[2];
366     CFTypeRef values[2];
367     keys[0]   = DatesArrayKey;
368     values[0] = entries.get();
369     keys[1]   = FileVersionKey;
370 
371     int version = currentFileVersion;
372     RetainPtr<CFNumberRef> versionCF(AdoptCF, CFNumberCreate(0, kCFNumberIntType, &version));
373     values[1] = versionCF.get();
374 
375     RetainPtr<CFDictionaryRef> dictionary(AdoptCF,
376         CFDictionaryCreate(0, keys, values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
377 
378     if (!CFWriteStreamOpen(stream.get()))
379         return E_FAIL;
380 
381     if (!CFPropertyListWriteToStream(dictionary.get(), stream.get(), kCFPropertyListXMLFormat_v1_0, 0))
382         hr = E_FAIL;
383 
384     CFWriteStreamClose(stream.get());
385 
386     return hr;
387 }
388 
datesArray(CFMutableArrayRef * datesArray)389 HRESULT WebHistory::datesArray(CFMutableArrayRef* datesArray)
390 {
391     HRESULT hr = S_OK;
392 
393     RetainPtr<CFMutableArrayRef> result(AdoptCF, CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks));
394 
395     // for each date with entries
396     int dateCount = CFArrayGetCount(m_entriesByDate.get());
397     for (int i = 0; i < dateCount; ++i) {
398         // get the entries for that date
399         CFArrayRef entries = (CFArrayRef)CFArrayGetValueAtIndex(m_entriesByDate.get(), i);
400         int entriesCount = CFArrayGetCount(entries);
401         for (int j = entriesCount - 1; j >= 0; --j) {
402             IWebHistoryItem* item = (IWebHistoryItem*) CFArrayGetValueAtIndex(entries, j);
403             IWebHistoryItemPrivate* webHistoryItem;
404             hr = item->QueryInterface(IID_IWebHistoryItemPrivate, (void**)&webHistoryItem);
405             if (FAILED(hr))
406                 return E_FAIL;
407 
408             CFDictionaryRef itemDict;
409             hr = webHistoryItem->dictionaryRepresentation((void**)&itemDict);
410             webHistoryItem->Release();
411             if (FAILED(hr))
412                 return E_FAIL;
413 
414             CFArrayAppendValue(result.get(), itemDict);
415             CFRelease(itemDict);
416         }
417     }
418 
419     if (SUCCEEDED(hr))
420         *datesArray = result.releaseRef();
421     return hr;
422 }
423 
addItems(int itemCount,IWebHistoryItem ** items)424 HRESULT STDMETHODCALLTYPE WebHistory::addItems(
425     /* [in] */ int itemCount,
426     /* [in] */ IWebHistoryItem** items)
427 {
428     // There is no guarantee that the incoming entries are in any particular
429     // order, but if this is called with a set of entries that were created by
430     // iterating through the results of orderedLastVisitedDays and orderedItemsLastVisitedOnDay
431     // then they will be ordered chronologically from newest to oldest. We can make adding them
432     // faster (fewer compares) by inserting them from oldest to newest.
433 
434     HRESULT hr;
435     for (int i = itemCount - 1; i >= 0; --i) {
436         hr = addItem(items[i], false, 0);
437         if (FAILED(hr))
438             return hr;
439     }
440 
441     return S_OK;
442 }
443 
removeItems(int itemCount,IWebHistoryItem ** items)444 HRESULT STDMETHODCALLTYPE WebHistory::removeItems(
445     /* [in] */ int itemCount,
446     /* [in] */ IWebHistoryItem** items)
447 {
448     HRESULT hr;
449     for (int i = 0; i < itemCount; ++i) {
450         hr = removeItem(items[i]);
451         if (FAILED(hr))
452             return hr;
453     }
454 
455     return S_OK;
456 }
457 
removeAllItems(void)458 HRESULT STDMETHODCALLTYPE WebHistory::removeAllItems( void)
459 {
460     CFArrayRemoveAllValues(m_entriesByDate.get());
461     CFArrayRemoveAllValues(m_datesWithEntries.get());
462 
463     CFIndex itemCount = CFDictionaryGetCount(m_entriesByURL.get());
464     Vector<IWebHistoryItem*> itemsVector(itemCount);
465     CFDictionaryGetKeysAndValues(m_entriesByURL.get(), 0, (const void**)itemsVector.data());
466     RetainPtr<CFArrayRef> allItems(AdoptCF, CFArrayCreate(kCFAllocatorDefault, (const void**)itemsVector.data(), itemCount, &MarshallingHelpers::kIUnknownArrayCallBacks));
467 
468     CFDictionaryRemoveAllValues(m_entriesByURL.get());
469 
470     PageGroup::removeAllVisitedLinks();
471 
472     CFDictionaryPropertyBag* userInfo = createUserInfoFromArray(getNotificationString(kWebHistoryAllItemsRemovedNotification), allItems.get());
473     return postNotification(kWebHistoryAllItemsRemovedNotification, userInfo);
474 }
475 
orderedLastVisitedDays(int * count,DATE * calendarDates)476 HRESULT STDMETHODCALLTYPE WebHistory::orderedLastVisitedDays(
477     /* [out][in] */ int* count,
478     /* [in] */ DATE* calendarDates)
479 {
480     int dateCount = CFArrayGetCount(m_datesWithEntries.get());
481     if (!calendarDates) {
482         *count = dateCount;
483         return S_OK;
484     }
485 
486     if (*count < dateCount) {
487         *count = dateCount;
488         return E_FAIL;
489     }
490 
491     *count = dateCount;
492     for (int i = 0; i < dateCount; i++) {
493         CFNumberRef absoluteTimeNumberRef = (CFNumberRef)CFArrayGetValueAtIndex(m_datesWithEntries.get(), i);
494         CFAbsoluteTime absoluteTime;
495         if (!CFNumberGetValue(absoluteTimeNumberRef, kCFNumberDoubleType, &absoluteTime))
496             return E_FAIL;
497         calendarDates[i] = MarshallingHelpers::CFAbsoluteTimeToDATE(absoluteTime);
498     }
499 
500     return S_OK;
501 }
502 
orderedItemsLastVisitedOnDay(int * count,IWebHistoryItem ** items,DATE calendarDate)503 HRESULT STDMETHODCALLTYPE WebHistory::orderedItemsLastVisitedOnDay(
504     /* [out][in] */ int* count,
505     /* [in] */ IWebHistoryItem** items,
506     /* [in] */ DATE calendarDate)
507 {
508     int index;
509     if (!findIndex(&index, MarshallingHelpers::DATEToCFAbsoluteTime(calendarDate))) {
510         *count = 0;
511         return 0;
512     }
513 
514     CFArrayRef entries = (CFArrayRef)CFArrayGetValueAtIndex(m_entriesByDate.get(), index);
515     if (!entries) {
516         *count = 0;
517         return 0;
518     }
519 
520     int newCount = CFArrayGetCount(entries);
521 
522     if (!items) {
523         *count = newCount;
524         return S_OK;
525     }
526 
527     if (*count < newCount) {
528         *count = newCount;
529         return E_FAIL;
530     }
531 
532     *count = newCount;
533     for (int i = 0; i < newCount; i++) {
534         IWebHistoryItem* item = (IWebHistoryItem*)CFArrayGetValueAtIndex(entries, i);
535         item->AddRef();
536         items[newCount - i - 1] = item; // reverse when inserting to get the list sorted oldest to newest
537     }
538 
539     return S_OK;
540 }
541 
allItems(int * count,IWebHistoryItem ** items)542 HRESULT STDMETHODCALLTYPE WebHistory::allItems(
543     /* [out][in] */ int* count,
544     /* [out][retval] */ IWebHistoryItem** items)
545 {
546     int entriesByURLCount = CFDictionaryGetCount(m_entriesByURL.get());
547 
548     if (!items) {
549         *count = entriesByURLCount;
550         return S_OK;
551     }
552 
553     if (*count < entriesByURLCount) {
554         *count = entriesByURLCount;
555         return E_FAIL;
556     }
557 
558     *count = entriesByURLCount;
559     CFDictionaryGetKeysAndValues(m_entriesByURL.get(), 0, (const void**)items);
560     for (int i = 0; i < entriesByURLCount; i++)
561         items[i]->AddRef();
562 
563     return S_OK;
564 }
565 
setHistoryItemLimit(int limit)566 HRESULT STDMETHODCALLTYPE WebHistory::setHistoryItemLimit(
567     /* [in] */ int limit)
568 {
569     if (!m_preferences)
570         return E_FAIL;
571     return m_preferences->setHistoryItemLimit(limit);
572 }
573 
historyItemLimit(int * limit)574 HRESULT STDMETHODCALLTYPE WebHistory::historyItemLimit(
575     /* [retval][out] */ int* limit)
576 {
577     if (!m_preferences)
578         return E_FAIL;
579     return m_preferences->historyItemLimit(limit);
580 }
581 
setHistoryAgeInDaysLimit(int limit)582 HRESULT STDMETHODCALLTYPE WebHistory::setHistoryAgeInDaysLimit(
583     /* [in] */ int limit)
584 {
585     if (!m_preferences)
586         return E_FAIL;
587     return m_preferences->setHistoryAgeInDaysLimit(limit);
588 }
589 
historyAgeInDaysLimit(int * limit)590 HRESULT STDMETHODCALLTYPE WebHistory::historyAgeInDaysLimit(
591     /* [retval][out] */ int* limit)
592 {
593     if (!m_preferences)
594         return E_FAIL;
595     return m_preferences->historyAgeInDaysLimit(limit);
596 }
597 
removeItem(IWebHistoryItem * entry)598 HRESULT WebHistory::removeItem(IWebHistoryItem* entry)
599 {
600     HRESULT hr = S_OK;
601     BSTR urlBStr = 0;
602 
603     hr = entry->URLString(&urlBStr);
604     if (FAILED(hr))
605         return hr;
606 
607     RetainPtr<CFStringRef> urlString(AdoptCF, MarshallingHelpers::BSTRToCFStringRef(urlBStr));
608     SysFreeString(urlBStr);
609 
610     // If this exact object isn't stored, then make no change.
611     // FIXME: Is this the right behavior if this entry isn't present, but another entry for the same URL is?
612     // Maybe need to change the API to make something like removeEntryForURLString public instead.
613     IWebHistoryItem *matchingEntry = (IWebHistoryItem*)CFDictionaryGetValue(m_entriesByURL.get(), urlString.get());
614     if (matchingEntry != entry)
615         return E_FAIL;
616 
617     hr = removeItemForURLString(urlString.get());
618     if (FAILED(hr))
619         return hr;
620 
621     CFDictionaryPropertyBag* userInfo = createUserInfoFromHistoryItem(
622         getNotificationString(kWebHistoryItemsRemovedNotification), entry);
623     hr = postNotification(kWebHistoryItemsRemovedNotification, userInfo);
624     releaseUserInfo(userInfo);
625 
626     return hr;
627 }
628 
addItem(IWebHistoryItem * entry,bool discardDuplicate,bool * added)629 HRESULT WebHistory::addItem(IWebHistoryItem* entry, bool discardDuplicate, bool* added)
630 {
631     HRESULT hr = S_OK;
632 
633     if (!entry)
634         return E_FAIL;
635 
636     BSTR urlBStr = 0;
637     hr = entry->URLString(&urlBStr);
638     if (FAILED(hr))
639         return hr;
640 
641     RetainPtr<CFStringRef> urlString(AdoptCF, MarshallingHelpers::BSTRToCFStringRef(urlBStr));
642     SysFreeString(urlBStr);
643 
644     COMPtr<IWebHistoryItem> oldEntry((IWebHistoryItem*) CFDictionaryGetValue(
645         m_entriesByURL.get(), urlString.get()));
646 
647     if (oldEntry) {
648         if (discardDuplicate) {
649             if (added)
650                 *added = false;
651             return S_OK;
652         }
653 
654         removeItemForURLString(urlString.get());
655 
656         // If we already have an item with this URL, we need to merge info that drives the
657         // URL autocomplete heuristics from that item into the new one.
658         IWebHistoryItemPrivate* entryPriv;
659         hr = entry->QueryInterface(IID_IWebHistoryItemPrivate, (void**)&entryPriv);
660         if (SUCCEEDED(hr)) {
661             entryPriv->mergeAutoCompleteHints(oldEntry.get());
662             entryPriv->Release();
663         }
664     }
665 
666     hr = addItemToDateCaches(entry);
667     if (FAILED(hr))
668         return hr;
669 
670     CFDictionarySetValue(m_entriesByURL.get(), urlString.get(), entry);
671 
672     CFDictionaryPropertyBag* userInfo = createUserInfoFromHistoryItem(
673         getNotificationString(kWebHistoryItemsAddedNotification), entry);
674     hr = postNotification(kWebHistoryItemsAddedNotification, userInfo);
675     releaseUserInfo(userInfo);
676 
677     if (added)
678         *added = true;
679 
680     return hr;
681 }
682 
visitedURL(const KURL & url,const String & title,const String & httpMethod,bool wasFailure,const KURL & serverRedirectURL,bool isClientRedirect)683 void WebHistory::visitedURL(const KURL& url, const String& title, const String& httpMethod, bool wasFailure, const KURL& serverRedirectURL, bool isClientRedirect)
684 {
685     if (isClientRedirect) {
686         ASSERT(serverRedirectURL.isEmpty());
687         if (m_lastVisitedEntry)
688             m_lastVisitedEntry->historyItem()->addRedirectURL(url.string());
689     }
690 
691     RetainPtr<CFStringRef> urlString(AdoptCF, url.string().createCFString());
692 
693     IWebHistoryItem* entry = (IWebHistoryItem*) CFDictionaryGetValue(m_entriesByURL.get(), urlString.get());
694 
695     if (entry) {
696         COMPtr<IWebHistoryItemPrivate> entryPrivate(Query, entry);
697         if (!entryPrivate)
698             return;
699 
700         // Remove the item from date caches before changing its last visited date.  Otherwise we might get duplicate entries
701         // as seen in <rdar://problem/6570573>.
702         removeItemFromDateCaches(entry);
703         entryPrivate->visitedWithTitle(BString(title));
704     } else {
705         COMPtr<WebHistoryItem> item(AdoptCOM, WebHistoryItem::createInstance());
706         if (!item)
707             return;
708 
709         entry = item.get();
710 
711         SYSTEMTIME currentTime;
712         GetSystemTime(&currentTime);
713         DATE lastVisited;
714         if (!SystemTimeToVariantTime(&currentTime, &lastVisited))
715             return;
716 
717         if (FAILED(entry->initWithURLString(BString(url.string()), BString(title), lastVisited)))
718             return;
719 
720         item->recordInitialVisit();
721 
722         CFDictionarySetValue(m_entriesByURL.get(), urlString.get(), entry);
723     }
724 
725     addItemToDateCaches(entry);
726 
727     m_lastVisitedEntry.query(entry);
728 
729     COMPtr<IWebHistoryItemPrivate> entryPrivate(Query, entry);
730     if (!entryPrivate)
731         return;
732 
733     entryPrivate->setLastVisitWasFailure(wasFailure);
734     if (!httpMethod.isEmpty())
735         entryPrivate->setLastVisitWasHTTPNonGet(!equalIgnoringCase(httpMethod, "GET"));
736 
737     if (!serverRedirectURL.isEmpty()) {
738         ASSERT(!isClientRedirect);
739         COMPtr<WebHistoryItem> item(Query, entry);
740         item->historyItem()->addRedirectURL(serverRedirectURL);
741     }
742 
743     CFDictionaryPropertyBag* userInfo = createUserInfoFromHistoryItem(
744         getNotificationString(kWebHistoryItemsAddedNotification), entry);
745     postNotification(kWebHistoryItemsAddedNotification, userInfo);
746     releaseUserInfo(userInfo);
747 }
748 
visitedURLForRedirectWithoutHistoryItem(const KURL & url)749 void WebHistory::visitedURLForRedirectWithoutHistoryItem(const KURL& url)
750 {
751     if (m_lastVisitedEntry)
752         m_lastVisitedEntry->historyItem()->addRedirectURL(url.string());
753 }
754 
itemForURLString(CFStringRef urlString,IWebHistoryItem ** item)755 HRESULT WebHistory::itemForURLString(
756     /* [in] */ CFStringRef urlString,
757     /* [retval][out] */ IWebHistoryItem** item)
758 {
759     if (!item)
760         return E_FAIL;
761     *item = 0;
762 
763     IWebHistoryItem* foundItem = (IWebHistoryItem*) CFDictionaryGetValue(m_entriesByURL.get(), urlString);
764     if (!foundItem)
765         return E_FAIL;
766 
767     foundItem->AddRef();
768     *item = foundItem;
769     return S_OK;
770 }
771 
itemForURL(BSTR url,IWebHistoryItem ** item)772 HRESULT STDMETHODCALLTYPE WebHistory::itemForURL(
773     /* [in] */ BSTR url,
774     /* [retval][out] */ IWebHistoryItem** item)
775 {
776     RetainPtr<CFStringRef> urlString(AdoptCF, MarshallingHelpers::BSTRToCFStringRef(url));
777     return itemForURLString(urlString.get(), item);
778 }
779 
removeItemForURLString(CFStringRef urlString)780 HRESULT WebHistory::removeItemForURLString(CFStringRef urlString)
781 {
782     IWebHistoryItem* entry = (IWebHistoryItem*) CFDictionaryGetValue(m_entriesByURL.get(), urlString);
783     if (!entry)
784         return E_FAIL;
785 
786     HRESULT hr = removeItemFromDateCaches(entry);
787     CFDictionaryRemoveValue(m_entriesByURL.get(), urlString);
788 
789     if (!CFDictionaryGetCount(m_entriesByURL.get()))
790         PageGroup::removeAllVisitedLinks();
791 
792     return hr;
793 }
794 
addItemToDateCaches(IWebHistoryItem * entry)795 HRESULT WebHistory::addItemToDateCaches(IWebHistoryItem* entry)
796 {
797     HRESULT hr = S_OK;
798 
799     DATE lastVisitedCOMTime;
800     entry->lastVisitedTimeInterval(&lastVisitedCOMTime);
801     CFAbsoluteTime lastVisitedDate = timeToDate(MarshallingHelpers::DATEToCFAbsoluteTime(lastVisitedCOMTime));
802 
803     int dateIndex;
804     if (findIndex(&dateIndex, lastVisitedDate)) {
805         // other entries already exist for this date
806         hr = insertItem(entry, dateIndex);
807     } else {
808         // no other entries exist for this date
809         RetainPtr<CFNumberRef> lastVisitedDateRef(AdoptCF, CFNumberCreate(0, kCFNumberDoubleType, &lastVisitedDate));
810         CFArrayInsertValueAtIndex(m_datesWithEntries.get(), dateIndex, lastVisitedDateRef.get());
811         RetainPtr<CFMutableArrayRef> entryArray(AdoptCF,
812             CFArrayCreateMutable(0, 0, &MarshallingHelpers::kIUnknownArrayCallBacks));
813         CFArrayAppendValue(entryArray.get(), entry);
814         CFArrayInsertValueAtIndex(m_entriesByDate.get(), dateIndex, entryArray.get());
815     }
816 
817     return hr;
818 }
819 
removeItemFromDateCaches(IWebHistoryItem * entry)820 HRESULT WebHistory::removeItemFromDateCaches(IWebHistoryItem* entry)
821 {
822     HRESULT hr = S_OK;
823 
824     DATE lastVisitedCOMTime;
825     entry->lastVisitedTimeInterval(&lastVisitedCOMTime);
826     CFAbsoluteTime lastVisitedDate = timeToDate(MarshallingHelpers::DATEToCFAbsoluteTime(lastVisitedCOMTime));
827 
828     int dateIndex;
829     if (!findIndex(&dateIndex, lastVisitedDate))
830         return E_FAIL;
831 
832     CFMutableArrayRef entriesForDate = (CFMutableArrayRef) CFArrayGetValueAtIndex(m_entriesByDate.get(), dateIndex);
833     CFIndex count = CFArrayGetCount(entriesForDate);
834     for (int i = count - 1; i >= 0; --i) {
835         if ((IWebHistoryItem*)CFArrayGetValueAtIndex(entriesForDate, i) == entry)
836             CFArrayRemoveValueAtIndex(entriesForDate, i);
837     }
838 
839     // remove this date entirely if there are no other entries on it
840     if (CFArrayGetCount(entriesForDate) == 0) {
841         CFArrayRemoveValueAtIndex(m_entriesByDate.get(), dateIndex);
842         CFArrayRemoveValueAtIndex(m_datesWithEntries.get(), dateIndex);
843     }
844 
845     return hr;
846 }
847 
848 // Returns whether the day is already in the list of days,
849 // and fills in *index with the found or proposed index.
findIndex(int * index,CFAbsoluteTime forDay)850 bool WebHistory::findIndex(int* index, CFAbsoluteTime forDay)
851 {
852     CFAbsoluteTime forDayInDays = timeToDate(forDay);
853 
854     //FIXME: just does linear search through days; inefficient if many days
855     int count = CFArrayGetCount(m_datesWithEntries.get());
856     for (*index = 0; *index < count; ++*index) {
857         CFNumberRef entryTimeNumberRef = (CFNumberRef) CFArrayGetValueAtIndex(m_datesWithEntries.get(), *index);
858         CFAbsoluteTime entryTime;
859         CFNumberGetValue(entryTimeNumberRef, kCFNumberDoubleType, &entryTime);
860         CFAbsoluteTime entryInDays = timeToDate(entryTime);
861         if (areEqualOrClose(forDayInDays, entryInDays))
862             return true;
863         else if (forDayInDays > entryInDays)
864             return false;
865     }
866     return false;
867 }
868 
insertItem(IWebHistoryItem * entry,int dateIndex)869 HRESULT WebHistory::insertItem(IWebHistoryItem* entry, int dateIndex)
870 {
871     HRESULT hr = S_OK;
872 
873     if (!entry)
874         return E_FAIL;
875     if (dateIndex < 0 || dateIndex >= CFArrayGetCount(m_entriesByDate.get()))
876         return E_FAIL;
877 
878     //FIXME: just does linear search through entries; inefficient if many entries for this date
879     DATE entryTime;
880     entry->lastVisitedTimeInterval(&entryTime);
881     CFMutableArrayRef entriesForDate = (CFMutableArrayRef) CFArrayGetValueAtIndex(m_entriesByDate.get(), dateIndex);
882     int count = CFArrayGetCount(entriesForDate);
883     // optimized for inserting oldest to youngest
884     int index;
885     for (index = 0; index < count; ++index) {
886         IWebHistoryItem* indEntry = (IWebHistoryItem*) CFArrayGetValueAtIndex(entriesForDate, index);
887         DATE indTime;
888         hr = indEntry->lastVisitedTimeInterval(&indTime);
889         if (FAILED(hr))
890             return hr;
891         if (entryTime < indTime)
892             break;
893     }
894     CFArrayInsertValueAtIndex(entriesForDate, index, entry);
895     return S_OK;
896 }
897 
timeToDate(CFAbsoluteTime time)898 CFAbsoluteTime WebHistory::timeToDate(CFAbsoluteTime time)
899 {
900     // can't just divide/round since the day boundaries depend on our current time zone
901     const double secondsPerDay = 60 * 60 * 24;
902     CFTimeZoneRef timeZone = CFTimeZoneCopySystem();
903     CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(time, timeZone);
904     date.hour = date.minute = 0;
905     date.second = 0.0;
906     CFAbsoluteTime timeInDays = CFGregorianDateGetAbsoluteTime(date, timeZone);
907     if (areEqualOrClose(time - timeInDays, secondsPerDay))
908         timeInDays += secondsPerDay;
909     return timeInDays;
910 }
911 
912 // Return a date that marks the age limit for history entries saved to or
913 // loaded from disk. Any entry older than this item should be rejected.
ageLimitDate(CFAbsoluteTime * time)914 HRESULT WebHistory::ageLimitDate(CFAbsoluteTime* time)
915 {
916     // get the current date as a CFAbsoluteTime
917     CFAbsoluteTime currentDate = timeToDate(CFAbsoluteTimeGetCurrent());
918 
919     CFGregorianUnits ageLimit = {0};
920     int historyLimitDays;
921     HRESULT hr = historyAgeInDaysLimit(&historyLimitDays);
922     if (FAILED(hr))
923         return hr;
924     ageLimit.days = -historyLimitDays;
925     *time = CFAbsoluteTimeAddGregorianUnits(currentDate, CFTimeZoneCopySystem(), ageLimit);
926     return S_OK;
927 }
928 
addVisitedLinkToPageGroup(const void * key,const void *,void * context)929 static void addVisitedLinkToPageGroup(const void* key, const void*, void* context)
930 {
931     CFStringRef url = static_cast<CFStringRef>(key);
932     PageGroup* group = static_cast<PageGroup*>(context);
933 
934     CFIndex length = CFStringGetLength(url);
935     const UChar* characters = reinterpret_cast<const UChar*>(CFStringGetCharactersPtr(url));
936     if (characters)
937         group->addVisitedLink(characters, length);
938     else {
939         Vector<UChar, 512> buffer(length);
940         CFStringGetCharacters(url, CFRangeMake(0, length), reinterpret_cast<UniChar*>(buffer.data()));
941         group->addVisitedLink(buffer.data(), length);
942     }
943 }
944 
addVisitedLinksToPageGroup(PageGroup & group)945 void WebHistory::addVisitedLinksToPageGroup(PageGroup& group)
946 {
947     CFDictionaryApplyFunction(m_entriesByURL.get(), addVisitedLinkToPageGroup, &group);
948 }
949