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 Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 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 <wtf/Vector.h>
25#include <wtf/glib/GMutexLocker.h>
26#include <wtf/glib/GRefPtr.h>
27
28static WebKitTestServer* kServer;
29
30static const char* kIndexHtml =
31 "<html><head>"
32 " <link rel='stylesheet' href='/style.css' type='text/css'>"
33 " <script language='javascript' src='/javascript.js'></script>"
34 "</head><body>WebKitGTK+ resources test</body></html>";
35
36static const char* kStyleCSS =
37 "body {"
38 " margin: 0px;"
39 " padding: 0px;"
40 " font-family: sans-serif;"
41 " background: url(/blank.ico) 0 0 no-repeat;"
42 " color: black;"
43 "}";
44
45static const char* kJavascript = "function foo () { var a = 1; }";
46
47class ResourcesTest: public WebViewTest {
48public:
49 MAKE_GLIB_TEST_FIXTURE(ResourcesTest);
50
51 static void resourceSentRequestCallback(WebKitWebResource* resource, WebKitURIRequest* request, WebKitURIResponse* redirectResponse, ResourcesTest* test)
52 {
53 test->assertObjectIsDeletedWhenTestFinishes(G_OBJECT(request));
54 if (redirectResponse)
55 test->assertObjectIsDeletedWhenTestFinishes(G_OBJECT(redirectResponse));
56 test->resourceSentRequest(resource, request, redirectResponse);
57 }
58
59 static void resourceReceivedResponseCallback(WebKitWebResource* resource, GParamSpec*, ResourcesTest* test)
60 {
61 g_assert_nonnull(webkit_web_resource_get_response(resource));
62 test->resourceReceivedResponse(resource);
63 }
64
65 static void resourceReceivedDataCallback(WebKitWebResource* resource, guint64 bytesReceived, ResourcesTest* test)
66 {
67 test->resourceReceivedData(resource, bytesReceived);
68 }
69
70 static void resourceFinishedCallback(WebKitWebResource* resource, ResourcesTest* test)
71 {
72 test->resourceFinished(resource);
73 }
74
75 static void resourceFailedCallback(WebKitWebResource* resource, GError* error, ResourcesTest* test)
76 {
77 g_assert_nonnull(error);
78 test->resourceFailed(resource, error);
79 }
80
81 static void resourceLoadStartedCallback(WebKitWebView* webView, WebKitWebResource* resource, WebKitURIRequest* request, ResourcesTest* test)
82 {
83 test->assertObjectIsDeletedWhenTestFinishes(G_OBJECT(resource));
84 test->assertObjectIsDeletedWhenTestFinishes(G_OBJECT(request));
85
86 // Ignore favicons.
87 if (g_str_has_suffix(webkit_uri_request_get_uri(request), "favicon.ico"))
88 return;
89
90 test->resourceLoadStarted(resource, request);
91 g_signal_connect(resource, "sent-request", G_CALLBACK(resourceSentRequestCallback), test);
92 g_signal_connect(resource, "notify::response", G_CALLBACK(resourceReceivedResponseCallback), test);
93 g_signal_connect(resource, "received-data", G_CALLBACK(resourceReceivedDataCallback), test);
94 g_signal_connect(resource, "finished", G_CALLBACK(resourceFinishedCallback), test);
95 g_signal_connect(resource, "failed", G_CALLBACK(resourceFailedCallback), test);
96 }
97
98 void clearSubresources()
99 {
100 g_list_free_full(m_subresources, reinterpret_cast<GDestroyNotify>(g_object_unref));
101 m_subresources = 0;
102 }
103
104 ResourcesTest()
105 : WebViewTest()
106 , m_resourcesLoaded(0)
107 , m_resourcesToLoad(0)
108 , m_resourceDataSize(0)
109 , m_subresources(0)
110 {
111 g_signal_connect(m_webView, "resource-load-started", G_CALLBACK(resourceLoadStartedCallback), this);
112 }
113
114 ~ResourcesTest()
115 {
116 clearSubresources();
117 }
118
119 virtual void resourceLoadStarted(WebKitWebResource* resource, WebKitURIRequest* request)
120 {
121 }
122
123 virtual void resourceSentRequest(WebKitWebResource* resource, WebKitURIRequest* request, WebKitURIResponse* redirectResponse)
124 {
125 }
126
127 virtual void resourceReceivedResponse(WebKitWebResource* resource)
128 {
129 }
130
131 virtual void resourceReceivedData(WebKitWebResource* resource, guint64 bytesReceived)
132 {
133 }
134
135 virtual void resourceFinished(WebKitWebResource* resource)
136 {
137 g_signal_handlers_disconnect_matched(resource, G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, this);
138 if (webkit_web_view_get_main_resource(m_webView) != resource)
139 m_subresources = g_list_prepend(m_subresources, g_object_ref(resource));
140 if (++m_resourcesLoaded == m_resourcesToLoad)
141 g_main_loop_quit(m_mainLoop);
142 }
143
144 virtual void resourceFailed(WebKitWebResource* resource, GError* error)
145 {
146 g_assert_not_reached();
147 }
148
149 void waitUntilResourcesLoaded(size_t resourcesCount)
150 {
151 m_resourcesLoaded = 0;
152 m_resourcesToLoad = resourcesCount;
153 clearSubresources();
154 g_main_loop_run(m_mainLoop);
155 }
156
157 GList* subresources()
158 {
159 return m_subresources;
160 }
161
162 static void resourceGetDataCallback(GObject* object, GAsyncResult* result, gpointer userData)
163 {
164 size_t dataSize;
165 GUniqueOutPtr<GError> error;
166 unsigned char* data = webkit_web_resource_get_data_finish(WEBKIT_WEB_RESOURCE(object), result, &dataSize, &error.outPtr());
167 g_assert_no_error(error.get());
168 g_assert_nonnull(data);
169 g_assert_cmpint(dataSize, >, 0);
170
171 ResourcesTest* test = static_cast<ResourcesTest*>(userData);
172 test->m_resourceData.reset(reinterpret_cast<char*>(data));
173 test->m_resourceDataSize = dataSize;
174 g_main_loop_quit(test->m_mainLoop);
175 }
176
177 void checkResourceData(WebKitWebResource* resource)
178 {
179 m_resourceDataSize = 0;
180 webkit_web_resource_get_data(resource, 0, resourceGetDataCallback, this);
181 g_main_loop_run(m_mainLoop);
182
183 const char* uri = webkit_web_resource_get_uri(resource);
184 if (uri == kServer->getURIForPath("/")) {
185 g_assert_cmpint(m_resourceDataSize, ==, strlen(kIndexHtml));
186 g_assert_cmpint(strncmp(m_resourceData.get(), kIndexHtml, m_resourceDataSize), ==, 0);
187 } else if (uri == kServer->getURIForPath("/style.css")) {
188 g_assert_cmpint(m_resourceDataSize, ==, strlen(kStyleCSS));
189 g_assert_cmpint(strncmp(m_resourceData.get(), kStyleCSS, m_resourceDataSize), ==, 0);
190 } else if (uri == kServer->getURIForPath("/javascript.js")) {
191 g_assert_cmpint(m_resourceDataSize, ==, strlen(kJavascript));
192 g_assert_cmpint(strncmp(m_resourceData.get(), kJavascript, m_resourceDataSize), ==, 0);
193 } else if (uri == kServer->getURIForPath("/blank.ico")) {
194 GUniquePtr<char> filePath(g_build_filename(Test::getResourcesDir().data(), "blank.ico", nullptr));
195 GUniqueOutPtr<char> contents;
196 gsize contentsLength;
197 g_file_get_contents(filePath.get(), &contents.outPtr(), &contentsLength, nullptr);
198 g_assert_cmpint(m_resourceDataSize, ==, contentsLength);
199 g_assert_cmpmem(m_resourceData.get(), contentsLength, contents.get(), contentsLength);
200 } else
201 g_assert_not_reached();
202 m_resourceData.reset();
203 }
204
205 size_t m_resourcesLoaded;
206 size_t m_resourcesToLoad;
207 GUniquePtr<char> m_resourceData;
208 size_t m_resourceDataSize;
209 GList* m_subresources;
210};
211
212static void testWebViewResources(ResourcesTest* test, gconstpointer)
213{
214 // Nothing loaded yet, there shoulnd't be resources.
215 g_assert_null(webkit_web_view_get_main_resource(test->m_webView));
216 g_assert_null(test->subresources());
217
218 // Load simple page without subresources.
219 test->loadHtml("<html><body>Testing WebKitGTK+</body></html>", 0);
220 test->waitUntilLoadFinished();
221 WebKitWebResource* resource = webkit_web_view_get_main_resource(test->m_webView);
222 g_assert_nonnull(resource);
223 g_assert_cmpstr(webkit_web_view_get_uri(test->m_webView), ==, webkit_web_resource_get_uri(resource));
224 g_assert_null(test->subresources());
225
226 // Load simple page with subresources.
227 test->loadURI(kServer->getURIForPath("/").data());
228 test->waitUntilResourcesLoaded(4);
229
230 resource = webkit_web_view_get_main_resource(test->m_webView);
231 g_assert_nonnull(resource);
232 g_assert_cmpstr(webkit_web_view_get_uri(test->m_webView), ==, webkit_web_resource_get_uri(resource));
233 GList* subresources = test->subresources();
234 g_assert_nonnull(subresources);
235 g_assert_cmpint(g_list_length(subresources), ==, 3);
236
237#if 0
238 // Load the same URI again.
239 // FIXME: we need a workaround for bug https://bugs.webkit.org/show_bug.cgi?id=78510.
240 test->loadURI(kServer->getURIForPath("/").data());
241 test->waitUntilResourcesLoaded(4);
242#endif
243
244 // Reload.
245 webkit_web_view_reload_bypass_cache(test->m_webView);
246 test->waitUntilResourcesLoaded(4);
247}
248
249class SingleResourceLoadTest: public ResourcesTest {
250public:
251 MAKE_GLIB_TEST_FIXTURE(SingleResourceLoadTest);
252
253 enum LoadEvents {
254 Started,
255 SentRequest,
256 Redirected,
257 ReceivedResponse,
258 ReceivedData,
259 Finished,
260 Failed
261 };
262
263 SingleResourceLoadTest()
264 : ResourcesTest()
265 , m_resourceDataReceived(0)
266 {
267 m_resourcesToLoad = 2;
268 }
269
270 void resourceLoadStarted(WebKitWebResource* resource, WebKitURIRequest* request)
271 {
272 if (resource == webkit_web_view_get_main_resource(m_webView))
273 return;
274
275 m_resourceDataReceived = 0;
276 m_resource = resource;
277 m_loadEvents.append(Started);
278 }
279
280 void resourceSentRequest(WebKitWebResource* resource, WebKitURIRequest* request, WebKitURIResponse* redirectResponse)
281 {
282 if (resource != m_resource)
283 return;
284
285 if (redirectResponse)
286 m_loadEvents.append(Redirected);
287 else
288 m_loadEvents.append(SentRequest);
289 }
290
291 void resourceReceivedResponse(WebKitWebResource* resource)
292 {
293 if (resource != m_resource)
294 return;
295
296 m_loadEvents.append(ReceivedResponse);
297 }
298
299 void resourceReceivedData(WebKitWebResource* resource, guint64 bytesReceived)
300 {
301 if (resource != m_resource)
302 return;
303
304 m_resourceDataReceived += bytesReceived;
305 if (!m_loadEvents.contains(ReceivedData))
306 m_loadEvents.append(ReceivedData);
307 }
308
309 void resourceFinished(WebKitWebResource* resource)
310 {
311 if (resource != m_resource) {
312 ResourcesTest::resourceFinished(resource);
313 return;
314 }
315
316 if (!m_loadEvents.contains(Failed)) {
317 WebKitURIResponse* response = webkit_web_resource_get_response(m_resource.get());
318 g_assert_nonnull(response);
319 g_assert_cmpint(webkit_uri_response_get_content_length(response), ==, m_resourceDataReceived);
320 }
321 m_loadEvents.append(Finished);
322 ResourcesTest::resourceFinished(resource);
323 }
324
325 void resourceFailed(WebKitWebResource* resource, GError* error)
326 {
327 if (resource == m_resource)
328 m_loadEvents.append(Failed);
329 }
330
331 void waitUntilResourceLoadFinished()
332 {
333 m_resource = 0;
334 m_resourcesLoaded = 0;
335 g_main_loop_run(m_mainLoop);
336 }
337
338 WebKitURIResponse* waitUntilResourceLoadFinishedAndReturnURIResponse()
339 {
340 waitUntilResourceLoadFinished();
341 g_assert_nonnull(m_resource);
342 return webkit_web_resource_get_response(m_resource.get());
343 }
344
345 GRefPtr<WebKitWebResource> m_resource;
346 Vector<LoadEvents> m_loadEvents;
347 guint64 m_resourceDataReceived;
348};
349
350static void testWebResourceLoading(SingleResourceLoadTest* test, gconstpointer)
351{
352 test->loadURI(kServer->getURIForPath("/javascript.html").data());
353 test->waitUntilResourceLoadFinished();
354 g_assert_nonnull(test->m_resource);
355 Vector<SingleResourceLoadTest::LoadEvents>& events = test->m_loadEvents;
356 g_assert_cmpint(events.size(), ==, 5);
357 g_assert_cmpint(events[0], ==, SingleResourceLoadTest::Started);
358 g_assert_cmpint(events[1], ==, SingleResourceLoadTest::SentRequest);
359 g_assert_cmpint(events[2], ==, SingleResourceLoadTest::ReceivedResponse);
360 g_assert_cmpint(events[3], ==, SingleResourceLoadTest::ReceivedData);
361 g_assert_cmpint(events[4], ==, SingleResourceLoadTest::Finished);
362 events.clear();
363
364 test->loadURI(kServer->getURIForPath("/redirected-css.html").data());
365 test->waitUntilResourceLoadFinished();
366 g_assert_nonnull(test->m_resource);
367 g_assert_cmpint(events.size(), ==, 6);
368 g_assert_cmpint(events[0], ==, SingleResourceLoadTest::Started);
369 g_assert_cmpint(events[1], ==, SingleResourceLoadTest::SentRequest);
370 g_assert_cmpint(events[2], ==, SingleResourceLoadTest::Redirected);
371 g_assert_cmpint(events[3], ==, SingleResourceLoadTest::ReceivedResponse);
372 g_assert_cmpint(events[4], ==, SingleResourceLoadTest::ReceivedData);
373 g_assert_cmpint(events[5], ==, SingleResourceLoadTest::Finished);
374 events.clear();
375
376 test->loadURI(kServer->getURIForPath("/invalid-css.html").data());
377 test->waitUntilResourceLoadFinished();
378 g_assert_nonnull(test->m_resource);
379 g_assert_cmpint(events.size(), ==, 4);
380 g_assert_cmpint(events[0], ==, SingleResourceLoadTest::Started);
381 g_assert_cmpint(events[1], ==, SingleResourceLoadTest::SentRequest);
382 g_assert_cmpint(events[2], ==, SingleResourceLoadTest::Failed);
383 g_assert_cmpint(events[3], ==, SingleResourceLoadTest::Finished);
384 events.clear();
385}
386
387static void testWebResourceResponse(SingleResourceLoadTest* test, gconstpointer)
388{
389 // No cached resource: First load.
390 test->loadURI(kServer->getURIForPath("/javascript.html").data());
391 WebKitURIResponse* response = test->waitUntilResourceLoadFinishedAndReturnURIResponse();
392 g_assert_cmpint(webkit_uri_response_get_status_code(response), ==, SOUP_STATUS_OK);
393
394 // No cached resource: Second load.
395 test->loadURI(kServer->getURIForPath("/javascript.html").data());
396 response = test->waitUntilResourceLoadFinishedAndReturnURIResponse();
397 g_assert_cmpint(webkit_uri_response_get_status_code(response), ==, SOUP_STATUS_OK);
398
399 // No cached resource: Reload.
400 webkit_web_view_reload(test->m_webView);
401 response = test->waitUntilResourceLoadFinishedAndReturnURIResponse();
402 g_assert_cmpint(webkit_uri_response_get_status_code(response), ==, SOUP_STATUS_OK);
403
404 // Cached resource: First load.
405 test->loadURI(kServer->getURIForPath("/image.html").data());
406 response = test->waitUntilResourceLoadFinishedAndReturnURIResponse();
407 g_assert_cmpint(webkit_uri_response_get_status_code(response), ==, SOUP_STATUS_OK);
408
409 // Cached resource: Second load.
410 test->loadURI(kServer->getURIForPath("/image.html").data());
411 response = test->waitUntilResourceLoadFinishedAndReturnURIResponse();
412 g_assert_cmpint(webkit_uri_response_get_status_code(response), ==, SOUP_STATUS_OK);
413
414 // Cached resource: Reload.
415 webkit_web_view_reload(test->m_webView);
416 response = test->waitUntilResourceLoadFinishedAndReturnURIResponse();
417 g_assert_cmpint(webkit_uri_response_get_status_code(response), ==, SOUP_STATUS_NOT_MODIFIED);
418}
419
420static void testWebResourceMimeType(SingleResourceLoadTest* test, gconstpointer)
421{
422 test->loadURI(kServer->getURIForPath("/javascript.html").data());
423 WebKitURIResponse* response = test->waitUntilResourceLoadFinishedAndReturnURIResponse();
424 g_assert_cmpstr(webkit_uri_response_get_mime_type(response), ==, "text/javascript");
425
426 test->loadURI(kServer->getURIForPath("/image.html").data());
427 response = test->waitUntilResourceLoadFinishedAndReturnURIResponse();
428 g_assert_cmpstr(webkit_uri_response_get_mime_type(response), ==, "image/x-icon");
429
430 test->loadURI(kServer->getURIForPath("/redirected-css.html").data());
431 response = test->waitUntilResourceLoadFinishedAndReturnURIResponse();
432 g_assert_cmpstr(webkit_uri_response_get_mime_type(response), ==, "text/css");
433}
434
435static void testWebResourceSuggestedFilename(SingleResourceLoadTest* test, gconstpointer)
436{
437 test->loadURI(kServer->getURIForPath("/javascript.html").data());
438 WebKitURIResponse* response = test->waitUntilResourceLoadFinishedAndReturnURIResponse();
439 g_assert_cmpstr(webkit_uri_response_get_suggested_filename(response), ==, "JavaScript.js");
440
441 test->loadURI(kServer->getURIForPath("/image.html").data());
442 response = test->waitUntilResourceLoadFinishedAndReturnURIResponse();
443 g_assert_null(webkit_uri_response_get_suggested_filename(response));
444}
445
446class ResourceURITrackingTest: public SingleResourceLoadTest {
447public:
448 MAKE_GLIB_TEST_FIXTURE(ResourceURITrackingTest);
449
450 ResourceURITrackingTest()
451 : SingleResourceLoadTest()
452 {
453 }
454
455 static void uriChanged(WebKitWebResource* resource, GParamSpec*, ResourceURITrackingTest* test)
456 {
457 g_assert_true(resource == test->m_resource.get());
458 g_assert_cmpstr(test->m_activeURI.data(), !=, webkit_web_resource_get_uri(test->m_resource.get()));
459 test->m_activeURI = webkit_web_resource_get_uri(test->m_resource.get());
460 }
461
462 void resourceLoadStarted(WebKitWebResource* resource, WebKitURIRequest* request)
463 {
464 if (resource == webkit_web_view_get_main_resource(m_webView))
465 return;
466
467 m_resource = resource;
468 m_activeURI = webkit_web_resource_get_uri(resource);
469 checkActiveURI("/redirected.css");
470 g_assert_cmpstr(m_activeURI.data(), ==, webkit_uri_request_get_uri(request));
471 g_signal_connect(resource, "notify::uri", G_CALLBACK(uriChanged), this);
472 }
473
474 void resourceSentRequest(WebKitWebResource* resource, WebKitURIRequest* request, WebKitURIResponse* redirectResponse)
475 {
476 if (resource != m_resource)
477 return;
478
479 if (redirectResponse)
480 checkActiveURI("/simple-style.css");
481 else
482 checkActiveURI("/redirected.css");
483 g_assert_cmpstr(m_activeURI.data(), ==, webkit_uri_request_get_uri(request));
484 }
485
486 void resourceReceivedResponse(WebKitWebResource* resource)
487 {
488 if (resource != m_resource)
489 return;
490
491 checkActiveURI("/simple-style.css");
492 }
493
494 void resourceReceivedData(WebKitWebResource* resource, guint64 bytesReceived)
495 {
496 }
497
498 void resourceFinished(WebKitWebResource* resource)
499 {
500 if (resource == m_resource)
501 checkActiveURI("/simple-style.css");
502 ResourcesTest::resourceFinished(resource);
503 }
504
505 void resourceFailed(WebKitWebResource*, GError*)
506 {
507 g_assert_not_reached();
508 }
509
510 CString m_activeURI;
511
512private:
513 void checkActiveURI(const char* uri)
514 {
515 ASSERT_CMP_CSTRING(m_activeURI, ==, kServer->getURIForPath(uri));
516 }
517};
518
519static void testWebResourceActiveURI(ResourceURITrackingTest* test, gconstpointer)
520{
521 test->loadURI(kServer->getURIForPath("/redirected-css.html").data());
522 test->waitUntilResourceLoadFinished();
523}
524
525static void testWebResourceGetData(ResourcesTest* test, gconstpointer)
526{
527 test->loadURI(kServer->getURIForPath("/").data());
528 test->waitUntilResourcesLoaded(4);
529
530 WebKitWebResource* resource = webkit_web_view_get_main_resource(test->m_webView);
531 g_assert_nonnull(resource);
532 test->checkResourceData(resource);
533
534 GList* subresources = test->subresources();
535 for (GList* item = subresources; item; item = g_list_next(item))
536 test->checkResourceData(WEBKIT_WEB_RESOURCE(item->data));
537}
538
539static void webViewLoadChanged(WebKitWebView* webView, WebKitLoadEvent loadEvent, GMainLoop* mainLoop)
540{
541 if (loadEvent != WEBKIT_LOAD_FINISHED)
542 return;
543 g_signal_handlers_disconnect_by_func(webView, reinterpret_cast<void*>(webViewLoadChanged), mainLoop);
544 g_main_loop_quit(mainLoop);
545}
546
547static void testWebResourceGetDataError(Test* test, gconstpointer)
548{
549 GRefPtr<GMainLoop> mainLoop = adoptGRef(g_main_loop_new(nullptr, FALSE));
550 GRefPtr<WebKitWebView> webView = WEBKIT_WEB_VIEW(Test::createWebView(test->m_webContext.get()));
551 webkit_web_view_load_html(webView.get(), "<html></html>", nullptr);
552 g_signal_connect(webView.get(), "load-changed", G_CALLBACK(webViewLoadChanged), mainLoop.get());
553 g_main_loop_run(mainLoop.get());
554
555 auto* resource = webkit_web_view_get_main_resource(webView.get());
556 test->assertObjectIsDeletedWhenTestFinishes(G_OBJECT(resource));
557 webkit_web_resource_get_data(resource, nullptr, [](GObject* source, GAsyncResult* result, gpointer userData) {
558 size_t dataSize;
559 GUniqueOutPtr<GError> error;
560 auto* data = webkit_web_resource_get_data_finish(WEBKIT_WEB_RESOURCE(source), result, &dataSize, &error.outPtr());
561 g_assert_null(data);
562 g_assert_error(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED);
563 g_main_loop_quit(static_cast<GMainLoop*>(userData));
564 }, mainLoop.get());
565 webView = nullptr;
566 g_main_loop_run(mainLoop.get());
567}
568
569static void testWebResourceGetDataEmpty(Test* test, gconstpointer)
570{
571 GRefPtr<GMainLoop> mainLoop = adoptGRef(g_main_loop_new(nullptr, FALSE));
572 GRefPtr<WebKitWebView> webView = WEBKIT_WEB_VIEW(Test::createWebView(test->m_webContext.get()));
573 webkit_web_view_load_html(webView.get(), "", nullptr);
574 g_signal_connect(webView.get(), "load-changed", G_CALLBACK(webViewLoadChanged), mainLoop.get());
575 g_main_loop_run(mainLoop.get());
576
577 auto* resource = webkit_web_view_get_main_resource(webView.get());
578 test->assertObjectIsDeletedWhenTestFinishes(G_OBJECT(resource));
579 webkit_web_resource_get_data(resource, nullptr, [](GObject* source, GAsyncResult* result, gpointer userData) {
580 size_t dataSize;
581 GUniqueOutPtr<GError> error;
582 auto* data = webkit_web_resource_get_data_finish(WEBKIT_WEB_RESOURCE(source), result, &dataSize, &error.outPtr());
583 g_assert_nonnull(data);
584 g_assert_cmpuint(dataSize, ==, 1);
585 g_assert_cmpint(data[0], ==, '\0');
586 g_assert_no_error(error.get());
587 g_main_loop_quit(static_cast<GMainLoop*>(userData));
588 }, mainLoop.get());
589 g_main_loop_run(mainLoop.get());
590}
591
592static void testWebViewResourcesHistoryCache(SingleResourceLoadTest* test, gconstpointer)
593{
594 CString javascriptURI = kServer->getURIForPath("/javascript.html");
595 test->loadURI(javascriptURI.data());
596 test->waitUntilResourceLoadFinished();
597 WebKitWebResource* resource = webkit_web_view_get_main_resource(test->m_webView);
598 g_assert_nonnull(resource);
599 g_assert_cmpstr(webkit_web_resource_get_uri(resource), ==, javascriptURI.data());
600
601 CString simpleStyleCSSURI = kServer->getURIForPath("/simple-style-css.html");
602 test->loadURI(simpleStyleCSSURI.data());
603 test->waitUntilResourceLoadFinished();
604 resource = webkit_web_view_get_main_resource(test->m_webView);
605 g_assert_nonnull(resource);
606 g_assert_cmpstr(webkit_web_resource_get_uri(resource), ==, simpleStyleCSSURI.data());
607
608 test->goBack();
609 test->waitUntilResourceLoadFinished();
610 resource = webkit_web_view_get_main_resource(test->m_webView);
611 g_assert_nonnull(resource);
612 g_assert_cmpstr(webkit_web_resource_get_uri(resource), ==, javascriptURI.data());
613
614 test->goForward();
615 test->waitUntilResourceLoadFinished();
616 resource = webkit_web_view_get_main_resource(test->m_webView);
617 g_assert_nonnull(resource);
618 g_assert_cmpstr(webkit_web_resource_get_uri(resource), ==, simpleStyleCSSURI.data());
619}
620
621class SendRequestTest: public SingleResourceLoadTest {
622public:
623 MAKE_GLIB_TEST_FIXTURE(SendRequestTest);
624
625 void resourceSentRequest(WebKitWebResource* resource, WebKitURIRequest* request, WebKitURIResponse* redirectResponse)
626 {
627 if (resource != m_resource)
628 return;
629
630 if (redirectResponse)
631 g_assert_cmpstr(webkit_uri_request_get_uri(request), ==, m_expectedNewResourceURIAfterRedirection.data());
632 else
633 g_assert_cmpstr(webkit_uri_request_get_uri(request), ==, m_expectedNewResourceURI.data());
634 g_assert_cmpstr(webkit_uri_request_get_uri(request), ==, webkit_web_resource_get_uri(resource));
635
636 SingleResourceLoadTest::resourceSentRequest(resource, request, redirectResponse);
637 }
638
639 void resourceFailed(WebKitWebResource* resource, GError* error)
640 {
641 if (resource != m_resource)
642 return;
643
644 g_assert_cmpstr(webkit_web_resource_get_uri(resource), ==, m_expectedCancelledResourceURI.data());
645 g_assert_error(error, WEBKIT_NETWORK_ERROR, WEBKIT_NETWORK_ERROR_CANCELLED);
646
647 SingleResourceLoadTest::resourceFailed(resource, error);
648 }
649
650 void setExpectedNewResourceURI(const CString& uri)
651 {
652 m_expectedNewResourceURI = uri;
653 }
654
655 void setExpectedCancelledResourceURI(const CString& uri)
656 {
657 m_expectedCancelledResourceURI = uri;
658 }
659
660 void setExpectedNewResourceURIAfterRedirection(const CString& uri)
661 {
662 m_expectedNewResourceURIAfterRedirection = uri;
663 }
664
665 CString m_expectedNewResourceURI;
666 CString m_expectedCancelledResourceURI;
667 CString m_expectedNewResourceURIAfterRedirection;
668};
669
670static void testWebResourceSendRequest(SendRequestTest* test, gconstpointer)
671{
672 test->setExpectedNewResourceURI(kServer->getURIForPath("/javascript.js"));
673 test->loadURI(kServer->getURIForPath("relative-javascript.html").data());
674 test->waitUntilResourceLoadFinished();
675 g_assert_nonnull(test->m_resource);
676
677 Vector<SingleResourceLoadTest::LoadEvents>& events = test->m_loadEvents;
678 g_assert_cmpint(events.size(), ==, 5);
679 g_assert_cmpint(events[0], ==, SingleResourceLoadTest::Started);
680 g_assert_cmpint(events[1], ==, SingleResourceLoadTest::SentRequest);
681 g_assert_cmpint(events[2], ==, SingleResourceLoadTest::ReceivedResponse);
682 g_assert_cmpint(events[3], ==, SingleResourceLoadTest::ReceivedData);
683 g_assert_cmpint(events[4], ==, SingleResourceLoadTest::Finished);
684 events.clear();
685
686 // Cancel request.
687 test->setExpectedCancelledResourceURI(kServer->getURIForPath("/cancel-this.js"));
688 test->loadURI(kServer->getURIForPath("/resource-to-cancel.html").data());
689 test->waitUntilResourceLoadFinished();
690 g_assert_nonnull(test->m_resource);
691
692 g_assert_cmpint(events.size(), ==, 3);
693 g_assert_cmpint(events[0], ==, SingleResourceLoadTest::Started);
694 g_assert_cmpint(events[1], ==, SingleResourceLoadTest::Failed);
695 g_assert_cmpint(events[2], ==, SingleResourceLoadTest::Finished);
696 events.clear();
697
698 // URI changed after a redirect.
699 test->setExpectedNewResourceURI(kServer->getURIForPath("/redirected.js"));
700 test->setExpectedNewResourceURIAfterRedirection(kServer->getURIForPath("/javascript-after-redirection.js"));
701 test->loadURI(kServer->getURIForPath("redirected-javascript.html").data());
702 test->waitUntilResourceLoadFinished();
703 g_assert_nonnull(test->m_resource);
704
705 g_assert_cmpint(events.size(), ==, 6);
706 g_assert_cmpint(events[0], ==, SingleResourceLoadTest::Started);
707 g_assert_cmpint(events[1], ==, SingleResourceLoadTest::SentRequest);
708 g_assert_cmpint(events[2], ==, SingleResourceLoadTest::Redirected);
709 g_assert_cmpint(events[3], ==, SingleResourceLoadTest::ReceivedResponse);
710 g_assert_cmpint(events[4], ==, SingleResourceLoadTest::ReceivedData);
711 g_assert_cmpint(events[5], ==, SingleResourceLoadTest::Finished);
712 events.clear();
713
714 // Cancel after a redirect.
715 test->setExpectedNewResourceURI(kServer->getURIForPath("/redirected-to-cancel.js"));
716 test->setExpectedCancelledResourceURI(kServer->getURIForPath("/redirected-to-cancel.js"));
717 test->loadURI(kServer->getURIForPath("/redirected-to-cancel.html").data());
718 test->waitUntilResourceLoadFinished();
719 g_assert_nonnull(test->m_resource);
720
721 g_assert_cmpint(events.size(), ==, 4);
722 g_assert_cmpint(events[0], ==, SingleResourceLoadTest::Started);
723 g_assert_cmpint(events[1], ==, SingleResourceLoadTest::SentRequest);
724 g_assert_cmpint(events[2], ==, SingleResourceLoadTest::Failed);
725 g_assert_cmpint(events[3], ==, SingleResourceLoadTest::Finished);
726 events.clear();
727}
728
729static GMutex s_serverMutex;
730static const unsigned s_maxConnectionsPerHost = 6;
731
732class SyncRequestOnMaxConnsTest: public ResourcesTest {
733public:
734 MAKE_GLIB_TEST_FIXTURE(SyncRequestOnMaxConnsTest);
735
736 void resourceLoadStarted(WebKitWebResource*, WebKitURIRequest*) override
737 {
738 if (!m_resourcesToStartPending)
739 return;
740
741 if (!--m_resourcesToStartPending)
742 g_main_loop_quit(m_mainLoop);
743 }
744
745 void waitUntilResourcesStarted(unsigned requestCount)
746 {
747 m_resourcesToStartPending = requestCount;
748 g_main_loop_run(m_mainLoop);
749 }
750
751 unsigned m_resourcesToStartPending;
752};
753
754#if SOUP_CHECK_VERSION(2, 49, 91)
755static void testWebViewSyncRequestOnMaxConns(SyncRequestOnMaxConnsTest* test, gconstpointer)
756{
757 WTF::GMutexLocker<GMutex> lock(s_serverMutex);
758 test->loadURI(kServer->getURIForPath("/sync-request-on-max-conns-0").data());
759 test->waitUntilResourcesStarted(s_maxConnectionsPerHost + 1); // s_maxConnectionsPerHost resource + main resource.
760
761 for (unsigned i = 0; i < 2; ++i) {
762 GUniquePtr<char> xhr(g_strdup_printf("xhr = new XMLHttpRequest; xhr.open('GET', '/sync-request-on-max-conns-xhr%u', false); xhr.send();", i));
763 webkit_web_view_run_javascript(test->m_webView, xhr.get(), nullptr, nullptr, nullptr);
764 }
765
766 // By default sync XHRs have a 10 seconds timeout, we don't want to wait all that so use our own timeout.
767 guint timeoutSourceID = g_timeout_add(1000, [] (gpointer) -> gboolean {
768 g_assert_not_reached();
769 return G_SOURCE_REMOVE;
770 }, nullptr);
771
772 struct UnlockServerSourceContext {
773 WTF::GMutexLocker<GMutex>& lock;
774 guint unlockServerSourceID;
775 } context = {
776 lock,
777 g_idle_add_full(G_PRIORITY_DEFAULT, [](gpointer userData) -> gboolean {
778 auto& context = *static_cast<UnlockServerSourceContext*>(userData);
779 context.unlockServerSourceID = 0;
780 context.lock.unlock();
781 return G_SOURCE_REMOVE;
782 }, &context, nullptr)
783 };
784 test->waitUntilResourcesLoaded(s_maxConnectionsPerHost + 3); // s_maxConnectionsPerHost resource + main resource + 2 XHR.
785 g_source_remove(timeoutSourceID);
786 if (context.unlockServerSourceID)
787 g_source_remove(context.unlockServerSourceID);
788}
789#endif
790
791static void addCacheHTTPHeadersToResponse(SoupMessage* message)
792{
793 // The actual date doesn't really matter.
794 SoupDate* soupDate = soup_date_new_from_now(0);
795 GUniquePtr<char> date(soup_date_to_string(soupDate, SOUP_DATE_HTTP));
796 soup_message_headers_append(message->response_headers, "Last-Modified", date.get());
797 soup_date_free(soupDate);
798 soup_message_headers_append(message->response_headers, "Cache-control", "public, max-age=31536000");
799 soupDate = soup_date_new_from_now(3600);
800 date.reset(soup_date_to_string(soupDate, SOUP_DATE_HTTP));
801 soup_message_headers_append(message->response_headers, "Expires", date.get());
802 soup_date_free(soupDate);
803}
804
805static void serverCallback(SoupServer* server, SoupMessage* message, const char* path, GHashTable*, SoupClientContext*, gpointer)
806{
807 if (message->method != SOUP_METHOD_GET) {
808 soup_message_set_status(message, SOUP_STATUS_NOT_IMPLEMENTED);
809 return;
810 }
811
812 soup_message_set_status(message, SOUP_STATUS_OK);
813
814 if (soup_message_headers_get_one(message->request_headers, "If-Modified-Since")) {
815 soup_message_set_status(message, SOUP_STATUS_NOT_MODIFIED);
816 soup_message_body_complete(message->response_body);
817 return;
818 }
819
820 if (g_str_equal(path, "/")) {
821 soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, kIndexHtml, strlen(kIndexHtml));
822 } else if (g_str_equal(path, "/javascript.html")) {
823 static const char* javascriptHtml = "<html><head><script language='javascript' src='/javascript.js'></script></head><body></body></html>";
824 soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, javascriptHtml, strlen(javascriptHtml));
825 } else if (g_str_equal(path, "/image.html")) {
826 static const char* imageHTML = "<html><body><img src='/blank.ico'></img></body></html>";
827 soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, imageHTML, strlen(imageHTML));
828 } else if (g_str_equal(path, "/redirected-css.html")) {
829 static const char* redirectedCSSHtml = "<html><head><link rel='stylesheet' href='/redirected.css' type='text/css'></head><body></html>";
830 soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, redirectedCSSHtml, strlen(redirectedCSSHtml));
831 } else if (g_str_equal(path, "/invalid-css.html")) {
832 static const char* invalidCSSHtml = "<html><head><link rel='stylesheet' href='/invalid.css' type='text/css'></head><body></html>";
833 soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, invalidCSSHtml, strlen(invalidCSSHtml));
834 } else if (g_str_equal(path, "/simple-style-css.html")) {
835 static const char* simpleStyleCSSHtml = "<html><head><link rel='stylesheet' href='/simple-style.css' type='text/css'></head><body></html>";
836 soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, simpleStyleCSSHtml, strlen(simpleStyleCSSHtml));
837 } else if (g_str_equal(path, "/style.css")) {
838 soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, kStyleCSS, strlen(kStyleCSS));
839 addCacheHTTPHeadersToResponse(message);
840 soup_message_headers_append(message->response_headers, "Content-Type", "text/css");
841 } else if (g_str_equal(path, "/javascript.js") || g_str_equal(path, "/javascript-after-redirection.js")) {
842 soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, kJavascript, strlen(kJavascript));
843 soup_message_headers_append(message->response_headers, "Content-Type", "text/javascript");
844 soup_message_headers_append(message->response_headers, "Content-Disposition", "attachment; filename=JavaScript.js");
845 } else if (g_str_equal(path, "/relative-javascript.html")) {
846 static const char* javascriptRelativeHTML = "<html><head><script language='javascript' src='remove-this/javascript.js'></script></head><body></body></html>";
847 soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, javascriptRelativeHTML, strlen(javascriptRelativeHTML));
848 } else if (g_str_equal(path, "/resource-to-cancel.html")) {
849 static const char* resourceToCancelHTML = "<html><head><script language='javascript' src='cancel-this.js'></script></head><body></body></html>";
850 soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, resourceToCancelHTML, strlen(resourceToCancelHTML));
851 } else if (g_str_equal(path, "/redirected-javascript.html")) {
852 static const char* javascriptRelativeHTML = "<html><head><script language='javascript' src='/redirected.js'></script></head><body></body></html>";
853 soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, javascriptRelativeHTML, strlen(javascriptRelativeHTML));
854 } else if (g_str_equal(path, "/redirected-to-cancel.html")) {
855 static const char* javascriptRelativeHTML = "<html><head><script language='javascript' src='/redirected-to-cancel.js'></script></head><body></body></html>";
856 soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, javascriptRelativeHTML, strlen(javascriptRelativeHTML));
857 } else if (g_str_equal(path, "/blank.ico")) {
858 GUniquePtr<char> filePath(g_build_filename(Test::getResourcesDir().data(), path, nullptr));
859 char* contents;
860 gsize contentsLength;
861 g_file_get_contents(filePath.get(), &contents, &contentsLength, 0);
862 soup_message_body_append(message->response_body, SOUP_MEMORY_TAKE, contents, contentsLength);
863 addCacheHTTPHeadersToResponse(message);
864 soup_message_headers_append(message->response_headers, "Content-Type", "image/vnd.microsoft.icon");
865 } else if (g_str_equal(path, "/simple-style.css")) {
866 static const char* simpleCSS =
867 "body {"
868 " margin: 0px;"
869 " padding: 0px;"
870 "}";
871 soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, simpleCSS, strlen(simpleCSS));
872 soup_message_headers_append(message->response_headers, "Content-Type", "text/css");
873 } else if (g_str_equal(path, "/redirected.css")) {
874 soup_message_set_status(message, SOUP_STATUS_MOVED_PERMANENTLY);
875 soup_message_headers_append(message->response_headers, "Location", "/simple-style.css");
876 } else if (g_str_equal(path, "/redirected.js")) {
877 soup_message_set_status(message, SOUP_STATUS_MOVED_PERMANENTLY);
878 soup_message_headers_append(message->response_headers, "Location", "/remove-this/javascript-after-redirection.js");
879 } else if (g_str_equal(path, "/redirected-to-cancel.js")) {
880 soup_message_set_status(message, SOUP_STATUS_MOVED_PERMANENTLY);
881 soup_message_headers_append(message->response_headers, "Location", "/cancel-this.js");
882 } else if (g_str_equal(path, "/invalid.css"))
883 soup_message_set_status(message, SOUP_STATUS_CANT_CONNECT);
884 else if (g_str_has_prefix(path, "/sync-request-on-max-conns-")) {
885 char* contents;
886 gsize contentsLength;
887 if (g_str_equal(path, "/sync-request-on-max-conns-0")) {
888 GString* imagesHTML = g_string_new("<html><body>");
889 for (unsigned i = 1; i <= s_maxConnectionsPerHost; ++i)
890 g_string_append_printf(imagesHTML, "<img src='/sync-request-on-max-conns-%u'>", i);
891 g_string_append(imagesHTML, "</body></html>");
892
893 contentsLength = imagesHTML->len;
894 contents = g_string_free(imagesHTML, FALSE);
895 } else {
896 {
897 // We don't actually need to keep the mutex, so we release it as soon as we get it.
898 WTF::GMutexLocker<GMutex> lock(s_serverMutex);
899 }
900
901 GUniquePtr<char> filePath(g_build_filename(Test::getResourcesDir().data(), "blank.ico", nullptr));
902 g_file_get_contents(filePath.get(), &contents, &contentsLength, 0);
903 }
904 soup_message_body_append(message->response_body, SOUP_MEMORY_TAKE, contents, contentsLength);
905 } else
906 soup_message_set_status(message, SOUP_STATUS_NOT_FOUND);
907 soup_message_body_complete(message->response_body);
908}
909
910void beforeAll()
911{
912 kServer = new WebKitTestServer(WebKitTestServer::ServerOptions::ServerRunInThread);
913 kServer->run(serverCallback);
914
915 ResourcesTest::add("WebKitWebView", "resources", testWebViewResources);
916 SingleResourceLoadTest::add("WebKitWebResource", "loading", testWebResourceLoading);
917 SingleResourceLoadTest::add("WebKitWebResource", "response", testWebResourceResponse);
918 SingleResourceLoadTest::add("WebKitWebResource", "mime-type", testWebResourceMimeType);
919 SingleResourceLoadTest::add("WebKitWebResource", "suggested-filename", testWebResourceSuggestedFilename);
920 ResourceURITrackingTest::add("WebKitWebResource", "active-uri", testWebResourceActiveURI);
921 ResourcesTest::add("WebKitWebResource", "get-data", testWebResourceGetData);
922 Test::add("WebKitWebResource", "get-data-error", testWebResourceGetDataError);
923 Test::add("WebKitWebResource", "get-data-empty", testWebResourceGetDataEmpty);
924 SingleResourceLoadTest::add("WebKitWebView", "history-cache", testWebViewResourcesHistoryCache);
925 SendRequestTest::add("WebKitWebPage", "send-request", testWebResourceSendRequest);
926#if SOUP_CHECK_VERSION(2, 49, 91)
927 SyncRequestOnMaxConnsTest::add("WebKitWebView", "sync-request-on-max-conns", testWebViewSyncRequestOnMaxConns);
928#endif
929}
930
931void afterAll()
932{
933 delete kServer;
934}
935