1/*
2 * Copyright (C) 2006, 2007, 2008, 2009 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 "WebIconDatabase.h"
29
30#include "CFDictionaryPropertyBag.h"
31#include "WebPreferences.h"
32#include "WebNotificationCenter.h"
33#include <WebCore/BitmapInfo.h>
34#include <WebCore/BString.h>
35#include <WebCore/COMPtr.h>
36#include <WebCore/FileSystem.h>
37#include <WebCore/IconDatabase.h>
38#include <WebCore/Image.h>
39#include <WebCore/PlatformString.h>
40#include <WebCore/SharedBuffer.h>
41#include <wtf/MainThread.h>
42#include "shlobj.h"
43
44using namespace WebCore;
45using namespace WTF;
46
47// WebIconDatabase ----------------------------------------------------------------
48
49WebIconDatabase* WebIconDatabase::m_sharedWebIconDatabase = 0;
50
51WebIconDatabase::WebIconDatabase()
52: m_refCount(0)
53, m_deliveryRequested(false)
54{
55    gClassCount++;
56    gClassNameCount.add("WebIconDatabase");
57}
58
59WebIconDatabase::~WebIconDatabase()
60{
61    gClassCount--;
62    gClassNameCount.remove("WebIconDatabase");
63}
64
65void WebIconDatabase::init()
66{
67    WebPreferences* standardPrefs = WebPreferences::sharedStandardPreferences();
68    BOOL enabled = FALSE;
69    if (FAILED(standardPrefs->iconDatabaseEnabled(&enabled))) {
70        enabled = FALSE;
71        LOG_ERROR("Unable to get icon database enabled preference");
72    }
73    iconDatabase().setEnabled(!!enabled);
74    if (!(!!enabled))
75        return;
76
77    startUpIconDatabase();
78}
79
80void WebIconDatabase::startUpIconDatabase()
81{
82    WebPreferences* standardPrefs = WebPreferences::sharedStandardPreferences();
83
84    iconDatabase().setClient(this);
85
86    BSTR prefDatabasePath = 0;
87    if (FAILED(standardPrefs->iconDatabaseLocation(&prefDatabasePath)))
88        LOG_ERROR("Unable to get icon database location preference");
89
90    String databasePath(prefDatabasePath, SysStringLen(prefDatabasePath));
91    SysFreeString(prefDatabasePath);
92
93    if (databasePath.isEmpty()) {
94        databasePath = localUserSpecificStorageDirectory();
95        if (databasePath.isEmpty())
96            LOG_ERROR("Failed to construct default icon database path");
97    }
98
99    if (!iconDatabase().open(databasePath, WebCore::IconDatabase::defaultDatabaseFilename()))
100            LOG_ERROR("Failed to open icon database path");
101}
102
103void WebIconDatabase::shutDownIconDatabase()
104{
105}
106
107WebIconDatabase* WebIconDatabase::createInstance()
108{
109    WebIconDatabase* instance = new WebIconDatabase();
110    instance->AddRef();
111    return instance;
112}
113
114WebIconDatabase* WebIconDatabase::sharedWebIconDatabase()
115{
116    if (m_sharedWebIconDatabase) {
117        m_sharedWebIconDatabase->AddRef();
118        return m_sharedWebIconDatabase;
119    }
120    m_sharedWebIconDatabase = createInstance();
121    m_sharedWebIconDatabase->init();
122    return m_sharedWebIconDatabase;
123}
124
125// IUnknown -------------------------------------------------------------------
126
127HRESULT STDMETHODCALLTYPE WebIconDatabase::QueryInterface(REFIID riid, void** ppvObject)
128{
129    *ppvObject = 0;
130    if (IsEqualGUID(riid, IID_IUnknown))
131        *ppvObject = static_cast<IWebIconDatabase*>(this);
132    else if (IsEqualGUID(riid, IID_IWebIconDatabase))
133        *ppvObject = static_cast<IWebIconDatabase*>(this);
134    else
135        return E_NOINTERFACE;
136
137    AddRef();
138    return S_OK;
139}
140
141ULONG STDMETHODCALLTYPE WebIconDatabase::AddRef(void)
142{
143    return ++m_refCount;
144}
145
146ULONG STDMETHODCALLTYPE WebIconDatabase::Release(void)
147{
148    ULONG newRef = --m_refCount;
149    if (!newRef)
150        delete(this);
151
152    return newRef;
153}
154
155// IWebIconDatabase --------------------------------------------------------------------
156
157HRESULT STDMETHODCALLTYPE WebIconDatabase::sharedIconDatabase(
158        /* [retval][out] */ IWebIconDatabase** result)
159{
160    *result = sharedWebIconDatabase();
161    return S_OK;
162}
163
164HRESULT STDMETHODCALLTYPE WebIconDatabase::iconForURL(
165        /* [in] */ BSTR url,
166        /* [optional][in] */ LPSIZE size,
167        /* [optional][in] */ BOOL /*cache*/,
168        /* [retval][out] */ OLE_HANDLE* bitmap)
169{
170    IntSize intSize(*size);
171
172    Image* icon = 0;
173    if (url)
174        icon = iconDatabase().synchronousIconForPageURL(String(url, SysStringLen(url)), intSize);
175
176    // Make sure we check for the case of an "empty image"
177    if (icon && icon->width()) {
178        *bitmap = (OLE_HANDLE)(ULONG64)getOrCreateSharedBitmap(size);
179        if (!icon->getHBITMAPOfSize((HBITMAP)(ULONG64)*bitmap, size)) {
180            LOG_ERROR("Failed to draw Image to HBITMAP");
181            *bitmap = 0;
182            return E_FAIL;
183        }
184        return S_OK;
185    }
186
187    return defaultIconWithSize(size, bitmap);
188}
189
190HRESULT STDMETHODCALLTYPE WebIconDatabase::defaultIconWithSize(
191        /* [in] */ LPSIZE size,
192        /* [retval][out] */ OLE_HANDLE* result)
193{
194    *result = (OLE_HANDLE)(ULONG64)getOrCreateDefaultIconBitmap(size);
195    return S_OK;
196}
197
198HRESULT STDMETHODCALLTYPE WebIconDatabase::retainIconForURL(
199        /* [in] */ BSTR url)
200{
201    iconDatabase().retainIconForPageURL(String(url, SysStringLen(url)));
202    return S_OK;
203}
204
205HRESULT STDMETHODCALLTYPE WebIconDatabase::releaseIconForURL(
206        /* [in] */ BSTR url)
207{
208    iconDatabase().releaseIconForPageURL(String(url, SysStringLen(url)));
209    return S_OK;
210}
211
212HRESULT STDMETHODCALLTYPE WebIconDatabase::removeAllIcons(void)
213{
214    iconDatabase().removeAllIcons();
215    return S_OK;
216}
217
218HRESULT STDMETHODCALLTYPE WebIconDatabase::delayDatabaseCleanup(void)
219{
220    IconDatabase::delayDatabaseCleanup();
221    return S_OK;
222}
223
224HRESULT STDMETHODCALLTYPE WebIconDatabase::allowDatabaseCleanup(void)
225{
226    IconDatabase::allowDatabaseCleanup();
227    return S_OK;
228}
229
230HRESULT STDMETHODCALLTYPE WebIconDatabase::iconURLForURL(
231        /* [in] */ BSTR url,
232        /* [retval][out] */ BSTR* iconURL)
233{
234    if (!url || !iconURL)
235        return E_POINTER;
236    BString iconURLBSTR(iconDatabase().synchronousIconURLForPageURL(String(url, SysStringLen(url))));
237    *iconURL = iconURLBSTR.release();
238    return S_OK;
239}
240
241HRESULT STDMETHODCALLTYPE WebIconDatabase::isEnabled(
242        /* [retval][out] */ BOOL *result)
243{
244    *result = iconDatabase().isEnabled();
245    return S_OK;
246}
247
248HRESULT STDMETHODCALLTYPE WebIconDatabase::setEnabled(
249        /* [in] */ BOOL flag)
250{
251    BOOL currentlyEnabled;
252    isEnabled(&currentlyEnabled);
253    if (currentlyEnabled && !flag) {
254        iconDatabase().setEnabled(false);
255        shutDownIconDatabase();
256    } else if (!currentlyEnabled && flag) {
257        iconDatabase().setEnabled(true);
258        startUpIconDatabase();
259    }
260    return S_OK;
261}
262
263HRESULT STDMETHODCALLTYPE WebIconDatabase::hasIconForURL(
264        /* [in] */ BSTR url,
265        /* [out][retval] */ BOOL* result)
266{
267    if (!url || !result)
268        return E_POINTER;
269
270    String urlString(url, SysStringLen(url));
271
272    // Passing a size parameter of 0, 0 means we don't care about the result of the image, we just
273    // want to make sure the read from disk to load the icon is kicked off.
274    iconDatabase().synchronousIconForPageURL(urlString, IntSize(0, 0));
275
276    // Check to see if we have a non-empty icon URL for the page, and if we do, we have an icon for
277    // the page.
278    *result = !(iconDatabase().synchronousIconURLForPageURL(urlString).isEmpty());
279
280    return S_OK;
281}
282
283HBITMAP createDIB(LPSIZE size)
284{
285    BitmapInfo bmInfo = BitmapInfo::create(IntSize(*size));
286
287    HDC dc = GetDC(0);
288    HBITMAP result = CreateDIBSection(dc, &bmInfo, DIB_RGB_COLORS, 0, 0, 0);
289    ReleaseDC(0, dc);
290
291    return result;
292}
293
294HBITMAP WebIconDatabase::getOrCreateSharedBitmap(LPSIZE size)
295{
296    HBITMAP result = m_sharedIconMap.get(*size);
297    if (result)
298        return result;
299    result = createDIB(size);
300    m_sharedIconMap.set(*size, result);
301    return result;
302}
303
304HBITMAP WebIconDatabase::getOrCreateDefaultIconBitmap(LPSIZE size)
305{
306    HBITMAP result = m_defaultIconMap.get(*size);
307    if (result)
308        return result;
309
310    result = createDIB(size);
311
312    m_defaultIconMap.set(*size, result);
313    if (!iconDatabase().defaultIcon(*size)->getHBITMAPOfSize(result, size)) {
314        LOG_ERROR("Failed to draw Image to HBITMAP");
315        return 0;
316    }
317    return result;
318}
319
320// IconDatabaseClient
321
322bool WebIconDatabase::performImport()
323{
324    // Windows doesn't do any old-style database importing.
325    return true;
326}
327
328void WebIconDatabase::didRemoveAllIcons()
329{
330    // Queueing the empty string is a special way of saying "this queued notification is the didRemoveAllIcons notification"
331    MutexLocker locker(m_notificationMutex);
332    m_notificationQueue.append(String());
333    scheduleNotificationDelivery();
334}
335
336void WebIconDatabase::didImportIconURLForPageURL(const WTF::String& pageURL)
337{
338    MutexLocker locker(m_notificationMutex);
339    m_notificationQueue.append(pageURL.threadsafeCopy());
340    scheduleNotificationDelivery();
341}
342
343void WebIconDatabase::didImportIconDataForPageURL(const WTF::String& pageURL)
344{
345    // WebKit1 only has a single "icon did change" notification.
346    didImportIconURLForPageURL(pageURL);
347}
348
349void WebIconDatabase::didChangeIconForPageURL(const WTF::String& pageURL)
350{
351    // WebKit1 only has a single "icon did change" notification.
352    didImportIconURLForPageURL(pageURL);
353}
354
355void WebIconDatabase::didFinishURLImport()
356{
357}
358
359void WebIconDatabase::scheduleNotificationDelivery()
360{
361    // Caller of this method must hold the m_notificationQueue lock
362    ASSERT(!m_notificationMutex.tryLock());
363
364    if (!m_deliveryRequested) {
365        m_deliveryRequested = true;
366        callOnMainThread(deliverNotifications, 0);
367    }
368}
369
370BSTR WebIconDatabase::iconDatabaseDidAddIconNotification()
371{
372    static BSTR didAddIconName = SysAllocString(WebIconDatabaseDidAddIconNotification);
373    return didAddIconName;
374}
375
376CFStringRef WebIconDatabase::iconDatabaseNotificationUserInfoURLKey()
377{
378    static CFStringRef iconUserInfoURLKey = String(WebIconNotificationUserInfoURLKey).createCFString();
379    return iconUserInfoURLKey;
380}
381
382BSTR WebIconDatabase::iconDatabaseDidRemoveAllIconsNotification()
383{
384    static BSTR didRemoveAllIconsName = SysAllocString(WebIconDatabaseDidRemoveAllIconsNotification);
385    return didRemoveAllIconsName;
386}
387
388static void postDidRemoveAllIconsNotification(WebIconDatabase* iconDB)
389{
390    IWebNotificationCenter* notifyCenter = WebNotificationCenter::defaultCenterInternal();
391    notifyCenter->postNotificationName(WebIconDatabase::iconDatabaseDidRemoveAllIconsNotification(), static_cast<IWebIconDatabase*>(iconDB), 0);
392}
393
394static void postDidAddIconNotification(const String& pageURL, WebIconDatabase* iconDB)
395{
396    RetainPtr<CFMutableDictionaryRef> dictionary(AdoptCF,
397    CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
398
399    RetainPtr<CFStringRef> url(AdoptCF, pageURL.createCFString());
400    CFDictionaryAddValue(dictionary.get(), WebIconDatabase::iconDatabaseNotificationUserInfoURLKey(), url.get());
401
402    COMPtr<CFDictionaryPropertyBag> userInfo = CFDictionaryPropertyBag::createInstance();
403    userInfo->setDictionary(dictionary.get());
404
405    IWebNotificationCenter* notifyCenter = WebNotificationCenter::defaultCenterInternal();
406    notifyCenter->postNotificationName(WebIconDatabase::iconDatabaseDidAddIconNotification(), static_cast<IWebIconDatabase*>(iconDB), userInfo.get());
407}
408
409void WebIconDatabase::deliverNotifications(void*)
410{
411    ASSERT(m_sharedWebIconDatabase);
412    if (!m_sharedWebIconDatabase)
413        return;
414
415    ASSERT(m_sharedWebIconDatabase->m_deliveryRequested);
416
417    Vector<String> queue;
418    {
419        MutexLocker locker(m_sharedWebIconDatabase->m_notificationMutex);
420        queue.swap(m_sharedWebIconDatabase->m_notificationQueue);
421        m_sharedWebIconDatabase->m_deliveryRequested = false;
422    }
423
424    for (unsigned i = 0; i < queue.size(); ++i) {
425        if (queue[i].isNull())
426            postDidRemoveAllIconsNotification(m_sharedWebIconDatabase);
427        else
428            postDidAddIconNotification(queue[i], m_sharedWebIconDatabase);
429    }
430}
431