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
22#include "WebKitTestServer.h"
23#include "WebViewTest.h"
24#include <glib/gstdio.h>
25#include <libsoup/soup.h>
26#include <wtf/glib/GUniquePtr.h>
27
28#if PLATFORM(GTK)
29static WebKitTestServer* kServer;
30#endif
31
32class FaviconDatabaseTest: public WebViewTest {
33public:
34 MAKE_GLIB_TEST_FIXTURE(FaviconDatabaseTest);
35
36 FaviconDatabaseTest()
37 {
38#if PLATFORM(GTK)
39 WebKitFaviconDatabase* database = webkit_web_context_get_favicon_database(m_webContext.get());
40 g_signal_connect(database, "favicon-changed", G_CALLBACK(faviconChangedCallback), this);
41#endif
42 }
43
44 ~FaviconDatabaseTest()
45 {
46#if PLATFORM(GTK)
47 if (m_favicon)
48 cairo_surface_destroy(m_favicon);
49
50 WebKitFaviconDatabase* database = webkit_web_context_get_favicon_database(m_webContext.get());
51 g_signal_handlers_disconnect_matched(database, G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, this);
52#endif
53 }
54
55#if PLATFORM(GTK)
56 static void faviconChangedCallback(WebKitFaviconDatabase* database, const char* pageURI, const char* faviconURI, FaviconDatabaseTest* test)
57 {
58 if (!g_strcmp0(webkit_web_view_get_uri(test->m_webView), pageURI)) {
59 test->m_faviconURI = faviconURI;
60 if (test->m_waitingForFaviconURI)
61 test->quitMainLoop();
62 }
63 }
64
65 static void viewFaviconChangedCallback(WebKitWebView* webView, GParamSpec* pspec, gpointer data)
66 {
67 FaviconDatabaseTest* test = static_cast<FaviconDatabaseTest*>(data);
68 g_assert_true(test->m_webView == webView);
69 test->m_faviconNotificationReceived = true;
70 test->quitMainLoop();
71 }
72
73 static void getFaviconCallback(GObject* sourceObject, GAsyncResult* result, void* data)
74 {
75 FaviconDatabaseTest* test = static_cast<FaviconDatabaseTest*>(data);
76 WebKitFaviconDatabase* database = webkit_web_context_get_favicon_database(test->m_webContext.get());
77 test->m_favicon = webkit_favicon_database_get_favicon_finish(database, result, &test->m_error.outPtr());
78 test->quitMainLoop();
79 }
80
81 void waitUntilFaviconChanged()
82 {
83 m_faviconNotificationReceived = false;
84 unsigned long handlerID = g_signal_connect(m_webView, "notify::favicon", G_CALLBACK(viewFaviconChangedCallback), this);
85 g_main_loop_run(m_mainLoop);
86 g_signal_handler_disconnect(m_webView, handlerID);
87 }
88
89 void getFaviconForPageURIAndWaitUntilReady(const char* pageURI)
90 {
91 if (m_favicon) {
92 cairo_surface_destroy(m_favicon);
93 m_favicon = 0;
94 }
95
96 WebKitFaviconDatabase* database = webkit_web_context_get_favicon_database(m_webContext.get());
97 webkit_favicon_database_get_favicon(database, pageURI, 0, getFaviconCallback, this);
98
99 g_main_loop_run(m_mainLoop);
100 }
101
102 void waitUntilFaviconURIChanged()
103 {
104 g_assert_false(m_waitingForFaviconURI);
105 m_faviconURI = CString();
106 m_waitingForFaviconURI = true;
107 g_main_loop_run(m_mainLoop);
108 m_waitingForFaviconURI = false;
109 }
110
111 cairo_surface_t* m_favicon { nullptr };
112
113 CString m_faviconURI;
114 GUniqueOutPtr<GError> m_error;
115 bool m_faviconNotificationReceived { false };
116 bool m_waitingForFaviconURI { false };
117#endif
118};
119
120#if PLATFORM(GTK)
121static void
122serverCallback(SoupServer* server, SoupMessage* message, const char* path, GHashTable* query, SoupClientContext* context, void* data)
123{
124 if (message->method != SOUP_METHOD_GET) {
125 soup_message_set_status(message, SOUP_STATUS_NOT_IMPLEMENTED);
126 return;
127 }
128
129 if (g_str_equal(path, "/favicon.ico")) {
130 soup_message_set_status(message, SOUP_STATUS_NOT_FOUND);
131 soup_message_body_complete(message->response_body);
132 return;
133 }
134
135 char* contents;
136 gsize length;
137 if (g_str_equal(path, "/icon/favicon.ico")) {
138 GUniquePtr<char> pathToFavicon(g_build_filename(Test::getResourcesDir().data(), "blank.ico", nullptr));
139 g_file_get_contents(pathToFavicon.get(), &contents, &length, 0);
140 soup_message_body_append(message->response_body, SOUP_MEMORY_TAKE, contents, length);
141 } else if (g_str_equal(path, "/nofavicon")) {
142 static const char* noFaviconHTML = "<html><head><body>test</body></html>";
143 soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, noFaviconHTML, strlen(noFaviconHTML));
144 } else {
145 static const char* contentsHTML = "<html><head><link rel='icon' href='/icon/favicon.ico' type='image/x-ico; charset=binary'></head><body>test</body></html>";
146 soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, contentsHTML, strlen(contentsHTML));
147 }
148
149 soup_message_set_status(message, SOUP_STATUS_OK);
150 soup_message_body_complete(message->response_body);
151}
152
153static void testNotInitialized(FaviconDatabaseTest* test)
154{
155 // Try to retrieve a valid favicon from a not initialized database.
156 test->getFaviconForPageURIAndWaitUntilReady(kServer->getURIForPath("/foo").data());
157 g_assert_null(test->m_favicon);
158 g_assert_nonnull(test->m_error);
159 g_assert_cmpint(test->m_error->code, ==, WEBKIT_FAVICON_DATABASE_ERROR_NOT_INITIALIZED);
160}
161#endif
162
163static void testSetDirectory(FaviconDatabaseTest* test)
164{
165 webkit_web_context_set_favicon_database_directory(test->m_webContext.get(), Test::dataDirectory());
166 g_assert_cmpstr(Test::dataDirectory(), ==, webkit_web_context_get_favicon_database_directory(test->m_webContext.get()));
167}
168
169#if PLATFORM(GTK)
170static void testClearDatabase(FaviconDatabaseTest* test)
171{
172 WebKitFaviconDatabase* database = webkit_web_context_get_favicon_database(test->m_webContext.get());
173 webkit_favicon_database_clear(database);
174
175 GUniquePtr<char> iconURI(webkit_favicon_database_get_favicon_uri(database, kServer->getURIForPath("/foo").data()));
176 g_assert_null(iconURI);
177}
178
179static void ephemeralViewLoadChanged(WebKitWebView* webView, WebKitLoadEvent loadEvent, WebViewTest* test)
180{
181 if (loadEvent != WEBKIT_LOAD_FINISHED)
182 return;
183 g_signal_handlers_disconnect_by_func(webView, reinterpret_cast<void*>(ephemeralViewLoadChanged), test);
184 test->quitMainLoop();
185}
186
187static void testPrivateBrowsing(FaviconDatabaseTest* test)
188{
189 auto webView = Test::adoptView(g_object_new(WEBKIT_TYPE_WEB_VIEW,
190 "web-context", test->m_webContext.get(),
191 "is-ephemeral", TRUE,
192 nullptr));
193 g_signal_connect(webView.get(), "load-changed", G_CALLBACK(ephemeralViewLoadChanged), test);
194 webkit_web_view_load_uri(webView.get(), kServer->getURIForPath("/foo").data());
195 g_main_loop_run(test->m_mainLoop);
196
197 // An ephemeral web view should not write to the database.
198 test->getFaviconForPageURIAndWaitUntilReady(kServer->getURIForPath("/foo").data());
199 g_assert_null(test->m_favicon);
200 g_assert_nonnull(test->m_error);
201}
202
203static void testGetFavicon(FaviconDatabaseTest* test)
204{
205 // We need to load the page first to ensure the icon data will be
206 // in the database in case there's an associated favicon.
207 test->loadURI(kServer->getURIForPath("/foo").data());
208 test->waitUntilFaviconChanged();
209 CString faviconURI = kServer->getURIForPath("/icon/favicon.ico");
210
211 // Check the API retrieving a valid favicon.
212 test->getFaviconForPageURIAndWaitUntilReady(kServer->getURIForPath("/foo").data());
213 g_assert_nonnull(test->m_favicon);
214 g_assert_cmpstr(test->m_faviconURI.data(), ==, faviconURI.data());
215 g_assert_no_error(test->m_error.get());
216
217 // Check that width and height match those from blank.ico (16x16 favicon).
218 g_assert_cmpint(cairo_image_surface_get_width(test->m_favicon), ==, 16);
219 g_assert_cmpint(cairo_image_surface_get_height(test->m_favicon), ==, 16);
220
221 // Check that another page with the same favicon return the same icon.
222 cairo_surface_t* favicon = cairo_surface_reference(test->m_favicon);
223 test->loadURI(kServer->getURIForPath("/bar").data());
224 // It's a new page in the database, so favicon will change twice, first to reset it
225 // and then when the icon is loaded.
226 test->waitUntilFaviconChanged();
227 test->waitUntilFaviconChanged();
228 test->getFaviconForPageURIAndWaitUntilReady(kServer->getURIForPath("/bar").data());
229 g_assert_nonnull(test->m_favicon);
230 g_assert_cmpstr(test->m_faviconURI.data(), ==, faviconURI.data());
231 g_assert_true(test->m_favicon == favicon);
232 g_assert_no_error(test->m_error.get());
233 cairo_surface_destroy(favicon);
234
235 // Check the API retrieving an invalid favicon. Favicon changes only once to reset it, then
236 // the database is updated with the favicon URI, but not with favicon image data.
237 test->loadURI(kServer->getURIForPath("/nofavicon").data());
238 test->waitUntilFaviconChanged();
239 test->waitUntilFaviconURIChanged();
240
241 test->getFaviconForPageURIAndWaitUntilReady(kServer->getURIForPath("/nofavicon").data());
242 g_assert_null(test->m_favicon);
243 g_assert_nonnull(test->m_error);
244}
245
246static void testGetFaviconURI(FaviconDatabaseTest* test)
247{
248 WebKitFaviconDatabase* database = webkit_web_context_get_favicon_database(test->m_webContext.get());
249
250 CString baseURI = kServer->getURIForPath("/foo");
251 GUniquePtr<char> iconURI(webkit_favicon_database_get_favicon_uri(database, baseURI.data()));
252 ASSERT_CMP_CSTRING(iconURI.get(), ==, kServer->getURIForPath("/icon/favicon.ico"));
253}
254
255static void testWebViewFavicon(FaviconDatabaseTest* test)
256{
257 test->m_faviconURI = CString();
258
259 cairo_surface_t* iconFromWebView = webkit_web_view_get_favicon(test->m_webView);
260 g_assert_null(iconFromWebView);
261
262 test->loadURI(kServer->getURIForPath("/foo").data());
263 test->waitUntilFaviconChanged();
264 g_assert_true(test->m_faviconNotificationReceived);
265 // The icon is known and hasn't changed in the database, so notify::favicon is emitted
266 // but WebKitFaviconDatabase::icon-changed isn't.
267 g_assert_true(test->m_faviconURI.isNull());
268
269 iconFromWebView = webkit_web_view_get_favicon(test->m_webView);
270 g_assert_nonnull(iconFromWebView);
271 g_assert_cmpuint(cairo_image_surface_get_width(iconFromWebView), ==, 16);
272 g_assert_cmpuint(cairo_image_surface_get_height(iconFromWebView), ==, 16);
273}
274#endif
275
276static void testFaviconDatabase(FaviconDatabaseTest* test, gconstpointer)
277{
278 // These tests depend on this order to run properly so we declare them in a single one.
279 // See https://bugs.webkit.org/show_bug.cgi?id=111434.
280#if PLATFORM(GTK)
281 testNotInitialized(test);
282#endif
283
284 testSetDirectory(test);
285
286#if PLATFORM(GTK)
287 testPrivateBrowsing(test);
288 testGetFavicon(test);
289 testWebViewFavicon(test);
290 testGetFaviconURI(test);
291 testClearDatabase(test);
292#endif
293}
294
295void beforeAll()
296{
297#if PLATFORM(GTK)
298 // Start a soup server for testing.
299 kServer = new WebKitTestServer();
300 kServer->run(serverCallback);
301#endif
302
303 // Add tests to the suite.
304 FaviconDatabaseTest::add("WebKitFaviconDatabase", "favicon-database-test", testFaviconDatabase);
305}
306
307void afterAll()
308{
309#if PLATFORM(GTK)
310 delete kServer;
311#endif
312}
313