1/* 2 * Copyright (C) 2012 Igalia S.L. 3 * 4 * This library is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU Lesser General Public 6 * License as published by the Free Software Foundation; either 7 * version 2,1 of the License, or (at your option) any later version. 8 * 9 * This library is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 * Library General Public License for more details. 13 * 14 * You should have received a copy of the GNU Library General Public License 15 * along with this library; see the file COPYING.LIB. If not, write to 16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 17 * Boston, MA 02110-1301, USA. 18 */ 19 20#include "config.h" 21#include "WebKitFaviconDatabase.h" 22 23#include "WebKitFaviconDatabasePrivate.h" 24#include "WebKitMarshal.h" 25#include "WebKitPrivate.h" 26#include <WebCore/FileSystem.h> 27#include <WebCore/Image.h> 28#include <WebCore/IntSize.h> 29#include <WebCore/RefPtrCairo.h> 30#include <glib/gi18n-lib.h> 31#include <wtf/RunLoop.h> 32#include <wtf/gobject/GRefPtr.h> 33#include <wtf/gobject/GUniquePtr.h> 34#include <wtf/text/CString.h> 35 36using namespace WebKit; 37using namespace WebCore; 38 39/** 40 * SECTION: WebKitFaviconDatabase 41 * @Short_description: A WebKit favicon database 42 * @Title: WebKitFaviconDatabase 43 * 44 * #WebKitFaviconDatabase provides access to the icons associated with 45 * web sites. 46 * 47 * WebKit will automatically look for available icons in <link> 48 * elements on opened pages as well as an existing favicon.ico and 49 * load the images found into a memory cache if possible. That cache 50 * is frozen to an on-disk database for persistence. 51 * 52 * If #WebKitSettings:enable-private-browsing is %TRUE, new icons 53 * won't be added to the on-disk database and no existing icons will 54 * be deleted from it. Nevertheless, WebKit will still store them in 55 * the in-memory cache during the current execution. 56 * 57 */ 58 59enum { 60 FAVICON_CHANGED, 61 62 LAST_SIGNAL 63}; 64 65static guint signals[LAST_SIGNAL] = { 0, }; 66 67typedef Vector<GRefPtr<GTask> > PendingIconRequestVector; 68typedef HashMap<String, PendingIconRequestVector*> PendingIconRequestMap; 69 70struct _WebKitFaviconDatabasePrivate { 71 RefPtr<WebIconDatabase> iconDatabase; 72 PendingIconRequestMap pendingIconRequests; 73 HashMap<String, String> pageURLToIconURLMap; 74}; 75 76WEBKIT_DEFINE_TYPE(WebKitFaviconDatabase, webkit_favicon_database, G_TYPE_OBJECT) 77 78static void webkitFaviconDatabaseDispose(GObject* object) 79{ 80 WebKitFaviconDatabase* database = WEBKIT_FAVICON_DATABASE(object); 81 82 WebKitFaviconDatabasePrivate* priv = database->priv; 83 if (priv->iconDatabase->isOpen()) 84 priv->iconDatabase->close(); 85 86 G_OBJECT_CLASS(webkit_favicon_database_parent_class)->dispose(object); 87} 88 89static void webkit_favicon_database_class_init(WebKitFaviconDatabaseClass* faviconDatabaseClass) 90{ 91 GObjectClass* gObjectClass = G_OBJECT_CLASS(faviconDatabaseClass); 92 gObjectClass->dispose = webkitFaviconDatabaseDispose; 93 94 /** 95 * WebKitFaviconDatabase::favicon-changed: 96 * @database: the object on which the signal is emitted 97 * @page_uri: the URI of the Web page containing the icon 98 * @favicon_uri: the URI of the favicon 99 * 100 * This signal is emitted when the favicon URI of @page_uri has 101 * been changed to @favicon_uri in the database. You can connect 102 * to this signal and call webkit_favicon_database_get_favicon() 103 * to get the favicon. If you are interested in the favicon of a 104 * #WebKitWebView it's easier to use the #WebKitWebView:favicon 105 * property. See webkit_web_view_get_favicon() for more details. 106 */ 107 signals[FAVICON_CHANGED] = 108 g_signal_new( 109 "favicon-changed", 110 G_TYPE_FROM_CLASS(faviconDatabaseClass), 111 G_SIGNAL_RUN_LAST, 112 0, 0, 0, 113 webkit_marshal_VOID__STRING_STRING, 114 G_TYPE_NONE, 2, 115 G_TYPE_STRING, 116 G_TYPE_STRING); 117} 118 119struct GetFaviconSurfaceAsyncData { 120 ~GetFaviconSurfaceAsyncData() 121 { 122 if (shouldReleaseIconForPageURL) 123 faviconDatabase->priv->iconDatabase->releaseIconForPageURL(pageURL); 124 } 125 126 GRefPtr<WebKitFaviconDatabase> faviconDatabase; 127 String pageURL; 128 RefPtr<cairo_surface_t> icon; 129 GRefPtr<GCancellable> cancellable; 130 bool shouldReleaseIconForPageURL; 131}; 132WEBKIT_DEFINE_ASYNC_DATA_STRUCT(GetFaviconSurfaceAsyncData) 133 134static PassRefPtr<cairo_surface_t> getIconSurfaceSynchronously(WebKitFaviconDatabase* database, const String& pageURL, GError** error) 135{ 136 ASSERT(RunLoop::isMain()); 137 138 // The exact size we pass is irrelevant to the iconDatabase code. 139 // We must pass something greater than 0x0 to get an icon. 140 WebCore::Image* iconImage = database->priv->iconDatabase->imageForPageURL(pageURL, WebCore::IntSize(1, 1)); 141 if (!iconImage) { 142 g_set_error(error, WEBKIT_FAVICON_DATABASE_ERROR, WEBKIT_FAVICON_DATABASE_ERROR_FAVICON_UNKNOWN, _("Unknown favicon for page %s"), pageURL.utf8().data()); 143 return 0; 144 } 145 146 RefPtr<cairo_surface_t> surface = iconImage->nativeImageForCurrentFrame(); 147 if (!surface) { 148 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()); 149 return 0; 150 } 151 152 return surface.release(); 153} 154 155static void deletePendingIconRequests(WebKitFaviconDatabase* database, PendingIconRequestVector* requests, const String& pageURL) 156{ 157 database->priv->pendingIconRequests.remove(pageURL); 158 delete requests; 159} 160 161static void processPendingIconsForPageURL(WebKitFaviconDatabase* database, const String& pageURL) 162{ 163 PendingIconRequestVector* pendingIconRequests = database->priv->pendingIconRequests.get(pageURL); 164 if (!pendingIconRequests) 165 return; 166 167 GUniqueOutPtr<GError> error; 168 RefPtr<cairo_surface_t> icon = getIconSurfaceSynchronously(database, pageURL, &error.outPtr()); 169 170 for (size_t i = 0; i < pendingIconRequests->size(); ++i) { 171 GTask* task = pendingIconRequests->at(i).get(); 172 if (error) 173 g_task_return_error(task, error.release().release()); 174 else { 175 GetFaviconSurfaceAsyncData* data = static_cast<GetFaviconSurfaceAsyncData*>(g_task_get_task_data(task)); 176 data->icon = icon; 177 data->shouldReleaseIconForPageURL = false; 178 g_task_return_boolean(task, TRUE); 179 } 180 } 181 deletePendingIconRequests(database, pendingIconRequests, pageURL); 182} 183 184static void didChangeIconForPageURLCallback(WKIconDatabaseRef, WKURLRef wkPageURL, const void* clientInfo) 185{ 186 WebKitFaviconDatabase* database = WEBKIT_FAVICON_DATABASE(clientInfo); 187 if (!database->priv->iconDatabase->isUrlImportCompleted()) 188 return; 189 190 // Wait until there's an icon record in the database for this page URL. 191 String pageURL = toImpl(wkPageURL)->string(); 192 WebCore::Image* iconImage = database->priv->iconDatabase->imageForPageURL(pageURL, WebCore::IntSize(1, 1)); 193 if (!iconImage || iconImage->isNull()) 194 return; 195 196 String currentIconURL; 197 database->priv->iconDatabase->synchronousIconURLForPageURL(pageURL, currentIconURL); 198 const String& iconURL = database->priv->pageURLToIconURLMap.get(pageURL); 199 if (iconURL == currentIconURL) 200 return; 201 202 database->priv->pageURLToIconURLMap.set(pageURL, currentIconURL); 203 g_signal_emit(database, signals[FAVICON_CHANGED], 0, pageURL.utf8().data(), currentIconURL.utf8().data()); 204} 205 206static void iconDataReadyForPageURLCallback(WKIconDatabaseRef, WKURLRef wkPageURL, const void* clientInfo) 207{ 208 ASSERT(RunLoop::isMain()); 209 processPendingIconsForPageURL(WEBKIT_FAVICON_DATABASE(clientInfo), toImpl(wkPageURL)->string()); 210} 211 212WebKitFaviconDatabase* webkitFaviconDatabaseCreate(WebIconDatabase* iconDatabase) 213{ 214 WebKitFaviconDatabase* faviconDatabase = WEBKIT_FAVICON_DATABASE(g_object_new(WEBKIT_TYPE_FAVICON_DATABASE, NULL)); 215 faviconDatabase->priv->iconDatabase = iconDatabase; 216 217 WKIconDatabaseClientV1 wkIconDatabaseClient = { 218 { 219 1, // version 220 faviconDatabase, // clientInfo 221 }, 222 didChangeIconForPageURLCallback, 223 0, // didRemoveAllIconsCallback 224 iconDataReadyForPageURLCallback, 225 }; 226 WKIconDatabaseSetIconDatabaseClient(toAPI(iconDatabase), &wkIconDatabaseClient.base); 227 return faviconDatabase; 228} 229 230static PendingIconRequestVector* getOrCreatePendingIconRequests(WebKitFaviconDatabase* database, const String& pageURL) 231{ 232 PendingIconRequestVector* icons = database->priv->pendingIconRequests.get(pageURL); 233 if (!icons) { 234 icons = new PendingIconRequestVector; 235 database->priv->pendingIconRequests.set(pageURL, icons); 236 } 237 238 return icons; 239} 240 241GQuark webkit_favicon_database_error_quark(void) 242{ 243 return g_quark_from_static_string("WebKitFaviconDatabaseError"); 244} 245 246/** 247 * webkit_favicon_database_get_favicon: 248 * @database: a #WebKitFaviconDatabase 249 * @page_uri: URI of the page for which we want to retrieve the favicon 250 * @cancellable: (allow-none): A #GCancellable or %NULL. 251 * @callback: (scope async): A #GAsyncReadyCallback to call when the request is 252 * satisfied or %NULL if you don't care about the result. 253 * @user_data: (closure): The data to pass to @callback. 254 * 255 * Asynchronously obtains a #cairo_surface_t of the favicon for the 256 * given page URI. It returns the cached icon if it's in the database 257 * asynchronously waiting for the icon to be read from the database. 258 * 259 * This is an asynchronous method. When the operation is finished, callback will 260 * be invoked. You can then call webkit_favicon_database_get_favicon_finish() 261 * to get the result of the operation. 262 */ 263void webkit_favicon_database_get_favicon(WebKitFaviconDatabase* database, const gchar* pageURI, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData) 264{ 265 g_return_if_fail(WEBKIT_IS_FAVICON_DATABASE(database)); 266 g_return_if_fail(pageURI); 267 268 WebKitFaviconDatabasePrivate* priv = database->priv; 269 WebIconDatabase* iconDatabaseImpl = priv->iconDatabase.get(); 270 if (!iconDatabaseImpl->isOpen()) { 271 g_task_report_new_error(database, callback, userData, 0, 272 WEBKIT_FAVICON_DATABASE_ERROR, WEBKIT_FAVICON_DATABASE_ERROR_NOT_INITIALIZED, _("Favicons database not initialized yet")); 273 return; 274 } 275 276 if (g_str_has_prefix(pageURI, "about:")) { 277 g_task_report_new_error(database, callback, userData, 0, 278 WEBKIT_FAVICON_DATABASE_ERROR, WEBKIT_FAVICON_DATABASE_ERROR_FAVICON_NOT_FOUND, _("Page %s does not have a favicon"), pageURI); 279 return; 280 } 281 282 GRefPtr<GTask> task = adoptGRef(g_task_new(database, cancellable, callback, userData)); 283 284 GetFaviconSurfaceAsyncData* data = createGetFaviconSurfaceAsyncData(); 285 data->faviconDatabase = database; 286 data->pageURL = String::fromUTF8(pageURI); 287 g_task_set_task_data(task.get(), data, reinterpret_cast<GDestroyNotify>(destroyGetFaviconSurfaceAsyncData)); 288 289 priv->iconDatabase->retainIconForPageURL(data->pageURL); 290 291 // We ask for the icon directly. If we don't get the icon data now, 292 // we'll be notified later (even if the database is still importing icons). 293 GUniqueOutPtr<GError> error; 294 data->icon = getIconSurfaceSynchronously(database, data->pageURL, &error.outPtr()); 295 if (data->icon) { 296 g_task_return_boolean(task.get(), TRUE); 297 return; 298 } 299 300 // At this point we still don't know whether we will get a valid icon for pageURL. 301 data->shouldReleaseIconForPageURL = true; 302 303 if (g_error_matches(error.get(), WEBKIT_FAVICON_DATABASE_ERROR, WEBKIT_FAVICON_DATABASE_ERROR_FAVICON_NOT_FOUND)) { 304 g_task_return_error(task.get(), error.release().release()); 305 return; 306 } 307 308 // If there's not a valid icon, but there's an iconURL registered, 309 // or it's still not registered but the import process hasn't 310 // finished yet, we need to wait for iconDataReadyForPage to be 311 // called before making and informed decision. 312 String iconURLForPageURL; 313 iconDatabaseImpl->synchronousIconURLForPageURL(data->pageURL, iconURLForPageURL); 314 if (!iconURLForPageURL.isEmpty() || !iconDatabaseImpl->isUrlImportCompleted()) { 315 PendingIconRequestVector* iconRequests = getOrCreatePendingIconRequests(database, data->pageURL); 316 ASSERT(iconRequests); 317 iconRequests->append(task); 318 return; 319 } 320 321 g_task_return_new_error(task.get(), WEBKIT_FAVICON_DATABASE_ERROR, WEBKIT_FAVICON_DATABASE_ERROR_FAVICON_UNKNOWN, 322 _("Unknown favicon for page %s"), pageURI); 323} 324 325/** 326 * webkit_favicon_database_get_favicon_finish: 327 * @database: a #WebKitFaviconDatabase 328 * @result: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to webkit_favicon_database_get_favicon() 329 * @error: (allow-none): Return location for error or %NULL. 330 * 331 * Finishes an operation started with webkit_favicon_database_get_favicon(). 332 * 333 * Returns: (transfer full): a new reference to a #cairo_surface_t, or 334 * %NULL in case of error. 335 */ 336cairo_surface_t* webkit_favicon_database_get_favicon_finish(WebKitFaviconDatabase* database, GAsyncResult* result, GError** error) 337{ 338 g_return_val_if_fail(WEBKIT_IS_FAVICON_DATABASE(database), 0); 339 g_return_val_if_fail(g_task_is_valid(result, database), 0); 340 341 GTask* task = G_TASK(result); 342 if (!g_task_propagate_boolean(task, error)) 343 return 0; 344 345 GetFaviconSurfaceAsyncData* data = static_cast<GetFaviconSurfaceAsyncData*>(g_task_get_task_data(task)); 346 return cairo_surface_reference(data->icon.get()); 347} 348 349/** 350 * webkit_favicon_database_get_favicon_uri: 351 * @database: a #WebKitFaviconDatabase 352 * @page_uri: URI of the page containing the icon 353 * 354 * Obtains the URI of the favicon for the given @page_uri. 355 * 356 * Returns: a newly allocated URI for the favicon, or %NULL if the 357 * database doesn't have a favicon for @page_uri. 358 */ 359gchar* webkit_favicon_database_get_favicon_uri(WebKitFaviconDatabase* database, const gchar* pageURL) 360{ 361 g_return_val_if_fail(WEBKIT_IS_FAVICON_DATABASE(database), 0); 362 g_return_val_if_fail(pageURL, 0); 363 ASSERT(RunLoop::isMain()); 364 365 String iconURLForPageURL; 366 database->priv->iconDatabase->synchronousIconURLForPageURL(String::fromUTF8(pageURL), iconURLForPageURL); 367 if (iconURLForPageURL.isEmpty()) 368 return 0; 369 370 return g_strdup(iconURLForPageURL.utf8().data()); 371} 372 373/** 374 * webkit_favicon_database_clear: 375 * @database: a #WebKitFaviconDatabase 376 * 377 * Clears all icons from the database. 378 */ 379void webkit_favicon_database_clear(WebKitFaviconDatabase* database) 380{ 381 g_return_if_fail(WEBKIT_IS_FAVICON_DATABASE(database)); 382 383 database->priv->iconDatabase->removeAllIcons(); 384} 385