1/*
2 * Copyright (C) 2006, 2007, 2008 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 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 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 "WebHistoryItem.h"
29
30#include "COMEnumVariant.h"
31#include "MarshallingHelpers.h"
32#include "WebKit.h"
33#include <WebCore/BString.h>
34#include <WebCore/COMPtr.h>
35#include <WebCore/HistoryItem.h>
36#include <WebCore/URL.h>
37#include <wtf/PassOwnPtr.h>
38#include <wtf/RetainPtr.h>
39#include <wtf/text/CString.h>
40
41using namespace WebCore;
42
43// WebHistoryItem ----------------------------------------------------------------
44
45static HashMap<HistoryItem*, WebHistoryItem*>& historyItemWrappers()
46{
47    static HashMap<HistoryItem*, WebHistoryItem*> staticHistoryItemWrappers;
48    return staticHistoryItemWrappers;
49}
50
51WebHistoryItem::WebHistoryItem(PassRefPtr<HistoryItem> historyItem)
52: m_refCount(0)
53, m_historyItem(historyItem)
54{
55    ASSERT(!historyItemWrappers().contains(m_historyItem.get()));
56    historyItemWrappers().set(m_historyItem.get(), this);
57
58    gClassCount++;
59    gClassNameCount.add("WebHistoryItem");
60}
61
62WebHistoryItem::~WebHistoryItem()
63{
64    ASSERT(historyItemWrappers().contains(m_historyItem.get()));
65    historyItemWrappers().remove(m_historyItem.get());
66
67    gClassCount--;
68    gClassNameCount.remove("WebHistoryItem");
69}
70
71WebHistoryItem* WebHistoryItem::createInstance()
72{
73    WebHistoryItem* instance = new WebHistoryItem(HistoryItem::create());
74    instance->AddRef();
75    return instance;
76}
77
78WebHistoryItem* WebHistoryItem::createInstance(PassRefPtr<HistoryItem> historyItem)
79{
80    WebHistoryItem* instance;
81
82    instance = historyItemWrappers().get(historyItem.get());
83
84    if (!instance)
85        instance = new WebHistoryItem(historyItem);
86
87    instance->AddRef();
88    return instance;
89}
90
91// IWebHistoryItemPrivate -----------------------------------------------------
92
93static CFStringRef urlKey = CFSTR("");
94static CFStringRef titleKey = CFSTR("title");
95static CFStringRef lastVisitWasFailureKey = CFSTR("lastVisitWasFailure");
96static CFStringRef redirectURLsKey = CFSTR("redirectURLs");
97
98HRESULT STDMETHODCALLTYPE WebHistoryItem::initFromDictionaryRepresentation(void* dictionary)
99{
100    CFDictionaryRef dictionaryRef = (CFDictionaryRef) dictionary;
101
102    CFStringRef urlStringRef = (CFStringRef) CFDictionaryGetValue(dictionaryRef, urlKey);
103    if (urlStringRef && CFGetTypeID(urlStringRef) != CFStringGetTypeID())
104        return E_FAIL;
105
106    CFStringRef titleRef = (CFStringRef) CFDictionaryGetValue(dictionaryRef, titleKey);
107    if (titleRef && CFGetTypeID(titleRef) != CFStringGetTypeID())
108        return E_FAIL;
109
110    CFBooleanRef lastVisitWasFailureRef = static_cast<CFBooleanRef>(CFDictionaryGetValue(dictionaryRef, lastVisitWasFailureKey));
111    if (lastVisitWasFailureRef && CFGetTypeID(lastVisitWasFailureRef) != CFBooleanGetTypeID())
112        return E_FAIL;
113    bool lastVisitWasFailure = lastVisitWasFailureRef && CFBooleanGetValue(lastVisitWasFailureRef);
114
115    std::unique_ptr<Vector<String>> redirectURLsVector;
116    if (CFArrayRef redirectURLsRef = static_cast<CFArrayRef>(CFDictionaryGetValue(dictionaryRef, redirectURLsKey))) {
117        CFIndex size = CFArrayGetCount(redirectURLsRef);
118        redirectURLsVector = std::make_unique<Vector<String>>(size);
119        for (CFIndex i = 0; i < size; ++i)
120            (*redirectURLsVector)[i] = String(static_cast<CFStringRef>(CFArrayGetValueAtIndex(redirectURLsRef, i)));
121    }
122
123    historyItemWrappers().remove(m_historyItem.get());
124    m_historyItem = HistoryItem::create(urlStringRef, titleRef);
125    historyItemWrappers().set(m_historyItem.get(), this);
126
127    if (lastVisitWasFailure)
128        m_historyItem->setLastVisitWasFailure(true);
129
130    if (redirectURLsVector.get())
131        m_historyItem->setRedirectURLs(WTF::move(redirectURLsVector));
132
133    return S_OK;
134}
135
136HRESULT STDMETHODCALLTYPE WebHistoryItem::dictionaryRepresentation(void** dictionary)
137{
138    CFDictionaryRef* dictionaryRef = (CFDictionaryRef*) dictionary;
139
140    int keyCount = 0;
141    CFTypeRef keys[9];
142    CFTypeRef values[9];
143
144    if (!m_historyItem->urlString().isEmpty()) {
145        keys[keyCount] = urlKey;
146        values[keyCount++] = m_historyItem->urlString().createCFString().leakRef();
147    }
148
149    if (!m_historyItem->title().isEmpty()) {
150        keys[keyCount] = titleKey;
151        values[keyCount++] = m_historyItem->title().createCFString().leakRef();
152    }
153
154    if (m_historyItem->lastVisitWasFailure()) {
155        keys[keyCount] = lastVisitWasFailureKey;
156        values[keyCount++] = CFRetain(kCFBooleanTrue);
157    }
158
159    if (Vector<String>* redirectURLs = m_historyItem->redirectURLs()) {
160        size_t size = redirectURLs->size();
161        ASSERT(size);
162        CFStringRef* items = new CFStringRef[size];
163        for (size_t i = 0; i < size; ++i)
164            items[i] = redirectURLs->at(i).createCFString().leakRef();
165        CFArrayRef result = CFArrayCreate(0, (const void**)items, size, &kCFTypeArrayCallBacks);
166        for (size_t i = 0; i < size; ++i)
167            CFRelease(items[i]);
168        delete[] items;
169
170        keys[keyCount] = redirectURLsKey;
171        values[keyCount++] = result;
172    }
173
174    *dictionaryRef = CFDictionaryCreate(0, keys, values, keyCount, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
175
176    for (int i = 0; i < keyCount; ++i)
177        CFRelease(values[i]);
178
179    return S_OK;
180}
181
182HRESULT STDMETHODCALLTYPE WebHistoryItem::hasURLString(BOOL *hasURL)
183{
184    *hasURL = m_historyItem->urlString().isEmpty() ? FALSE : TRUE;
185    return S_OK;
186}
187
188// FIXME: This function should be removed from the IWebHistoryItem interface.
189HRESULT STDMETHODCALLTYPE WebHistoryItem::visitCount(int *count)
190{
191    return E_NOTIMPL;
192}
193
194// FIXME: This function should be removed from the IWebHistoryItem interface.
195HRESULT STDMETHODCALLTYPE WebHistoryItem::setVisitCount(int count)
196{
197    return E_NOTIMPL;
198
199}
200
201// FIXME: This function should be removed from the IWebHistoryItem interface.
202HRESULT STDMETHODCALLTYPE WebHistoryItem::mergeAutoCompleteHints(IWebHistoryItem* otherItem)
203{
204    return E_NOTIMPL;
205}
206
207// FIXME: This function should be removed from the IWebHistoryItem interface.
208HRESULT STDMETHODCALLTYPE WebHistoryItem::setLastVisitedTimeInterval(DATE time)
209{
210    return E_NOTIMPL;
211}
212
213HRESULT STDMETHODCALLTYPE WebHistoryItem::setTitle(BSTR title)
214{
215    m_historyItem->setTitle(String(title, SysStringLen(title)));
216
217    return S_OK;
218}
219
220HRESULT STDMETHODCALLTYPE WebHistoryItem::RSSFeedReferrer(BSTR* url)
221{
222    BString str(m_historyItem->referrer());
223    *url = str.release();
224
225    return S_OK;
226}
227
228HRESULT STDMETHODCALLTYPE WebHistoryItem::setRSSFeedReferrer(BSTR url)
229{
230    m_historyItem->setReferrer(String(url, SysStringLen(url)));
231
232    return S_OK;
233}
234
235HRESULT STDMETHODCALLTYPE WebHistoryItem::hasPageCache(BOOL* /*hasCache*/)
236{
237    // FIXME - TODO
238    ASSERT_NOT_REACHED();
239    return E_NOTIMPL;
240}
241
242HRESULT STDMETHODCALLTYPE WebHistoryItem::setHasPageCache(BOOL /*hasCache*/)
243{
244    // FIXME - TODO
245    return E_NOTIMPL;
246}
247
248HRESULT STDMETHODCALLTYPE WebHistoryItem::target(BSTR* target)
249{
250    if (!target) {
251        ASSERT_NOT_REACHED();
252        return E_POINTER;
253    }
254
255    *target = BString(m_historyItem->target()).release();
256    return S_OK;
257}
258
259HRESULT STDMETHODCALLTYPE WebHistoryItem::isTargetItem(BOOL* result)
260{
261    if (!result) {
262        ASSERT_NOT_REACHED();
263        return E_POINTER;
264    }
265
266    *result = m_historyItem->isTargetItem() ? TRUE : FALSE;
267    return S_OK;
268}
269
270HRESULT STDMETHODCALLTYPE WebHistoryItem::children(unsigned* outChildCount, SAFEARRAY** outChildren)
271{
272    if (!outChildCount || !outChildren) {
273        ASSERT_NOT_REACHED();
274        return E_POINTER;
275    }
276
277    *outChildCount = 0;
278    *outChildren = 0;
279
280    const HistoryItemVector& coreChildren = m_historyItem->children();
281    if (coreChildren.isEmpty())
282        return S_OK;
283    size_t childCount = coreChildren.size();
284
285    SAFEARRAY* children = SafeArrayCreateVector(VT_UNKNOWN, 0, static_cast<ULONG>(childCount));
286    if (!children)
287        return E_OUTOFMEMORY;
288
289    for (unsigned i = 0; i < childCount; ++i) {
290        COMPtr<WebHistoryItem> item(AdoptCOM, WebHistoryItem::createInstance(coreChildren[i]));
291        if (!item) {
292            SafeArrayDestroy(children);
293            return E_OUTOFMEMORY;
294        }
295
296        LONG longI = i;
297        HRESULT hr = SafeArrayPutElement(children, &longI, item.get());
298        if (FAILED(hr)) {
299            SafeArrayDestroy(children);
300            return hr;
301        }
302    }
303
304    *outChildCount = static_cast<unsigned>(childCount);
305    *outChildren = children;
306    return S_OK;
307
308}
309
310HRESULT STDMETHODCALLTYPE WebHistoryItem::lastVisitWasFailure(BOOL* wasFailure)
311{
312    if (!wasFailure) {
313        ASSERT_NOT_REACHED();
314        return E_POINTER;
315    }
316
317    *wasFailure = m_historyItem->lastVisitWasFailure();
318    return S_OK;
319}
320
321HRESULT STDMETHODCALLTYPE WebHistoryItem::setLastVisitWasFailure(BOOL wasFailure)
322{
323    m_historyItem->setLastVisitWasFailure(wasFailure);
324    return S_OK;
325}
326
327// FIXME: This function should be removed from the IWebHistoryItem interface.
328HRESULT STDMETHODCALLTYPE WebHistoryItem::lastVisitWasHTTPNonGet(BOOL* HTTPNonGet)
329{
330    return E_NOTIMPL;
331}
332
333// FIXME: This function should be removed from the IWebHistoryItem interface.
334HRESULT STDMETHODCALLTYPE WebHistoryItem::setLastVisitWasHTTPNonGet(BOOL HTTPNonGet)
335{
336    return E_NOTIMPL;
337}
338
339HRESULT STDMETHODCALLTYPE WebHistoryItem::redirectURLs(IEnumVARIANT** urls)
340{
341    if (!urls) {
342        ASSERT_NOT_REACHED();
343        return E_POINTER;
344    }
345
346    Vector<String>* urlVector = m_historyItem->redirectURLs();
347    if (!urlVector) {
348        *urls = 0;
349        return S_OK;
350    }
351
352    COMPtr<COMEnumVariant<Vector<String> > > enumVariant(AdoptCOM, COMEnumVariant<Vector<String> >::createInstance(*urlVector));
353    *urls = enumVariant.leakRef();
354
355    return S_OK;
356}
357
358// FIXME: This function should be removed from the IWebHistoryItem interface.
359HRESULT STDMETHODCALLTYPE WebHistoryItem::visitedWithTitle(BSTR title, BOOL increaseVisitCount)
360{
361    return E_NOTIMPL;
362}
363
364// FIXME: This function should be removed from the IWebHistoryItem interface.
365HRESULT STDMETHODCALLTYPE WebHistoryItem::getDailyVisitCounts(int* number, int** counts)
366{
367    return E_NOTIMPL;
368}
369
370// FIXME: This function should be removed from the IWebHistoryItem interface.
371HRESULT STDMETHODCALLTYPE WebHistoryItem::getWeeklyVisitCounts(int* number, int** counts)
372{
373    return E_NOTIMPL;
374}
375
376// FIXME: This function should be removed from the IWebHistoryItem interface.
377HRESULT STDMETHODCALLTYPE WebHistoryItem::recordInitialVisit()
378{
379    // FIXME: This function should be removed from the IWebHistoryItem interface.
380    return E_NOTIMPL;
381}
382
383// IUnknown -------------------------------------------------------------------
384
385HRESULT STDMETHODCALLTYPE WebHistoryItem::QueryInterface(REFIID riid, void** ppvObject)
386{
387    *ppvObject = 0;
388    if (IsEqualGUID(riid, __uuidof(WebHistoryItem)))
389        *ppvObject = this;
390    else if (IsEqualGUID(riid, IID_IUnknown))
391        *ppvObject = static_cast<IWebHistoryItem*>(this);
392    else if (IsEqualGUID(riid, IID_IWebHistoryItem))
393        *ppvObject = static_cast<IWebHistoryItem*>(this);
394    else if (IsEqualGUID(riid, IID_IWebHistoryItemPrivate))
395        *ppvObject = static_cast<IWebHistoryItemPrivate*>(this);
396    else
397        return E_NOINTERFACE;
398
399    AddRef();
400    return S_OK;
401}
402
403ULONG STDMETHODCALLTYPE WebHistoryItem::AddRef(void)
404{
405    return ++m_refCount;
406}
407
408ULONG STDMETHODCALLTYPE WebHistoryItem::Release(void)
409{
410    ULONG newRef = --m_refCount;
411    if (!newRef)
412        delete(this);
413
414    return newRef;
415}
416
417// IWebHistoryItem -------------------------------------------------------------
418
419HRESULT STDMETHODCALLTYPE WebHistoryItem::initWithURLString(
420    /* [in] */ BSTR urlString,
421    /* [in] */ BSTR title,
422    /* [in] */ DATE lastVisited)
423{
424    historyItemWrappers().remove(m_historyItem.get());
425    m_historyItem = HistoryItem::create(String(urlString, SysStringLen(urlString)), String(title, SysStringLen(title)));
426    historyItemWrappers().set(m_historyItem.get(), this);
427
428    return S_OK;
429}
430
431HRESULT STDMETHODCALLTYPE WebHistoryItem::originalURLString(
432    /* [retval][out] */ BSTR* url)
433{
434    if (!url)
435        return E_POINTER;
436
437    BString str = m_historyItem->originalURLString();
438    *url = str.release();
439    return S_OK;
440}
441
442HRESULT STDMETHODCALLTYPE WebHistoryItem::URLString(
443    /* [retval][out] */ BSTR* url)
444{
445    if (!url)
446        return E_POINTER;
447
448    BString str = m_historyItem->urlString();
449    *url = str.release();
450    return S_OK;
451}
452
453HRESULT STDMETHODCALLTYPE WebHistoryItem::title(
454    /* [retval][out] */ BSTR* pageTitle)
455{
456    if (!pageTitle)
457        return E_POINTER;
458
459    BString str(m_historyItem->title());
460    *pageTitle = str.release();
461    return S_OK;
462}
463
464// FIXME: This function should be removed from the IWebHistoryItem interface.
465HRESULT STDMETHODCALLTYPE WebHistoryItem::lastVisitedTimeInterval(
466    /* [retval][out] */ DATE* lastVisited)
467{
468    return E_NOTIMPL;
469}
470
471HRESULT STDMETHODCALLTYPE WebHistoryItem::setAlternateTitle(
472    /* [in] */ BSTR title)
473{
474    m_alternateTitle = String(title, SysStringLen(title));
475    return S_OK;
476}
477
478HRESULT STDMETHODCALLTYPE WebHistoryItem::alternateTitle(
479    /* [retval][out] */ BSTR* title)
480{
481    if (!title) {
482        ASSERT_NOT_REACHED();
483        return E_POINTER;
484    }
485
486    *title = BString(m_alternateTitle).release();
487    return S_OK;
488}
489
490HRESULT STDMETHODCALLTYPE WebHistoryItem::icon(/* [out, retval] */ HBITMAP* /*hBitmap*/)
491{
492    ASSERT_NOT_REACHED();
493    return E_NOTIMPL;
494}
495
496// WebHistoryItem -------------------------------------------------------------
497
498HistoryItem* WebHistoryItem::historyItem() const
499{
500    return m_historyItem.get();
501}
502