[GTK] Expose user script messages to GObject DOM bindings
[WebKit-https.git] / Tools / TestWebKitAPI / Tests / WebKit2Gtk / TestWebKitUserContentManager.cpp
1 /*
2  * Copyright (C) 2013-2014 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 <cstdarg>
25 #include <gtk/gtk.h>
26 #include <webkit2/webkit2.h>
27 #include <wtf/gobject/GRefPtr.h>
28 #include <wtf/gobject/GUniquePtr.h>
29
30 class UserContentManagerTest : public WebViewTest {
31 public:
32     MAKE_GLIB_TEST_FIXTURE(UserContentManagerTest);
33
34     UserContentManagerTest()
35         : WebViewTest(WEBKIT_WEB_VIEW(webkit_web_view_new_with_user_content_manager(webkit_user_content_manager_new())))
36     {
37         // A reference is leaked when passing the result of webkit_user_content_manager_new()
38         // directly to webkit_web_view_new_with_user_content_manager() above. Adopting the
39         // reference here avoids the leak.
40         m_userContentManager = adoptGRef(webkit_web_view_get_user_content_manager(m_webView));
41         assertObjectIsDeletedWhenTestFinishes(G_OBJECT(m_userContentManager.get()));
42     }
43
44     GRefPtr<WebKitUserContentManager> m_userContentManager;
45 };
46
47 static WebKitTestServer* kServer;
48
49 // These are all here so that they can be changed easily, if necessary.
50 static const char* kStyleSheetHTML = "<html><div id=\"styledElement\">Sweet stylez!</div></html>";
51 static const char* kInjectedStyleSheet = "#styledElement { font-weight: bold; }";
52 static const char* kStyleSheetTestScript = "getComputedStyle(document.getElementById('styledElement'))['font-weight']";
53 static const char* kStyleSheetTestScriptResult = "bold";
54 static const char* kInjectedScript = "document.write('<div id=\"item\">Generated by a script</div>')";
55 static const char* kScriptTestScript = "document.getElementById('item').innerText";
56 static const char* kScriptTestScriptResult = "Generated by a script";
57
58 static void testWebViewNewWithUserContentManager(Test* test, gconstpointer)
59 {
60     GRefPtr<WebKitUserContentManager> userContentManager1 = adoptGRef(webkit_user_content_manager_new());
61     test->assertObjectIsDeletedWhenTestFinishes(G_OBJECT(userContentManager1.get()));
62     GRefPtr<WebKitWebView> webView1 = WEBKIT_WEB_VIEW(webkit_web_view_new_with_user_content_manager(userContentManager1.get()));
63     g_assert(webkit_web_view_get_user_content_manager(webView1.get()) == userContentManager1.get());
64
65     GRefPtr<WebKitWebView> webView2 = WEBKIT_WEB_VIEW(webkit_web_view_new());
66     g_assert(webkit_web_view_get_user_content_manager(webView2.get()) != userContentManager1.get());
67 }
68
69 static bool isStyleSheetInjectedForURLAtPath(WebViewTest* test, const char* path)
70 {
71     test->loadURI(kServer->getURIForPath(path).data());
72     test->waitUntilLoadFinished();
73
74     GUniqueOutPtr<GError> error;
75     WebKitJavascriptResult* javascriptResult = test->runJavaScriptAndWaitUntilFinished(kStyleSheetTestScript, &error.outPtr());
76     g_assert(javascriptResult);
77     g_assert(!error.get());
78
79     GUniquePtr<char> resultString(WebViewTest::javascriptResultToCString(javascriptResult));
80     return !g_strcmp0(resultString.get(), kStyleSheetTestScriptResult);
81 }
82
83 static bool isScriptInjectedForURLAtPath(WebViewTest* test, const char* path)
84 {
85     test->loadURI(kServer->getURIForPath(path).data());
86     test->waitUntilLoadFinished();
87
88     GUniqueOutPtr<GError> error;
89     if (WebKitJavascriptResult* javascriptResult = test->runJavaScriptAndWaitUntilFinished(kScriptTestScript, &error.outPtr())) {
90         g_assert(!error.get());
91
92         GUniquePtr<char> resultString(WebViewTest::javascriptResultToCString(javascriptResult));
93         return !g_strcmp0(resultString.get(), kScriptTestScriptResult);
94     }
95     return false;
96 }
97
98 static void fillURLListFromPaths(char** list, const char* path, ...)
99 {
100     va_list argumentList;
101     va_start(argumentList, path);
102
103     int i = 0;
104     while (path) {
105         // FIXME: We must use a wildcard for the host here until http://wkbug.com/112476 is fixed.
106         // Until that time patterns with port numbers in them will not properly match URLs with port numbers.
107         list[i++] = g_strdup_printf("http://*/%s*", path);
108         path = va_arg(argumentList, const char*);
109     }
110 }
111
112 static void removeOldInjectedContentAndResetLists(WebKitUserContentManager* userContentManager, char** whitelist, char** blacklist)
113 {
114     webkit_user_content_manager_remove_all_style_sheets(userContentManager);
115     webkit_user_content_manager_remove_all_scripts(userContentManager);
116
117     while (*whitelist) {
118         g_free(*whitelist);
119         *whitelist = 0;
120         whitelist++;
121     }
122
123     while (*blacklist) {
124         g_free(*blacklist);
125         *blacklist = 0;
126         blacklist++;
127     }
128 }
129
130 static void testUserContentManagerInjectedStyleSheet(UserContentManagerTest* test, gconstpointer)
131 {
132     char* whitelist[3] = { 0, 0, 0 };
133     char* blacklist[3] = { 0, 0, 0 };
134
135     removeOldInjectedContentAndResetLists(test->m_userContentManager.get(), whitelist, blacklist);
136
137     // Without a whitelist or a blacklist all URLs should have the injected style sheet.
138     static const char* randomPath = "somerandompath";
139     g_assert(!isStyleSheetInjectedForURLAtPath(test, randomPath));
140     WebKitUserStyleSheet* styleSheet = webkit_user_style_sheet_new(kInjectedStyleSheet, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, WEBKIT_USER_STYLE_LEVEL_USER, nullptr, nullptr);
141     webkit_user_content_manager_add_style_sheet(test->m_userContentManager.get(), styleSheet);
142     webkit_user_style_sheet_unref(styleSheet);
143     g_assert(isStyleSheetInjectedForURLAtPath(test, randomPath));
144
145     removeOldInjectedContentAndResetLists(test->m_userContentManager.get(), whitelist, blacklist);
146
147     fillURLListFromPaths(blacklist, randomPath, 0);
148     styleSheet = webkit_user_style_sheet_new(kInjectedStyleSheet, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, WEBKIT_USER_STYLE_LEVEL_USER, nullptr, blacklist);
149     webkit_user_content_manager_add_style_sheet(test->m_userContentManager.get(), styleSheet);
150     webkit_user_style_sheet_unref(styleSheet);
151     g_assert(!isStyleSheetInjectedForURLAtPath(test, randomPath));
152     g_assert(isStyleSheetInjectedForURLAtPath(test, "someotherrandompath"));
153
154     removeOldInjectedContentAndResetLists(test->m_userContentManager.get(), whitelist, blacklist);
155
156     static const char* inTheWhiteList = "inthewhitelist";
157     static const char* notInWhitelist = "notinthewhitelist";
158     static const char* inTheWhiteListAndBlackList = "inthewhitelistandblacklist";
159
160     fillURLListFromPaths(whitelist, inTheWhiteList, inTheWhiteListAndBlackList, 0);
161     fillURLListFromPaths(blacklist, inTheWhiteListAndBlackList, 0);
162     styleSheet = webkit_user_style_sheet_new(kInjectedStyleSheet, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, WEBKIT_USER_STYLE_LEVEL_USER, whitelist, blacklist);
163     webkit_user_content_manager_add_style_sheet(test->m_userContentManager.get(), styleSheet);
164     webkit_user_style_sheet_unref(styleSheet);
165     g_assert(isStyleSheetInjectedForURLAtPath(test, inTheWhiteList));
166     g_assert(!isStyleSheetInjectedForURLAtPath(test, inTheWhiteListAndBlackList));
167     g_assert(!isStyleSheetInjectedForURLAtPath(test, notInWhitelist));
168
169     // It's important to clean up the environment before other tests.
170     removeOldInjectedContentAndResetLists(test->m_userContentManager.get(), whitelist, blacklist);
171 }
172
173 static void testUserContentManagerInjectedScript(UserContentManagerTest* test, gconstpointer)
174 {
175     char* whitelist[3] = { 0, 0, 0 };
176     char* blacklist[3] = { 0, 0, 0 };
177
178     removeOldInjectedContentAndResetLists(test->m_userContentManager.get(), whitelist, blacklist);
179
180     // Without a whitelist or a blacklist all URLs should have the injected script.
181     static const char* randomPath = "somerandompath";
182     g_assert(!isScriptInjectedForURLAtPath(test, randomPath));
183     WebKitUserScript* script = webkit_user_script_new(kInjectedScript, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END, nullptr, nullptr);
184     webkit_user_content_manager_add_script(test->m_userContentManager.get(), script);
185     webkit_user_script_unref(script);
186     g_assert(isScriptInjectedForURLAtPath(test, randomPath));
187
188     removeOldInjectedContentAndResetLists(test->m_userContentManager.get(), whitelist, blacklist);
189
190     fillURLListFromPaths(blacklist, randomPath, 0);
191     script = webkit_user_script_new(kInjectedScript, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END, nullptr, blacklist);
192     webkit_user_content_manager_add_script(test->m_userContentManager.get(), script);
193     webkit_user_script_unref(script);
194     g_assert(!isScriptInjectedForURLAtPath(test, randomPath));
195     g_assert(isScriptInjectedForURLAtPath(test, "someotherrandompath"));
196
197     removeOldInjectedContentAndResetLists(test->m_userContentManager.get(), whitelist, blacklist);
198
199     static const char* inTheWhiteList = "inthewhitelist";
200     static const char* notInWhitelist = "notinthewhitelist";
201     static const char* inTheWhiteListAndBlackList = "inthewhitelistandblacklist";
202
203     fillURLListFromPaths(whitelist, inTheWhiteList, inTheWhiteListAndBlackList, 0);
204     fillURLListFromPaths(blacklist, inTheWhiteListAndBlackList, 0);
205     script = webkit_user_script_new(kInjectedScript, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END, whitelist, blacklist);
206     webkit_user_content_manager_add_script(test->m_userContentManager.get(), script);
207     webkit_user_script_unref(script);
208     g_assert(isScriptInjectedForURLAtPath(test, inTheWhiteList));
209     g_assert(!isScriptInjectedForURLAtPath(test, inTheWhiteListAndBlackList));
210     g_assert(!isScriptInjectedForURLAtPath(test, notInWhitelist));
211
212     // It's important to clean up the environment before other tests.
213     removeOldInjectedContentAndResetLists(test->m_userContentManager.get(), whitelist, blacklist);
214 }
215
216 class UserScriptMessageTest : public UserContentManagerTest {
217 public:
218     MAKE_GLIB_TEST_FIXTURE(UserScriptMessageTest);
219
220     UserScriptMessageTest()
221         : UserContentManagerTest()
222         , m_userScriptMessage(nullptr)
223     {
224     }
225
226     ~UserScriptMessageTest()
227     {
228         if (m_userScriptMessage)
229             webkit_javascript_result_unref(m_userScriptMessage);
230     }
231
232     bool registerHandler(const char* handlerName)
233     {
234         return webkit_user_content_manager_register_script_message_handler(m_userContentManager.get(), handlerName);
235     }
236
237     void unregisterHandler(const char* handlerName)
238     {
239         webkit_user_content_manager_unregister_script_message_handler(m_userContentManager.get(), handlerName);
240     }
241
242     static void scriptMessageReceived(WebKitUserContentManager* userContentManager, WebKitJavascriptResult* jsResult, UserScriptMessageTest* test)
243     {
244         g_signal_handlers_disconnect_by_func(userContentManager, reinterpret_cast<gpointer>(scriptMessageReceived), test);
245         g_main_loop_quit(test->m_mainLoop);
246
247         g_assert(!test->m_userScriptMessage);
248         test->m_userScriptMessage = webkit_javascript_result_ref(jsResult);
249     }
250
251     WebKitJavascriptResult* waitUntilMessageReceived(const char* handlerName)
252     {
253         if (m_userScriptMessage) {
254             webkit_javascript_result_unref(m_userScriptMessage);
255             m_userScriptMessage = nullptr;
256         }
257
258         GUniquePtr<char> signalName(g_strdup_printf("script-message-received::%s", handlerName));
259         g_signal_connect(m_userContentManager.get(), signalName.get(), G_CALLBACK(scriptMessageReceived), this);
260
261         g_main_loop_run(m_mainLoop);
262         g_assert(m_userScriptMessage);
263         return m_userScriptMessage;
264     }
265
266     WebKitJavascriptResult* postMessageAndWaitUntilReceived(const char* handlerName, const char* javascriptValueAsText)
267     {
268         GUniquePtr<char> javascriptSnippet(g_strdup_printf("window.webkit.messageHandlers.%s.postMessage(%s);", handlerName, javascriptValueAsText));
269         webkit_web_view_run_javascript(m_webView, javascriptSnippet.get(), nullptr, nullptr, nullptr);
270         return waitUntilMessageReceived(handlerName);
271     }
272
273 private:
274     WebKitJavascriptResult* m_userScriptMessage;
275 };
276
277 static void testUserContentManagerScriptMessageReceived(UserScriptMessageTest* test, gconstpointer)
278 {
279     g_assert(test->registerHandler("msg"));
280
281     // Trying to register the same handler a second time must fail.
282     g_assert(!test->registerHandler("msg"));
283
284     test->loadHtml("<html></html>", nullptr);
285     test->waitUntilLoadFinished();
286
287     // Check that the "window.webkit.messageHandlers" namespace exists.
288     GUniqueOutPtr<GError> error;
289     WebKitJavascriptResult* javascriptResult = test->runJavaScriptAndWaitUntilFinished("window.webkit.messageHandlers ? 'y' : 'n';", &error.outPtr());
290     g_assert(javascriptResult);
291     g_assert(!error.get());
292     GUniquePtr<char> valueString(WebViewTest::javascriptResultToCString(javascriptResult));
293     g_assert_cmpstr(valueString.get(), ==, "y");
294
295     // Check that the "document.webkit.messageHandlers.msg" namespace exists.
296     javascriptResult = test->runJavaScriptAndWaitUntilFinished("window.webkit.messageHandlers.msg ? 'y' : 'n';", &error.outPtr());
297     g_assert(javascriptResult);
298     g_assert(!error.get());
299     valueString.reset(WebViewTest::javascriptResultToCString(javascriptResult));
300     g_assert_cmpstr(valueString.get(), ==, "y");
301
302     valueString.reset(WebViewTest::javascriptResultToCString(test->postMessageAndWaitUntilReceived("msg", "'user message'")));
303     g_assert_cmpstr(valueString.get(), ==, "user message");
304
305     // Messages should arrive despite of other handlers being registered.
306     g_assert(test->registerHandler("anotherHandler"));
307
308     // Check that the "document.webkit.messageHandlers.msg" namespace still exists.
309     javascriptResult = test->runJavaScriptAndWaitUntilFinished("window.webkit.messageHandlers.msg ? 'y' : 'n';", &error.outPtr());
310     g_assert(javascriptResult);
311     g_assert(!error.get());
312     valueString.reset(WebViewTest::javascriptResultToCString(javascriptResult));
313     g_assert_cmpstr(valueString.get(), ==, "y");
314
315     // Check that the "document.webkit.messageHandlers.anotherHandler" namespace exists.
316     javascriptResult = test->runJavaScriptAndWaitUntilFinished("window.webkit.messageHandlers.anotherHandler ? 'y' : 'n';", &error.outPtr());
317     g_assert(javascriptResult);
318     g_assert(!error.get());
319     valueString.reset(WebViewTest::javascriptResultToCString(javascriptResult));
320     g_assert_cmpstr(valueString.get(), ==, "y");
321
322     valueString.reset(WebViewTest::javascriptResultToCString(test->postMessageAndWaitUntilReceived("msg", "'handler: msg'")));
323     g_assert_cmpstr(valueString.get(), ==, "handler: msg");
324
325     valueString.reset(WebViewTest::javascriptResultToCString(test->postMessageAndWaitUntilReceived("anotherHandler", "'handler: anotherHandler'")));
326     g_assert_cmpstr(valueString.get(), ==, "handler: anotherHandler");
327
328     // Unregistering a handler and re-registering again under the same name should work.
329     test->unregisterHandler("msg");
330
331     // FIXME: Enable after https://bugs.webkit.org/show_bug.cgi?id=138142 gets fixed.
332 #if 0
333     javascriptResult = test->runJavaScriptAndWaitUntilFinished("window.webkit.messageHandlers.msg.postMessage('42');", &error.outPtr());
334     g_assert(!javascriptResult);
335     g_assert(error.get());
336
337     // Re-registering a handler that has been unregistered must work
338     g_assert(test->registerHandler("msg"));
339     message = test->postMessageAndWaitUntilReceived("msg", "'handler: msg'");
340     valueString.reset(WebViewTest::javascriptResultToCString(message));
341     webkit_javascript_result_unref(message);
342     g_assert_cmpstr(valueString.get(), ==, "handler: msg");
343 #endif
344
345     test->unregisterHandler("anotherHandler");
346 }
347
348 static void testUserContentManagerScriptMessageFromDOMBindings(UserScriptMessageTest* test, gconstpointer)
349 {
350     g_assert(test->registerHandler("dom"));
351
352     test->loadHtml("<html></html>", nullptr);
353     WebKitJavascriptResult* javascriptResult = test->waitUntilMessageReceived("dom");
354     g_assert(javascriptResult);
355     GUniquePtr<char> valueString(WebViewTest::javascriptResultToCString(javascriptResult));
356     g_assert_cmpstr(valueString.get(), ==, "DocumentLoaded");
357
358     test->unregisterHandler("dom");
359 }
360
361 static void serverCallback(SoupServer* server, SoupMessage* message, const char* path, GHashTable*, SoupClientContext*, gpointer)
362 {
363     soup_message_set_status(message, SOUP_STATUS_OK);
364     soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, kStyleSheetHTML, strlen(kStyleSheetHTML));
365     soup_message_body_complete(message->response_body);
366 }
367
368 void beforeAll()
369 {
370     webkit_web_context_set_web_extensions_directory(webkit_web_context_get_default(), WEBKIT_TEST_WEB_EXTENSIONS_DIR);
371     kServer = new WebKitTestServer();
372     kServer->run(serverCallback);
373
374     Test::add("WebKitWebView", "new-with-user-content-manager", testWebViewNewWithUserContentManager);
375     UserContentManagerTest::add("WebKitUserContentManager", "injected-style-sheet", testUserContentManagerInjectedStyleSheet);
376     UserContentManagerTest::add("WebKitUserContentManager", "injected-script", testUserContentManagerInjectedScript);
377     UserScriptMessageTest::add("WebKitUserContentManager", "script-message-received", testUserContentManagerScriptMessageReceived);
378     UserScriptMessageTest::add("WebKitUserContentManager", "script-message-from-dom-bindings", testUserContentManagerScriptMessageFromDOMBindings);
379 }
380
381 void afterAll()
382 {
383     delete kServer;
384 }