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