/* * Copyright (C) 2012 Igalia S.L. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2,1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #include "WebKitFaviconDatabase.h" #include "WebKitFaviconDatabasePrivate.h" #include "WebKitMarshal.h" #include "WebKitPrivate.h" #include #include #include #include #include #include #include #include #include using namespace WebKit; enum { FAVICON_CHANGED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0, }; typedef Vector > PendingIconRequestVector; typedef HashMap PendingIconRequestMap; struct _WebKitFaviconDatabasePrivate { RefPtr iconDatabase; PendingIconRequestMap pendingIconRequests; HashMap pageURLToIconURLMap; }; G_DEFINE_TYPE(WebKitFaviconDatabase, webkit_favicon_database, G_TYPE_OBJECT) static void webkit_favicon_database_init(WebKitFaviconDatabase* manager) { WebKitFaviconDatabasePrivate* priv = G_TYPE_INSTANCE_GET_PRIVATE(manager, WEBKIT_TYPE_FAVICON_DATABASE, WebKitFaviconDatabasePrivate); manager->priv = priv; new (priv) WebKitFaviconDatabasePrivate(); } static void webkitFaviconDatabaseDispose(GObject* object) { WebKitFaviconDatabase* database = WEBKIT_FAVICON_DATABASE(object); WebKitFaviconDatabasePrivate* priv = database->priv; if (priv->iconDatabase->isOpen()) priv->iconDatabase->close(); G_OBJECT_CLASS(webkit_favicon_database_parent_class)->dispose(object); } static void webkitFaviconDatabaseFinalize(GObject* object) { WebKitFaviconDatabase* database = WEBKIT_FAVICON_DATABASE(object); database->priv->~WebKitFaviconDatabasePrivate(); G_OBJECT_CLASS(webkit_favicon_database_parent_class)->finalize(object); } static void webkit_favicon_database_class_init(WebKitFaviconDatabaseClass* faviconDatabaseClass) { GObjectClass* gObjectClass = G_OBJECT_CLASS(faviconDatabaseClass); gObjectClass->dispose = webkitFaviconDatabaseDispose; gObjectClass->finalize = webkitFaviconDatabaseFinalize; /** * WebKitFaviconDatabase::favicon-changed: * @database: the object on which the signal is emitted * @page_uri: the URI of the Web page containing the icon * @favicon_uri: the URI of the favicon * * This signal is emitted when the favicon URI of @page_uri has * been changed to @favicon_uri in the database. You can connect * to this signal and call webkit_favicon_database_get_favicon() * to get the favicon. If you are interested in the favicon of a * #WebKitWebView it's easier to use the #WebKitWebView:favicon * property. See webkit_web_view_get_favicon() for more details. */ signals[FAVICON_CHANGED] = g_signal_new( "favicon-changed", G_TYPE_FROM_CLASS(faviconDatabaseClass), G_SIGNAL_RUN_LAST, 0, 0, 0, webkit_marshal_VOID__STRING_STRING, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); g_type_class_add_private(faviconDatabaseClass, sizeof(WebKitFaviconDatabasePrivate)); } struct GetFaviconSurfaceAsyncData { ~GetFaviconSurfaceAsyncData() { if (shouldReleaseIconForPageURL) faviconDatabase->priv->iconDatabase->releaseIconForPageURL(pageURL); } GRefPtr faviconDatabase; String pageURL; RefPtr icon; GRefPtr cancellable; bool shouldReleaseIconForPageURL; }; WEBKIT_DEFINE_ASYNC_DATA_STRUCT(GetFaviconSurfaceAsyncData) static cairo_surface_t* getIconSurfaceSynchronously(WebKitFaviconDatabase* database, const String& pageURL, GError** error) { ASSERT(isMainThread()); // The exact size we pass is irrelevant to the iconDatabase code. // We must pass something greater than 0x0 to get an icon. WebCore::Image* iconImage = database->priv->iconDatabase->imageForPageURL(pageURL, WebCore::IntSize(1, 1)); if (!iconImage) { g_set_error(error, WEBKIT_FAVICON_DATABASE_ERROR, WEBKIT_FAVICON_DATABASE_ERROR_FAVICON_UNKNOWN, _("Unknown favicon for page %s"), pageURL.utf8().data()); return 0; } WebCore::NativeImagePtr icon = iconImage->nativeImageForCurrentFrame(); if (!icon) { g_set_error(error, WEBKIT_FAVICON_DATABASE_ERROR, WEBKIT_FAVICON_DATABASE_ERROR_FAVICON_NOT_FOUND, _("Page %s does not have a favicon"), pageURL.utf8().data()); return 0; } return icon->surface(); } static void deletePendingIconRequests(WebKitFaviconDatabase* database, PendingIconRequestVector* requests, const String& pageURL) { database->priv->pendingIconRequests.remove(pageURL); delete requests; } static void processPendingIconsForPageURL(WebKitFaviconDatabase* database, const String& pageURL) { PendingIconRequestVector* pendingIconRequests = database->priv->pendingIconRequests.get(pageURL); if (!pendingIconRequests) return; GOwnPtr error; RefPtr icon = getIconSurfaceSynchronously(database, pageURL, &error.outPtr()); for (size_t i = 0; i < pendingIconRequests->size(); ++i) { GSimpleAsyncResult* result = pendingIconRequests->at(i).get(); GetFaviconSurfaceAsyncData* data = static_cast(g_simple_async_result_get_op_res_gpointer(result)); if (!g_cancellable_is_cancelled(data->cancellable.get())) { if (error) g_simple_async_result_take_error(result, error.release()); else { data->icon = icon; data->shouldReleaseIconForPageURL = false; } } g_simple_async_result_complete(result); } deletePendingIconRequests(database, pendingIconRequests, pageURL); } static void didChangeIconForPageURLCallback(WKIconDatabaseRef wkIconDatabase, WKURLRef wkPageURL, const void* clientInfo) { WebKitFaviconDatabase* database = WEBKIT_FAVICON_DATABASE(clientInfo); if (!database->priv->iconDatabase->isUrlImportCompleted()) return; // Wait until there's an icon record in the database for this page URL. String pageURL = toImpl(wkPageURL)->string(); WebCore::Image* iconImage = database->priv->iconDatabase->imageForPageURL(pageURL, WebCore::IntSize(1, 1)); if (!iconImage || iconImage->isNull()) return; String currentIconURL; database->priv->iconDatabase->synchronousIconURLForPageURL(pageURL, currentIconURL); const String& iconURL = database->priv->pageURLToIconURLMap.get(pageURL); if (iconURL == currentIconURL) return; database->priv->pageURLToIconURLMap.set(pageURL, currentIconURL); g_signal_emit(database, signals[FAVICON_CHANGED], 0, pageURL.utf8().data(), currentIconURL.utf8().data()); } static void iconDataReadyForPageURLCallback(WKIconDatabaseRef wkIconDatabase, WKURLRef wkPageURL, const void* clientInfo) { ASSERT(isMainThread()); processPendingIconsForPageURL(WEBKIT_FAVICON_DATABASE(clientInfo), toImpl(wkPageURL)->string()); } WebKitFaviconDatabase* webkitFaviconDatabaseCreate(WebIconDatabase* iconDatabase) { WebKitFaviconDatabase* faviconDatabase = WEBKIT_FAVICON_DATABASE(g_object_new(WEBKIT_TYPE_FAVICON_DATABASE, NULL)); faviconDatabase->priv->iconDatabase = iconDatabase; WKIconDatabaseClient wkIconDatabaseClient = { kWKIconDatabaseClientCurrentVersion, faviconDatabase, // clientInfo didChangeIconForPageURLCallback, 0, // didRemoveAllIconsCallback iconDataReadyForPageURLCallback, }; WKIconDatabaseSetIconDatabaseClient(toAPI(iconDatabase), &wkIconDatabaseClient); return faviconDatabase; } static PendingIconRequestVector* getOrCreatePendingIconRequests(WebKitFaviconDatabase* database, const String& pageURL) { PendingIconRequestVector* icons = database->priv->pendingIconRequests.get(pageURL); if (!icons) { icons = new PendingIconRequestVector; database->priv->pendingIconRequests.set(pageURL, icons); } return icons; } static void setErrorForAsyncResult(GSimpleAsyncResult* result, WebKitFaviconDatabaseError error, const String& pageURL = String()) { ASSERT(result); switch (error) { case WEBKIT_FAVICON_DATABASE_ERROR_NOT_INITIALIZED: g_simple_async_result_set_error(result, WEBKIT_FAVICON_DATABASE_ERROR, error, _("Favicons database not initialized yet")); break; case WEBKIT_FAVICON_DATABASE_ERROR_FAVICON_NOT_FOUND: g_simple_async_result_set_error(result, WEBKIT_FAVICON_DATABASE_ERROR, error, _("Page %s does not have a favicon"), pageURL.utf8().data()); break; case WEBKIT_FAVICON_DATABASE_ERROR_FAVICON_UNKNOWN: g_simple_async_result_set_error(result, WEBKIT_FAVICON_DATABASE_ERROR, error, _("Unknown favicon for page %s"), pageURL.utf8().data()); break; default: ASSERT_NOT_REACHED(); } } GQuark webkit_favicon_database_error_quark(void) { return g_quark_from_static_string("WebKitFaviconDatabaseError"); } /** * webkit_favicon_database_get_favicon: * @database: a #WebKitFaviconDatabase * @page_uri: URI of the page for which we want to retrieve the favicon * @cancellable: (allow-none): A #GCancellable or %NULL. * @callback: (scope async): A #GAsyncReadyCallback to call when the request is * satisfied or %NULL if you don't care about the result. * @user_data: (closure): The data to pass to @callback. * * Asynchronously obtains a #cairo_surface_t of the favicon for the * given page URI. It returns the cached icon if it's in the database * asynchronously waiting for the icon to be read from the database. * * This is an asynchronous method. When the operation is finished, callback will * be invoked. You can then call webkit_favicon_database_get_favicon_finish() * to get the result of the operation. */ void webkit_favicon_database_get_favicon(WebKitFaviconDatabase* database, const gchar* pageURI, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData) { g_return_if_fail(WEBKIT_IS_FAVICON_DATABASE(database)); g_return_if_fail(pageURI); GRefPtr result = adoptGRef(g_simple_async_result_new(G_OBJECT(database), callback, userData, reinterpret_cast(webkit_favicon_database_get_favicon))); g_simple_async_result_set_check_cancellable(result.get(), cancellable); GetFaviconSurfaceAsyncData* data = createGetFaviconSurfaceAsyncData(); g_simple_async_result_set_op_res_gpointer(result.get(), data, reinterpret_cast(destroyGetFaviconSurfaceAsyncData)); data->faviconDatabase = database; data->pageURL = String::fromUTF8(pageURI); data->cancellable = cancellable; WebKitFaviconDatabasePrivate* priv = database->priv; WebIconDatabase* iconDatabaseImpl = priv->iconDatabase.get(); if (!iconDatabaseImpl->isOpen()) { setErrorForAsyncResult(result.get(), WEBKIT_FAVICON_DATABASE_ERROR_NOT_INITIALIZED); g_simple_async_result_complete_in_idle(result.get()); return; } if (data->pageURL.isEmpty() || data->pageURL.startsWith("about:")) { setErrorForAsyncResult(result.get(), WEBKIT_FAVICON_DATABASE_ERROR_FAVICON_NOT_FOUND, data->pageURL); g_simple_async_result_complete_in_idle(result.get()); return; } priv->iconDatabase->retainIconForPageURL(data->pageURL); // We ask for the icon directly. If we don't get the icon data now, // we'll be notified later (even if the database is still importing icons). GOwnPtr error; data->icon = getIconSurfaceSynchronously(database, data->pageURL, &error.outPtr()); if (data->icon) { g_simple_async_result_complete_in_idle(result.get()); return; } // At this point we still don't know whether we will get a valid icon for pageURL. data->shouldReleaseIconForPageURL = true; if (g_error_matches(error.get(), WEBKIT_FAVICON_DATABASE_ERROR, WEBKIT_FAVICON_DATABASE_ERROR_FAVICON_NOT_FOUND)) { g_simple_async_result_take_error(result.get(), error.release()); g_simple_async_result_complete_in_idle(result.get()); return; } // If there's not a valid icon, but there's an iconURL registered, // or it's still not registered but the import process hasn't // finished yet, we need to wait for iconDataReadyForPage to be // called before making and informed decision. String iconURLForPageURL; iconDatabaseImpl->synchronousIconURLForPageURL(data->pageURL, iconURLForPageURL); if (!iconURLForPageURL.isEmpty() || !iconDatabaseImpl->isUrlImportCompleted()) { PendingIconRequestVector* icons = getOrCreatePendingIconRequests(database, data->pageURL); ASSERT(icons); icons->append(result); return; } setErrorForAsyncResult(result.get(), WEBKIT_FAVICON_DATABASE_ERROR_FAVICON_UNKNOWN, data->pageURL); g_simple_async_result_complete_in_idle(result.get()); } /** * webkit_favicon_database_get_favicon_finish: * @database: a #WebKitFaviconDatabase * @result: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to webkit_favicon_database_get_favicon() * @error: (allow-none): Return location for error or %NULL. * * Finishes an operation started with webkit_favicon_database_get_favicon(). * * Returns: (transfer full): a new reference to a #cairo_surface_t, or * %NULL in case of error. */ cairo_surface_t* webkit_favicon_database_get_favicon_finish(WebKitFaviconDatabase* database, GAsyncResult* result, GError** error) { GSimpleAsyncResult* simpleResult = G_SIMPLE_ASYNC_RESULT(result); g_warn_if_fail(g_simple_async_result_get_source_tag(simpleResult) == webkit_favicon_database_get_favicon); if (g_simple_async_result_propagate_error(simpleResult, error)) return 0; GetFaviconSurfaceAsyncData* data = static_cast(g_simple_async_result_get_op_res_gpointer(simpleResult)); ASSERT(data); return cairo_surface_reference(data->icon.get()); } /** * webkit_favicon_database_get_favicon_uri: * @database: a #WebKitFaviconDatabase * @page_uri: URI of the page containing the icon * * Obtains the URI of the favicon for the given @page_uri. * * Returns: a newly allocated URI for the favicon, or %NULL if the * database doesn't have a favicon for @page_uri. */ gchar* webkit_favicon_database_get_favicon_uri(WebKitFaviconDatabase* database, const gchar* pageURL) { g_return_val_if_fail(WEBKIT_IS_FAVICON_DATABASE(database), 0); g_return_val_if_fail(pageURL, 0); ASSERT(isMainThread()); String iconURLForPageURL; database->priv->iconDatabase->synchronousIconURLForPageURL(String::fromUTF8(pageURL), iconURLForPageURL); if (iconURLForPageURL.isEmpty()) return 0; return g_strdup(iconURLForPageURL.utf8().data()); } /** * webkit_favicon_database_clear: * @database: a #WebKitFaviconDatabase * * Clears all icons from the database. */ void webkit_favicon_database_clear(WebKitFaviconDatabase* database) { g_return_if_fail(WEBKIT_IS_FAVICON_DATABASE(database)); database->priv->iconDatabase->removeAllIcons(); }