/* * Copyright (C) 2011 Christian Dywan * 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 Library General Public * License as published by the Free Software Foundation; either * version 2 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 "FileSystem.h" #include "GdkCairoUtilities.h" #include "IconDatabase.h" #include "IconDatabaseClient.h" #include "Image.h" #include "IntSize.h" #include "webkitfavicondatabaseprivate.h" #include "webkitglobals.h" #include "webkitglobalsprivate.h" #include "webkitmarshal.h" #include "webkitsecurityoriginprivate.h" #include "webkitwebframe.h" #include #include #include #include #include /** * SECTION:webkitfavicondatabase * @short_description: A WebKit favicon database * @Title: WebKitFaviconDatabase * * #WebKitFaviconDatabase provides access to the icons associated with * web sites. * * WebKit will automatically look for available icons in link elements * on opened pages as well as an existing favicon.ico and load the * images found into a memory cache if possible. That cache is frozen * to an on-disk database for persistence. * * The database is disabled by default. In order for icons to be * stored and accessed, you will need to set an icon database path * using webkit_favicon_database_set_path(). Disable the database * again passing %NULL to the previous call. * * If WebKitWebSettings::enable-private-browsing is %TRUE new icons * won't be added to the on-disk database and no existing icons will * be deleted from it. Nevertheless, WebKit will still store them in * the in-memory cache during the current execution. * * Since: 1.8 */ using namespace WebKit; using namespace WebCore; class PendingIconRequest; static void webkitFaviconDatabaseProcessPendingIconsForURI(WebKitFaviconDatabase*, const String& pageURI); static void webkitFaviconDatabaseImportFinished(WebKitFaviconDatabase*); static void webkitFaviconDatabaseGetIconPixbufCancelled(GCancellable*, PendingIconRequest*); static void webkitFaviconDatabaseClose(WebKitFaviconDatabase* database); class IconDatabaseClientGtk : public IconDatabaseClient { public: // IconDatabaseClient interface virtual void didRemoveAllIcons() { }; // Called when an icon is requested while the initial import is // going on. virtual void didImportIconURLForPageURL(const String& URL) { }; // Called whenever a retained icon is read from database. virtual void didImportIconDataForPageURL(const String& URL) { WebKitFaviconDatabase* database = webkit_get_favicon_database(); // We need to emit this here because webkitFaviconDatabaseDispatchDidReceiveIcon() // is only called for icons that have just being downloaded, and this is called // when icon data is imported from the database. g_signal_emit_by_name(database, "icon-loaded", URL.utf8().data()); webkitFaviconDatabaseProcessPendingIconsForURI(database, URL); } virtual void didChangeIconForPageURL(const String& URL) { // Called when the the favicon for a particular URL changes. // It does not mean that the new icon data is available yet. } virtual void didFinishURLImport() { webkitFaviconDatabaseImportFinished(webkit_get_favicon_database()); // Now that everything is imported enable pruning of old // icons. No icon will be removed during the import process // because we disable cleanups before opening the database. IconDatabase::allowDatabaseCleanup(); } }; class PendingIconRequest { public: PendingIconRequest(const String& pageURL, GSimpleAsyncResult* result, GCancellable* cancellable, IntSize iconSize) : m_pageURL(pageURL) , m_asyncResult(result) , m_cancellable(cancellable) , m_cancelledId(0) , m_iconSize(iconSize) { if (cancellable) { m_cancelledId = g_cancellable_connect(cancellable, G_CALLBACK(webkitFaviconDatabaseGetIconPixbufCancelled), this, 0); g_object_set_data_full(G_OBJECT(result), "cancellable", g_object_ref(cancellable), static_cast(g_object_unref)); } } ~PendingIconRequest() { if (m_cancelledId > 0) g_cancellable_disconnect(m_cancellable.get(), m_cancelledId); } const String& pageURL() { return m_pageURL; } GSimpleAsyncResult* asyncResult() { return m_asyncResult.get(); } const IntSize& iconSize() { return m_iconSize; } void asyncResultCancel() { ASSERT(m_asyncResult); g_simple_async_result_set_error(m_asyncResult.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED, "%s", _("Operation was cancelled")); g_simple_async_result_complete(m_asyncResult.get()); } void asyncResultCompleteInIdle(GdkPixbuf* icon) { ASSERT(m_asyncResult); g_simple_async_result_set_op_res_gpointer(m_asyncResult.get(), icon, 0); g_simple_async_result_complete_in_idle(m_asyncResult.get()); } void asyncResultComplete(GdkPixbuf* icon) { ASSERT(m_asyncResult); g_simple_async_result_set_op_res_gpointer(m_asyncResult.get(), icon, 0); g_simple_async_result_complete(m_asyncResult.get()); } private: String m_pageURL; GRefPtr m_asyncResult; GRefPtr m_cancellable; gulong m_cancelledId; IntSize m_iconSize; }; enum { PROP_0, PROP_PATH, }; enum { ICON_LOADED, LAST_SIGNAL }; static guint webkit_favicon_database_signals[LAST_SIGNAL] = { 0, }; G_DEFINE_TYPE(WebKitFaviconDatabase, webkit_favicon_database, G_TYPE_OBJECT) typedef Vector > PendingIconRequestVector; typedef HashMap PendingIconRequestMap; struct _WebKitFaviconDatabasePrivate { GOwnPtr path; IconDatabaseClientGtk iconDatabaseClient; PendingIconRequestMap pendingIconRequests; bool importFinished; }; static void webkit_favicon_database_finalize(GObject* object) { WebKitFaviconDatabase* database = WEBKIT_FAVICON_DATABASE(object); webkitFaviconDatabaseClose(database); database->priv->~WebKitFaviconDatabasePrivate(); G_OBJECT_CLASS(webkit_favicon_database_parent_class)->finalize(object); } static void webkit_favicon_database_set_property(GObject* object, guint propId, const GValue* value, GParamSpec* pspec) { WebKitFaviconDatabase* database = WEBKIT_FAVICON_DATABASE(object); switch (propId) { case PROP_PATH: webkit_favicon_database_set_path(database, g_value_get_string(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec); break; } } static void webkit_favicon_database_get_property(GObject* object, guint propId, GValue* value, GParamSpec* pspec) { WebKitFaviconDatabase* database = WEBKIT_FAVICON_DATABASE(object); switch (propId) { case PROP_PATH: g_value_set_string(value, webkit_favicon_database_get_path(database)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec); break; } } static void webkit_favicon_database_class_init(WebKitFaviconDatabaseClass* klass) { webkitInit(); GObjectClass* gobjectClass = G_OBJECT_CLASS(klass); gobjectClass->finalize = webkit_favicon_database_finalize; gobjectClass->set_property = webkit_favicon_database_set_property; gobjectClass->get_property = webkit_favicon_database_get_property; /** * WebKitFaviconDatabase:path: * * The absolute path of the icon database folder. * * Since: 1.8 */ g_object_class_install_property(gobjectClass, PROP_PATH, g_param_spec_string("path", _("Path"), _("The absolute path of the icon database folder"), NULL, WEBKIT_PARAM_READWRITE)); /** * WebKitFaviconDatabase::icon-loaded: * @database: the object on which the signal is emitted * @frame_uri: the URI of the main frame of a Web page containing * the icon * * This signal is fired if an icon is loaded on any * #WebKitWebView. If you are only interested in a particular * #WebKitWebView see #WebKitWebView::icon-loaded. * * Note that this signal carries the URI of the frame that loads * the icon, while #WebKitWebView::icon-loaded provides the URI * of the favicon. * * Since: 1.8 */ webkit_favicon_database_signals[ICON_LOADED] = g_signal_new("icon-loaded", G_TYPE_FROM_CLASS(klass), (GSignalFlags)G_SIGNAL_RUN_LAST, 0, 0, 0, webkit_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); g_type_class_add_private(klass, sizeof(WebKitFaviconDatabasePrivate)); } static void webkit_favicon_database_init(WebKitFaviconDatabase* database) { database->priv = G_TYPE_INSTANCE_GET_PRIVATE(database, WEBKIT_TYPE_FAVICON_DATABASE, WebKitFaviconDatabasePrivate); new (database->priv) WebKitFaviconDatabasePrivate(); } // Called from FrameLoaderClient::dispatchDidReceiveIcon() void webkitFaviconDatabaseDispatchDidReceiveIcon(WebKitFaviconDatabase* database, const char* frameURI) { g_signal_emit(database, webkit_favicon_database_signals[ICON_LOADED], 0, frameURI); // Retain the new icon. iconDatabase().retainIconForPageURL(String::fromUTF8(frameURI)); } /** * webkit_favicon_database_get_path: * @database: a #WebKitFaviconDatabase * * Determines the absolute path to the database folder on disk. * * Returns: the absolute path of the database folder, or %NULL * * Since: 1.8 */ const gchar* webkit_favicon_database_get_path(WebKitFaviconDatabase* database) { g_return_val_if_fail(WEBKIT_IS_FAVICON_DATABASE(database), 0); return database->priv->path.get(); } static void webkitFaviconDatabaseClose(WebKitFaviconDatabase* database) { if (iconDatabase().isEnabled()) { iconDatabase().setEnabled(false); iconDatabase().close(); } } /** * webkit_favicon_database_set_path: * @database: a #WebKitFaviconDatabase * @path: (allow-none): an absolute path to the icon database folder * or %NULL to disable the database * * Specifies the absolute path to the database folder on disk. The * icon database will only be enabled after a call to this method. * * Passing %NULL or "" as path disables the icon database. * * Since: 1.8 */ void webkit_favicon_database_set_path(WebKitFaviconDatabase* database, const gchar* path) { g_return_if_fail(WEBKIT_IS_FAVICON_DATABASE(database)); // Always try to close because the deprecated icondatabase is opened by default. webkitFaviconDatabaseClose(database); database->priv->importFinished = false; if (!path || !path[0]) { database->priv->path.set(0); iconDatabase().setEnabled(false); return; } iconDatabase().setClient(&database->priv->iconDatabaseClient); IconDatabase::delayDatabaseCleanup(); iconDatabase().setEnabled(true); if (!iconDatabase().open(filenameToString(path), IconDatabase::defaultDatabaseFilename())) { IconDatabase::allowDatabaseCleanup(); return; } database->priv->path.set(g_strdup(path)); } /** * webkit_favicon_database_get_favicon_uri: * @database: a #WebKitFaviconDatabase * @page_uri: URI of the page containing the icon * * Obtains the URI for the favicon for the given page URI. * See also webkit_web_view_get_icon_uri(). * * Returns: a newly allocated URI for the favicon, or %NULL * * Since: 1.8 */ gchar* webkit_favicon_database_get_favicon_uri(WebKitFaviconDatabase* database, const gchar* pageURI) { g_return_val_if_fail(WEBKIT_IS_FAVICON_DATABASE(database), 0); g_return_val_if_fail(pageURI, 0); ASSERT(isMainThread()); String iconURI = iconDatabase().synchronousIconURLForPageURL(String::fromUTF8(pageURI)); if (iconURI.isEmpty()) return 0; return g_strdup(iconURI.utf8().data()); } static GdkPixbuf* getIconPixbufSynchronously(WebKitFaviconDatabase* database, const String& pageURL, const IntSize& iconSize) { ASSERT(isMainThread()); // The exact size we pass is irrelevant to the iconDatabase code. // We must pass something greater than 0x0 to get a pixbuf. RefPtr surface = iconDatabase().synchronousNativeIconForPageURL(pageURL, !iconSize.isZero() ? iconSize : IntSize(1, 1)); if (!surface) return 0; GRefPtr pixbuf = adoptGRef(cairoImageSurfaceToGdkPixbuf(surface.get())); if (!pixbuf) return 0; // A size of (0, 0) means the maximum available size. int pixbufWidth = gdk_pixbuf_get_width(pixbuf.get()); int pixbufHeight = gdk_pixbuf_get_height(pixbuf.get()); if (!iconSize.isZero() && (pixbufWidth != iconSize.width() || pixbufHeight != iconSize.height())) pixbuf = adoptGRef(gdk_pixbuf_scale_simple(pixbuf.get(), iconSize.width(), iconSize.height(), GDK_INTERP_BILINEAR)); return pixbuf.leakRef(); } /** * webkit_favicon_database_try_get_favicon_pixbuf: * @database: a #WebKitFaviconDatabase * @page_uri: URI of the page containing the icon * @width: the desired width for the icon * @height: the desired height for the icon * * Obtains a #GdkPixbuf of the favicon for the given page URI, or * %NULL if there is no icon for the given page or it hasn't been * loaded from disk yet. Use webkit_favicon_database_get_favicon_uri() * if you need to distinguish these cases. To make sure this method * will return a valid icon when the given URI has one, you should * connect to #WebKitFaviconDatabase::icon-loaded and use this function * in the callback. * * If @width and @height ar both 0 then this method will return the * maximum available size for the icon. Note that if you specify a * different size the icon will be scaled each time you call this * function. * * Returns: (transfer full): a new reference to a #GdkPixbuf, or %NULL * if the given URI doesn't have an icon or it hasn't been loaded yet. * * Since: 1.8 */ GdkPixbuf* webkit_favicon_database_try_get_favicon_pixbuf(WebKitFaviconDatabase* database, const gchar* pageURI, guint width, guint height) { g_return_val_if_fail(WEBKIT_IS_FAVICON_DATABASE(database), 0); g_return_val_if_fail(pageURI, 0); g_return_val_if_fail((width && height) || (!width && !height), 0); return getIconPixbufSynchronously(database, String::fromUTF8(pageURI), IntSize(width, height)); } static PendingIconRequestVector* webkitFaviconDatabaseGetOrCreateRequests(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 webkitfavicondatabaseDeleteRequests(WebKitFaviconDatabase* database, PendingIconRequestVector* requests, const String& pageURL) { database->priv->pendingIconRequests.remove(pageURL); delete requests; } static void getIconPixbufCancelled(void* userData) { PendingIconRequest* request = static_cast(userData); request->asyncResultCancel(); const String& pageURL = request->pageURL(); WebKitFaviconDatabase* database = webkit_get_favicon_database(); PendingIconRequestVector* icons = database->priv->pendingIconRequests.get(pageURL); if (!icons) return; size_t itemIndex = icons->find(request); if (itemIndex != notFound) icons->remove(itemIndex); if (icons->isEmpty()) webkitfavicondatabaseDeleteRequests(database, icons, pageURL); } static void webkitFaviconDatabaseGetIconPixbufCancelled(GCancellable* cancellable, PendingIconRequest* request) { // Handle cancelled in a in idle since it might be called from any thread. callOnMainThread(getIconPixbufCancelled, request); } /** * webkit_favicon_database_get_favicon_pixbuf: * @database: a #WebKitFaviconDatabase * @page_uri: URI of the page containing the icon * @width: the desired width for the icon * @height: the desired height for the icon * @cancellable: (allow-none): A #GCancellable or %NULL. * @callback: (allow-none): A #GAsyncReadyCallback to call when the request is * satisfied or %NULL if you don't care about the result. * @user_data: The data to pass to @callback. * * Asynchronously obtains a #GdkPixbuf of the favicon for the given * page URI. The advantage of this method over * webkit_favicon_database_try_get_favicon_pixbuf() is that it always 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_pixbuf_finish() * to get the result of the operation. * See also webkit_favicon_database_try_get_favicon_pixbuf(). * * If @width and @height are both 0 then this method will return the * maximum available size for the icon. Note that if you specify a * different size the icon will be scaled each time you call this * function. * * Since: 1.8 */ void webkit_favicon_database_get_favicon_pixbuf(WebKitFaviconDatabase* database, const gchar* pageURI, guint width, guint height, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData) { g_return_if_fail(WEBKIT_IS_FAVICON_DATABASE(database)); g_return_if_fail(pageURI); g_return_if_fail((width && height) || (!width && !height)); GRefPtr result = adoptGRef(g_simple_async_result_new(G_OBJECT(database), callback, userData, reinterpret_cast(webkit_favicon_database_get_favicon_pixbuf))); // If we don't have an icon for the given URI or the database is not opened then return ASAP. We have to check that // because if the database is not opened it will skip (and not notify about) every single icon load request if ((database->priv->importFinished && iconDatabase().synchronousIconURLForPageURL(String::fromUTF8(pageURI)).isEmpty()) || !iconDatabase().isOpen()) { g_simple_async_result_set_op_res_gpointer(result.get(), 0, 0); g_simple_async_result_complete_in_idle(result.get()); return; } String pageURL = String::fromUTF8(pageURI); PendingIconRequest* request = new PendingIconRequest(pageURL, result.get(), cancellable, IntSize(width, height)); // Register icon request before asking for the icon to avoid race conditions. PendingIconRequestVector* icons = webkitFaviconDatabaseGetOrCreateRequests(database, pageURL); ASSERT(icons); icons->append(adoptPtr(request)); // 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). GdkPixbuf* pixbuf = getIconPixbufSynchronously(database, pageURL, IntSize(width, height)); if (!pixbuf) return; request->asyncResultCompleteInIdle(pixbuf); // Remove the request we have just created as it isn't pending // anymore because we already have the pixbuf. ASSERT(icons->last().get() == request); icons->removeLast(); if (icons->isEmpty()) webkitfavicondatabaseDeleteRequests(database, icons, pageURL); } /** * webkit_favicon_database_get_favicon_pixbuf_finish: * @database: a #WebKitFaviconDatabase * @result: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to webkit_favicon_database_get_favicon_pixbuf() * @error: (allow-none): Return location for error or %NULL. * * Finishes an operation started with webkit_favicon_database_get_favicon_pixbuf(). * * Returns: (transfer full): a new reference to a #GdkPixbuf, or %NULL. * * Since: 1.8 */ GdkPixbuf* webkit_favicon_database_get_favicon_pixbuf_finish(WebKitFaviconDatabase* database, GAsyncResult* result, GError** error) { GSimpleAsyncResult* simpleResult = G_SIMPLE_ASYNC_RESULT(result); g_return_val_if_fail(g_simple_async_result_get_source_tag(simpleResult) == webkit_favicon_database_get_favicon_pixbuf, 0); if (g_simple_async_result_propagate_error(simpleResult, error)) return 0; GCancellable* cancellable = static_cast(g_object_get_data(G_OBJECT(simpleResult), "cancellable")); if (cancellable && g_cancellable_is_cancelled(cancellable)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_CANCELLED, _("Operation was cancelled")); return 0; } GdkPixbuf* icon = static_cast(g_simple_async_result_get_op_res_gpointer(simpleResult)); if (!icon) return 0; return static_cast(icon); } static void webkitFaviconDatabaseProcessPendingIconsForURI(WebKitFaviconDatabase* database, const String& pageURL) { PendingIconRequestVector* icons = database->priv->pendingIconRequests.get(pageURL); if (!icons) return; for (size_t i = 0; i < icons->size(); ++i) { PendingIconRequest* request = icons->at(i).get(); if (request->asyncResult()) request->asyncResultComplete(getIconPixbufSynchronously(database, pageURL, request->iconSize())); } webkitfavicondatabaseDeleteRequests(database, icons, pageURL); } static void webkitFaviconDatabaseImportFinished(WebKitFaviconDatabase* database) { ASSERT(isMainThread()); database->priv->importFinished = true; // Import is complete, process pending requests for pages that are not in the database, // since didImportIconDataForPageURL() will never be called for them. Vector toDeleteURLs; PendingIconRequestMap::const_iterator end = database->priv->pendingIconRequests.end(); for (PendingIconRequestMap::const_iterator iter = database->priv->pendingIconRequests.begin(); iter != end; ++iter) { String iconURL = iconDatabase().synchronousIconURLForPageURL(iter->key); if (!iconURL.isEmpty()) continue; PendingIconRequestVector* icons = iter->value; for (size_t i = 0; i < icons->size(); ++i) { PendingIconRequest* request = icons->at(i).get(); if (request->asyncResult()) request->asyncResultComplete(0); } toDeleteURLs.append(iter->key); } for (size_t i = 0; i < toDeleteURLs.size(); ++i) webkitfavicondatabaseDeleteRequests(database, database->priv->pendingIconRequests.get(toDeleteURLs[i]), toDeleteURLs[i]); } /** * webkit_favicon_database_clear: * @database: a #WebKitFaviconDatabase * * Clears all icons from the database. * * Since: 1.8 */ void webkit_favicon_database_clear(WebKitFaviconDatabase* database) { g_return_if_fail(WEBKIT_IS_FAVICON_DATABASE(database)); iconDatabase().removeAllIcons(); }