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(¤tTime);
713 DATE lastVisited;
714 if (!SystemTimeToVariantTime(¤tTime, &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