1 | /* |
2 | * Copyright (C) 2012, 2017 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 "IconDatabase.h" |
24 | #include "WebKitFaviconDatabasePrivate.h" |
25 | #include "WebPreferences.h" |
26 | #include <WebCore/Image.h> |
27 | #include <WebCore/IntSize.h> |
28 | #include <WebCore/RefPtrCairo.h> |
29 | #include <WebCore/SharedBuffer.h> |
30 | #include <glib/gi18n-lib.h> |
31 | #include <wtf/FileSystem.h> |
32 | #include <wtf/RunLoop.h> |
33 | #include <wtf/SetForScope.h> |
34 | #include <wtf/glib/GRefPtr.h> |
35 | #include <wtf/glib/GUniquePtr.h> |
36 | #include <wtf/glib/WTFGType.h> |
37 | #include <wtf/text/CString.h> |
38 | #include <wtf/text/StringHash.h> |
39 | |
40 | using namespace WebKit; |
41 | using namespace WebCore; |
42 | |
43 | /** |
44 | * SECTION: WebKitFaviconDatabase |
45 | * @Short_description: A WebKit favicon database |
46 | * @Title: WebKitFaviconDatabase |
47 | * |
48 | * #WebKitFaviconDatabase provides access to the icons associated with |
49 | * web sites. |
50 | * |
51 | * WebKit will automatically look for available icons in <link> |
52 | * elements on opened pages as well as an existing favicon.ico and |
53 | * load the images found into a memory cache if possible. That cache |
54 | * is frozen to an on-disk database for persistence. |
55 | * |
56 | * If #WebKitSettings:enable-private-browsing is %TRUE, new icons |
57 | * won't be added to the on-disk database and no existing icons will |
58 | * be deleted from it. Nevertheless, WebKit will still store them in |
59 | * the in-memory cache during the current execution. |
60 | * |
61 | */ |
62 | |
63 | enum { |
64 | FAVICON_CHANGED, |
65 | |
66 | LAST_SIGNAL |
67 | }; |
68 | |
69 | static guint signals[LAST_SIGNAL] = { 0, }; |
70 | |
71 | typedef Vector<GRefPtr<GTask> > PendingIconRequestVector; |
72 | typedef HashMap<String, PendingIconRequestVector*> PendingIconRequestMap; |
73 | |
74 | struct _WebKitFaviconDatabasePrivate { |
75 | std::unique_ptr<IconDatabase> iconDatabase; |
76 | Vector<std::pair<String, Function<void(bool)>>> pendingLoadDecisions; |
77 | PendingIconRequestMap pendingIconRequests; |
78 | HashMap<String, String> pageURLToIconURLMap; |
79 | bool isURLImportCompleted; |
80 | bool isSettingIcon; |
81 | }; |
82 | |
83 | WEBKIT_DEFINE_TYPE(WebKitFaviconDatabase, webkit_favicon_database, G_TYPE_OBJECT) |
84 | |
85 | static void webkitFaviconDatabaseDispose(GObject* object) |
86 | { |
87 | WebKitFaviconDatabase* database = WEBKIT_FAVICON_DATABASE(object); |
88 | |
89 | if (webkitFaviconDatabaseIsOpen(database)) |
90 | database->priv->iconDatabase->close(); |
91 | |
92 | G_OBJECT_CLASS(webkit_favicon_database_parent_class)->dispose(object); |
93 | } |
94 | |
95 | static void webkit_favicon_database_class_init(WebKitFaviconDatabaseClass* faviconDatabaseClass) |
96 | { |
97 | GObjectClass* gObjectClass = G_OBJECT_CLASS(faviconDatabaseClass); |
98 | gObjectClass->dispose = webkitFaviconDatabaseDispose; |
99 | |
100 | /** |
101 | * WebKitFaviconDatabase::favicon-changed: |
102 | * @database: the object on which the signal is emitted |
103 | * @page_uri: the URI of the Web page containing the icon |
104 | * @favicon_uri: the URI of the favicon |
105 | * |
106 | * This signal is emitted when the favicon URI of @page_uri has |
107 | * been changed to @favicon_uri in the database. You can connect |
108 | * to this signal and call webkit_favicon_database_get_favicon() |
109 | * to get the favicon. If you are interested in the favicon of a |
110 | * #WebKitWebView it's easier to use the #WebKitWebView:favicon |
111 | * property. See webkit_web_view_get_favicon() for more details. |
112 | */ |
113 | signals[FAVICON_CHANGED] = g_signal_new( |
114 | "favicon-changed" , |
115 | G_TYPE_FROM_CLASS(faviconDatabaseClass), |
116 | G_SIGNAL_RUN_LAST, |
117 | 0, nullptr, nullptr, |
118 | g_cclosure_marshal_generic, |
119 | G_TYPE_NONE, 2, |
120 | G_TYPE_STRING, |
121 | G_TYPE_STRING); |
122 | } |
123 | |
124 | #if PLATFORM(GTK) |
125 | struct GetFaviconSurfaceAsyncData { |
126 | ~GetFaviconSurfaceAsyncData() |
127 | { |
128 | if (shouldReleaseIconForPageURL) |
129 | faviconDatabase->priv->iconDatabase->releaseIconForPageURL(pageURL); |
130 | } |
131 | |
132 | GRefPtr<WebKitFaviconDatabase> faviconDatabase; |
133 | String pageURL; |
134 | RefPtr<cairo_surface_t> icon; |
135 | GRefPtr<GCancellable> cancellable; |
136 | bool shouldReleaseIconForPageURL; |
137 | }; |
138 | WEBKIT_DEFINE_ASYNC_DATA_STRUCT(GetFaviconSurfaceAsyncData) |
139 | |
140 | static RefPtr<cairo_surface_t> getIconSurfaceSynchronously(WebKitFaviconDatabase* database, const String& pageURL, GError** error) |
141 | { |
142 | ASSERT(RunLoop::isMain()); |
143 | |
144 | // The exact size we pass is irrelevant to the iconDatabase code. |
145 | // We must pass something greater than 0x0 to get an icon. |
146 | auto iconData = database->priv->iconDatabase->synchronousIconForPageURL(pageURL, WebCore::IntSize(1, 1)); |
147 | if (iconData.second == IconDatabase::IsKnownIcon::No) { |
148 | g_set_error(error, WEBKIT_FAVICON_DATABASE_ERROR, WEBKIT_FAVICON_DATABASE_ERROR_FAVICON_UNKNOWN, _("Unknown favicon for page %s" ), pageURL.utf8().data()); |
149 | return nullptr; |
150 | } |
151 | |
152 | if (!iconData.first) { |
153 | 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()); |
154 | return nullptr; |
155 | } |
156 | |
157 | return iconData.first; |
158 | } |
159 | |
160 | static void deletePendingIconRequests(WebKitFaviconDatabase* database, PendingIconRequestVector* requests, const String& pageURL) |
161 | { |
162 | database->priv->pendingIconRequests.remove(pageURL); |
163 | delete requests; |
164 | } |
165 | |
166 | static void processPendingIconsForPageURL(WebKitFaviconDatabase* database, const String& pageURL) |
167 | { |
168 | PendingIconRequestVector* pendingIconRequests = database->priv->pendingIconRequests.get(pageURL); |
169 | if (!pendingIconRequests) |
170 | return; |
171 | |
172 | GUniqueOutPtr<GError> error; |
173 | RefPtr<cairo_surface_t> icon = getIconSurfaceSynchronously(database, pageURL, &error.outPtr()); |
174 | |
175 | for (size_t i = 0; i < pendingIconRequests->size(); ++i) { |
176 | GTask* task = pendingIconRequests->at(i).get(); |
177 | if (error) |
178 | g_task_return_error(task, error.release().release()); |
179 | else { |
180 | GetFaviconSurfaceAsyncData* data = static_cast<GetFaviconSurfaceAsyncData*>(g_task_get_task_data(task)); |
181 | data->icon = icon; |
182 | data->shouldReleaseIconForPageURL = false; |
183 | g_task_return_boolean(task, TRUE); |
184 | } |
185 | } |
186 | deletePendingIconRequests(database, pendingIconRequests, pageURL); |
187 | } |
188 | #endif |
189 | |
190 | static void webkitFaviconDatabaseSetIconURLForPageURL(WebKitFaviconDatabase* database, const String& iconURL, const String& pageURL) |
191 | { |
192 | WebKitFaviconDatabasePrivate* priv = database->priv; |
193 | if (!priv->isURLImportCompleted) |
194 | return; |
195 | |
196 | if (pageURL.isEmpty()) |
197 | return; |
198 | |
199 | const String& currentIconURL = priv->pageURLToIconURLMap.get(pageURL); |
200 | if (iconURL == currentIconURL) |
201 | return; |
202 | |
203 | priv->pageURLToIconURLMap.set(pageURL, iconURL); |
204 | if (priv->isSettingIcon) |
205 | return; |
206 | |
207 | g_signal_emit(database, signals[FAVICON_CHANGED], 0, pageURL.utf8().data(), iconURL.utf8().data()); |
208 | } |
209 | |
210 | class WebKitIconDatabaseClient final : public IconDatabaseClient { |
211 | public: |
212 | explicit WebKitIconDatabaseClient(WebKitFaviconDatabase* database) |
213 | : m_database(database) |
214 | { |
215 | } |
216 | |
217 | private: |
218 | void didImportIconURLForPageURL(const String& pageURL) override |
219 | { |
220 | String iconURL = m_database->priv->iconDatabase->synchronousIconURLForPageURL(pageURL); |
221 | webkitFaviconDatabaseSetIconURLForPageURL(m_database, iconURL, pageURL); |
222 | } |
223 | |
224 | void didChangeIconForPageURL(const String& pageURL) override |
225 | { |
226 | if (m_database->priv->isSettingIcon) |
227 | return; |
228 | String iconURL = m_database->priv->iconDatabase->synchronousIconURLForPageURL(pageURL); |
229 | webkitFaviconDatabaseSetIconURLForPageURL(m_database, iconURL, pageURL); |
230 | } |
231 | |
232 | void didImportIconDataForPageURL(const String& pageURL) override |
233 | { |
234 | #if PLATFORM(GTK) |
235 | processPendingIconsForPageURL(m_database, pageURL); |
236 | #endif |
237 | String iconURL = m_database->priv->iconDatabase->synchronousIconURLForPageURL(pageURL); |
238 | webkitFaviconDatabaseSetIconURLForPageURL(m_database, iconURL, pageURL); |
239 | } |
240 | |
241 | void didFinishURLImport() override |
242 | { |
243 | WebKitFaviconDatabasePrivate* priv = m_database->priv; |
244 | |
245 | while (!priv->pendingLoadDecisions.isEmpty()) { |
246 | auto iconURLAndCallback = priv->pendingLoadDecisions.takeLast(); |
247 | auto decision = priv->iconDatabase->synchronousLoadDecisionForIconURL(iconURLAndCallback.first); |
248 | // Decisions should never be unknown after the inital import is complete. |
249 | ASSERT(decision != IconDatabase::IconLoadDecision::Unknown); |
250 | iconURLAndCallback.second(decision == IconDatabase::IconLoadDecision::Yes); |
251 | } |
252 | |
253 | priv->isURLImportCompleted = true; |
254 | } |
255 | |
256 | WebKitFaviconDatabase* m_database; |
257 | }; |
258 | |
259 | WebKitFaviconDatabase* webkitFaviconDatabaseCreate() |
260 | { |
261 | return WEBKIT_FAVICON_DATABASE(g_object_new(WEBKIT_TYPE_FAVICON_DATABASE, nullptr)); |
262 | } |
263 | |
264 | void webkitFaviconDatabaseOpen(WebKitFaviconDatabase* database, const String& path) |
265 | { |
266 | if (webkitFaviconDatabaseIsOpen(database)) |
267 | return; |
268 | |
269 | WebKitFaviconDatabasePrivate* priv = database->priv; |
270 | priv->iconDatabase = std::make_unique<IconDatabase>(); |
271 | priv->iconDatabase->setClient(std::make_unique<WebKitIconDatabaseClient>(database)); |
272 | IconDatabase::delayDatabaseCleanup(); |
273 | priv->iconDatabase->setEnabled(true); |
274 | priv->iconDatabase->setPrivateBrowsingEnabled(WebPreferences::anyPagesAreUsingPrivateBrowsing()); |
275 | |
276 | if (!priv->iconDatabase->open(FileSystem::directoryName(path), FileSystem::pathGetFileName(path))) { |
277 | priv->iconDatabase = nullptr; |
278 | IconDatabase::allowDatabaseCleanup(); |
279 | } |
280 | } |
281 | |
282 | bool webkitFaviconDatabaseIsOpen(WebKitFaviconDatabase* database) |
283 | { |
284 | return database->priv->iconDatabase && database->priv->iconDatabase->isOpen(); |
285 | } |
286 | |
287 | void webkitFaviconDatabaseSetPrivateBrowsingEnabled(WebKitFaviconDatabase* database, bool enabled) |
288 | { |
289 | if (database->priv->iconDatabase) |
290 | database->priv->iconDatabase->setPrivateBrowsingEnabled(enabled); |
291 | } |
292 | |
293 | #if PLATFORM(GTK) |
294 | void webkitFaviconDatabaseGetLoadDecisionForIcon(WebKitFaviconDatabase* database, const LinkIcon& icon, const String& pageURL, Function<void(bool)>&& completionHandler) |
295 | { |
296 | if (!webkitFaviconDatabaseIsOpen(database)) { |
297 | completionHandler(false); |
298 | return; |
299 | } |
300 | |
301 | WebKitFaviconDatabasePrivate* priv = database->priv; |
302 | auto decision = priv->iconDatabase->synchronousLoadDecisionForIconURL(icon.url.string()); |
303 | switch (decision) { |
304 | case IconDatabase::IconLoadDecision::Unknown: |
305 | priv->pendingLoadDecisions.append(std::make_pair(icon.url.string(), WTFMove(completionHandler))); |
306 | priv->iconDatabase->setIconURLForPageURL(icon.url.string(), pageURL); |
307 | break; |
308 | case IconDatabase::IconLoadDecision::No: |
309 | priv->iconDatabase->setIconURLForPageURL(icon.url.string(), pageURL); |
310 | completionHandler(false); |
311 | break; |
312 | case IconDatabase::IconLoadDecision::Yes: |
313 | completionHandler(true); |
314 | break; |
315 | } |
316 | } |
317 | |
318 | void webkitFaviconDatabaseSetIconForPageURL(WebKitFaviconDatabase* database, const LinkIcon& icon, API::Data& iconData, const String& pageURL) |
319 | { |
320 | if (!webkitFaviconDatabaseIsOpen(database)) |
321 | return; |
322 | |
323 | if (pageURL.isEmpty()) |
324 | return; |
325 | |
326 | WebKitFaviconDatabasePrivate* priv = database->priv; |
327 | SetForScope<bool> change(priv->isSettingIcon, true); |
328 | priv->iconDatabase->setIconURLForPageURL(icon.url.string(), pageURL); |
329 | priv->iconDatabase->setIconDataForIconURL(SharedBuffer::create(iconData.bytes(), iconData.size()), icon.url.string()); |
330 | webkitFaviconDatabaseSetIconURLForPageURL(database, icon.url.string(), pageURL); |
331 | g_signal_emit(database, signals[FAVICON_CHANGED], 0, pageURL.utf8().data(), icon.url.string().utf8().data()); |
332 | processPendingIconsForPageURL(database, pageURL); |
333 | } |
334 | |
335 | static PendingIconRequestVector* getOrCreatePendingIconRequests(WebKitFaviconDatabase* database, const String& pageURL) |
336 | { |
337 | PendingIconRequestVector* icons = database->priv->pendingIconRequests.get(pageURL); |
338 | if (!icons) { |
339 | icons = new PendingIconRequestVector; |
340 | database->priv->pendingIconRequests.set(pageURL, icons); |
341 | } |
342 | |
343 | return icons; |
344 | } |
345 | #endif |
346 | |
347 | GQuark webkit_favicon_database_error_quark(void) |
348 | { |
349 | return g_quark_from_static_string("WebKitFaviconDatabaseError" ); |
350 | } |
351 | |
352 | #if PLATFORM(GTK) |
353 | /** |
354 | * webkit_favicon_database_get_favicon: |
355 | * @database: a #WebKitFaviconDatabase |
356 | * @page_uri: URI of the page for which we want to retrieve the favicon |
357 | * @cancellable: (allow-none): A #GCancellable or %NULL. |
358 | * @callback: (scope async): A #GAsyncReadyCallback to call when the request is |
359 | * satisfied or %NULL if you don't care about the result. |
360 | * @user_data: (closure): The data to pass to @callback. |
361 | * |
362 | * Asynchronously obtains a #cairo_surface_t of the favicon for the |
363 | * given page URI. It returns the cached icon if it's in the database |
364 | * asynchronously waiting for the icon to be read from the database. |
365 | * |
366 | * This is an asynchronous method. When the operation is finished, callback will |
367 | * be invoked. You can then call webkit_favicon_database_get_favicon_finish() |
368 | * to get the result of the operation. |
369 | * |
370 | * You must call webkit_web_context_set_favicon_database_directory() for |
371 | * the #WebKitWebContext associated with this #WebKitFaviconDatabase |
372 | * before attempting to use this function; otherwise, |
373 | * webkit_favicon_database_get_favicon_finish() will return |
374 | * %WEBKIT_FAVICON_DATABASE_ERROR_NOT_INITIALIZED. |
375 | */ |
376 | void webkit_favicon_database_get_favicon(WebKitFaviconDatabase* database, const gchar* pageURI, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData) |
377 | { |
378 | g_return_if_fail(WEBKIT_IS_FAVICON_DATABASE(database)); |
379 | g_return_if_fail(pageURI); |
380 | |
381 | if (!webkitFaviconDatabaseIsOpen(database)) { |
382 | g_task_report_new_error(database, callback, userData, 0, |
383 | WEBKIT_FAVICON_DATABASE_ERROR, WEBKIT_FAVICON_DATABASE_ERROR_NOT_INITIALIZED, _("Favicons database not initialized yet" )); |
384 | return; |
385 | } |
386 | |
387 | if (g_str_has_prefix(pageURI, "about:" )) { |
388 | g_task_report_new_error(database, callback, userData, 0, |
389 | WEBKIT_FAVICON_DATABASE_ERROR, WEBKIT_FAVICON_DATABASE_ERROR_FAVICON_NOT_FOUND, _("Page %s does not have a favicon" ), pageURI); |
390 | return; |
391 | } |
392 | |
393 | GRefPtr<GTask> task = adoptGRef(g_task_new(database, cancellable, callback, userData)); |
394 | |
395 | GetFaviconSurfaceAsyncData* data = createGetFaviconSurfaceAsyncData(); |
396 | data->faviconDatabase = database; |
397 | data->pageURL = String::fromUTF8(pageURI); |
398 | g_task_set_task_data(task.get(), data, reinterpret_cast<GDestroyNotify>(destroyGetFaviconSurfaceAsyncData)); |
399 | |
400 | WebKitFaviconDatabasePrivate* priv = database->priv; |
401 | priv->iconDatabase->retainIconForPageURL(data->pageURL); |
402 | |
403 | // We ask for the icon directly. If we don't get the icon data now, |
404 | // we'll be notified later (even if the database is still importing icons). |
405 | GUniqueOutPtr<GError> error; |
406 | data->icon = getIconSurfaceSynchronously(database, data->pageURL, &error.outPtr()); |
407 | if (data->icon) { |
408 | g_task_return_boolean(task.get(), TRUE); |
409 | return; |
410 | } |
411 | |
412 | // At this point we still don't know whether we will get a valid icon for pageURL. |
413 | data->shouldReleaseIconForPageURL = true; |
414 | |
415 | if (g_error_matches(error.get(), WEBKIT_FAVICON_DATABASE_ERROR, WEBKIT_FAVICON_DATABASE_ERROR_FAVICON_NOT_FOUND)) { |
416 | g_task_return_error(task.get(), error.release().release()); |
417 | return; |
418 | } |
419 | |
420 | // If there's not a valid icon, but there's an iconURL registered, |
421 | // or it's still not registered but the import process hasn't |
422 | // finished yet, we need to wait for iconDataReadyForPage to be |
423 | // called before making and informed decision. |
424 | String iconURLForPageURL = priv->iconDatabase->synchronousIconURLForPageURL(data->pageURL); |
425 | if (!iconURLForPageURL.isEmpty() || !priv->isURLImportCompleted) { |
426 | PendingIconRequestVector* iconRequests = getOrCreatePendingIconRequests(database, data->pageURL); |
427 | ASSERT(iconRequests); |
428 | iconRequests->append(task); |
429 | return; |
430 | } |
431 | |
432 | g_task_return_new_error(task.get(), WEBKIT_FAVICON_DATABASE_ERROR, WEBKIT_FAVICON_DATABASE_ERROR_FAVICON_UNKNOWN, |
433 | _("Unknown favicon for page %s" ), pageURI); |
434 | } |
435 | |
436 | /** |
437 | * webkit_favicon_database_get_favicon_finish: |
438 | * @database: a #WebKitFaviconDatabase |
439 | * @result: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to webkit_favicon_database_get_favicon() |
440 | * @error: (allow-none): Return location for error or %NULL. |
441 | * |
442 | * Finishes an operation started with webkit_favicon_database_get_favicon(). |
443 | * |
444 | * Returns: (transfer full): a new reference to a #cairo_surface_t, or |
445 | * %NULL in case of error. |
446 | */ |
447 | cairo_surface_t* webkit_favicon_database_get_favicon_finish(WebKitFaviconDatabase* database, GAsyncResult* result, GError** error) |
448 | { |
449 | g_return_val_if_fail(WEBKIT_IS_FAVICON_DATABASE(database), 0); |
450 | g_return_val_if_fail(g_task_is_valid(result, database), 0); |
451 | |
452 | GTask* task = G_TASK(result); |
453 | if (!g_task_propagate_boolean(task, error)) |
454 | return 0; |
455 | |
456 | GetFaviconSurfaceAsyncData* data = static_cast<GetFaviconSurfaceAsyncData*>(g_task_get_task_data(task)); |
457 | return cairo_surface_reference(data->icon.get()); |
458 | } |
459 | #endif |
460 | |
461 | /** |
462 | * webkit_favicon_database_get_favicon_uri: |
463 | * @database: a #WebKitFaviconDatabase |
464 | * @page_uri: URI of the page containing the icon |
465 | * |
466 | * Obtains the URI of the favicon for the given @page_uri. |
467 | * |
468 | * Returns: a newly allocated URI for the favicon, or %NULL if the |
469 | * database doesn't have a favicon for @page_uri. |
470 | */ |
471 | gchar* webkit_favicon_database_get_favicon_uri(WebKitFaviconDatabase* database, const gchar* pageURL) |
472 | { |
473 | g_return_val_if_fail(WEBKIT_IS_FAVICON_DATABASE(database), nullptr); |
474 | g_return_val_if_fail(pageURL, nullptr); |
475 | ASSERT(RunLoop::isMain()); |
476 | |
477 | if (!webkitFaviconDatabaseIsOpen(database)) |
478 | return nullptr; |
479 | |
480 | String iconURLForPageURL = database->priv->iconDatabase->synchronousIconURLForPageURL(String::fromUTF8(pageURL)); |
481 | if (iconURLForPageURL.isEmpty()) |
482 | return nullptr; |
483 | |
484 | return g_strdup(iconURLForPageURL.utf8().data()); |
485 | } |
486 | |
487 | /** |
488 | * webkit_favicon_database_clear: |
489 | * @database: a #WebKitFaviconDatabase |
490 | * |
491 | * Clears all icons from the database. |
492 | */ |
493 | void webkit_favicon_database_clear(WebKitFaviconDatabase* database) |
494 | { |
495 | g_return_if_fail(WEBKIT_IS_FAVICON_DATABASE(database)); |
496 | |
497 | if (!webkitFaviconDatabaseIsOpen(database)) |
498 | return; |
499 | |
500 | database->priv->iconDatabase->removeAllIcons(); |
501 | } |
502 | |