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