[CMake] Properly test if compiler supports compiler flags
[WebKit-https.git] / Tools / TestWebKitAPI / Tests / WebKitGLib / TestAutomationSession.cpp
1 /*
2  * Copyright (C) 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 "TestMain.h"
23 #include <gio/gio.h>
24 #include <wtf/UUID.h>
25 #include <wtf/text/StringBuilder.h>
26
27 // FIXME: WPE doesn't expose WebKitVersion yet, the numbers defined here don't really matter.
28 #if PLATFORM(WPE)
29 #define WEBKIT_MAJOR_VERSION 1
30 #define WEBKIT_MINOR_VERSION 2
31 #define WEBKIT_MICRO_VERSION 3
32 #endif
33
34 class AutomationTest: public Test {
35 public:
36     MAKE_GLIB_TEST_FIXTURE(AutomationTest);
37
38     AutomationTest()
39         : m_mainLoop(adoptGRef(g_main_loop_new(nullptr, TRUE)))
40     {
41         g_dbus_connection_new_for_address("tcp:host=127.0.0.1,port=2229",
42             G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, nullptr, nullptr, [](GObject*, GAsyncResult* result, gpointer userData) {
43                 GRefPtr<GDBusConnection> connection = adoptGRef(g_dbus_connection_new_for_address_finish(result, nullptr));
44                 static_cast<AutomationTest*>(userData)->setConnection(WTFMove(connection));
45             }, this);
46         g_main_loop_run(m_mainLoop.get());
47     }
48
49     ~AutomationTest()
50     {
51     }
52
53     struct Target {
54         Target() = default;
55         Target(guint64 id, CString name, bool isPaired)
56             : id(id)
57             , name(name)
58             , isPaired(isPaired)
59         {
60         }
61
62         guint64 id { 0 };
63         CString name;
64         bool isPaired { false };
65     };
66
67     const GDBusInterfaceVTable s_interfaceVTable = {
68         // method_call
69         [](GDBusConnection* connection, const gchar* sender, const gchar* objectPath, const gchar* interfaceName, const gchar* methodName, GVariant* parameters, GDBusMethodInvocation* invocation, gpointer userData) {
70             auto* test = static_cast<AutomationTest*>(userData);
71             if (!g_strcmp0(methodName, "SetTargetList")) {
72                 guint64 connectionID;
73                 GUniqueOutPtr<GVariantIter> iter;
74                 g_variant_get(parameters, "(ta(tsssb))", &connectionID, &iter.outPtr());
75                 guint64 targetID;
76                 const char* type;
77                 const char* name;
78                 const char* dummy;
79                 gboolean isPaired;
80                 while (g_variant_iter_loop(iter.get(), "(t&s&s&sb)", &targetID, &type, &name, &dummy, &isPaired)) {
81                     if (!g_strcmp0(type, "Automation")) {
82                         test->setTarget(connectionID, Target(targetID, name, isPaired));
83                         break;
84                     }
85                 }
86                 g_dbus_method_invocation_return_value(invocation, nullptr);
87             } else if (!g_strcmp0(methodName, "SendMessageToFrontend")) {
88                 guint64 connectionID, targetID;
89                 const char* message;
90                 g_variant_get(parameters, "(tt&s)", &connectionID, &targetID, &message);
91                 test->receivedMessage(connectionID, targetID, message);
92                 g_dbus_method_invocation_return_value(invocation, nullptr);
93             }
94         },
95         // get_property
96         nullptr,
97         // set_property
98         nullptr,
99         // padding
100         nullptr
101     };
102
103     void registerDBusObject()
104     {
105         static const char introspectionXML[] =
106             "<node>"
107             "  <interface name='org.webkit.RemoteInspectorClient'>"
108             "    <method name='SetTargetList'>"
109             "      <arg type='t' name='connectionID' direction='in'/>"
110             "      <arg type='a(tsssb)' name='list' direction='in'/>"
111             "    </method>"
112             "    <method name='SendMessageToFrontend'>"
113             "      <arg type='t' name='connectionID' direction='in'/>"
114             "      <arg type='t' name='target' direction='in'/>"
115             "      <arg type='s' name='message' direction='in'/>"
116             "    </method>"
117             "  </interface>"
118             "</node>";
119         static GDBusNodeInfo* introspectionData = nullptr;
120         if (!introspectionData)
121             introspectionData = g_dbus_node_info_new_for_xml(introspectionXML, nullptr);
122         g_dbus_connection_register_object(m_connection.get(), "/org/webkit/RemoteInspectorClient", introspectionData->interfaces[0], &s_interfaceVTable, this, nullptr, nullptr);
123     }
124
125     void setConnection(GRefPtr<GDBusConnection>&& connection)
126     {
127         g_assert(G_IS_DBUS_CONNECTION(connection.get()));
128         m_connection = WTFMove(connection);
129         registerDBusObject();
130         g_main_loop_quit(m_mainLoop.get());
131     }
132
133     void setTarget(guint64 connectionID, Target&& target)
134     {
135         bool newConnection = !m_connectionID;
136         bool wasPaired = m_target.isPaired;
137         m_connectionID = connectionID;
138         m_target = WTFMove(target);
139         if (newConnection || (!wasPaired && m_target.isPaired))
140             g_main_loop_quit(m_mainLoop.get());
141     }
142
143     void receivedMessage(guint64 connectionID, guint64 targetID, const char* message)
144     {
145         g_assert(connectionID == m_connectionID);
146         g_assert(targetID == m_target.id);
147         m_message = message;
148         g_main_loop_quit(m_mainLoop.get());
149     }
150
151     void sendCommandToBackend(const String& command, const String& parameters = String())
152     {
153         static long sequenceID = 0;
154         StringBuilder messageBuilder;
155         messageBuilder.appendLiteral("{\"id\":");
156         messageBuilder.appendNumber(++sequenceID);
157         messageBuilder.appendLiteral(",\"method\":\"Automation.");
158         messageBuilder.append(command);
159         messageBuilder.append('"');
160         if (!parameters.isNull()) {
161             messageBuilder.appendLiteral(",\"params\":");
162             messageBuilder.append(parameters);
163         }
164         messageBuilder.append('}');
165         g_dbus_connection_call(m_connection.get(), nullptr, "/org/webkit/Inspector", "org.webkit.Inspector",
166             "SendMessageToBackend", g_variant_new("(tts)", m_connectionID, m_target.id, messageBuilder.toString().utf8().data()),
167             nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, nullptr, nullptr, nullptr);
168     }
169
170     static WebKitWebView* createWebViewCallback(WebKitAutomationSession* session, AutomationTest* test)
171     {
172         test->m_createWebViewWasCalled = true;
173         return test->m_webViewForAutomation;
174     }
175
176     void automationStarted(WebKitAutomationSession* session)
177     {
178         m_session = session;
179         assertObjectIsDeletedWhenTestFinishes(G_OBJECT(m_session));
180         g_assert(!webkit_automation_session_get_application_info(session));
181         WebKitApplicationInfo* info = webkit_application_info_new();
182         webkit_application_info_set_name(info, "AutomationTestBrowser");
183         webkit_application_info_set_version(info, WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, WEBKIT_MICRO_VERSION);
184         webkit_automation_session_set_application_info(session, info);
185         webkit_application_info_unref(info);
186         g_assert(webkit_automation_session_get_application_info(session) == info);
187     }
188
189     static void automationStartedCallback(WebKitWebContext* webContext, WebKitAutomationSession* session, AutomationTest* test)
190     {
191         g_assert(webContext == test->m_webContext.get());
192         g_assert(WEBKIT_IS_AUTOMATION_SESSION(session));
193         test->automationStarted(session);
194     }
195
196     WebKitAutomationSession* requestSession(const char* sessionID)
197     {
198         auto signalID = g_signal_connect(m_webContext.get(), "automation-started", G_CALLBACK(automationStartedCallback), this);
199         g_dbus_connection_call(m_connection.get(), nullptr, "/org/webkit/Inspector", "org.webkit.Inspector",
200             "StartAutomationSession", g_variant_new("(s)", sessionID), nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, nullptr,
201             [](GObject* source, GAsyncResult* result, gpointer userData) {
202                 auto* test = static_cast<AutomationTest*>(userData);
203                 if (!test->m_session)
204                     return;
205
206                 GRefPtr<GVariant> capabilities = adoptGRef(g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, nullptr));
207                 g_assert(capabilities.get());
208                 const char* browserName;
209                 const char* browserVersion;
210                 g_variant_get(capabilities.get(), "(&s&s)", &browserName, &browserVersion);
211                 g_assert_cmpstr(browserName, ==, "AutomationTestBrowser");
212                 GUniquePtr<char> versionString(g_strdup_printf("%u.%u.%u", WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, WEBKIT_MICRO_VERSION));
213                 g_assert_cmpstr(browserVersion, ==, versionString.get());
214             }, this
215         );
216         auto timeoutID = g_timeout_add(1000, [](gpointer userData) -> gboolean {
217             g_main_loop_quit(static_cast<GMainLoop*>(userData));
218             return G_SOURCE_REMOVE;
219         }, m_mainLoop.get());
220         g_main_loop_run(m_mainLoop.get());
221         if (!m_connectionID)
222             m_session = nullptr;
223         if (m_session && m_connectionID)
224             g_source_remove(timeoutID);
225         g_signal_handler_disconnect(m_webContext.get(), signalID);
226         return m_session;
227     }
228
229     void setupIfNeeded()
230     {
231         if (m_target.isPaired)
232             return;
233         g_assert(m_target.id);
234         g_dbus_connection_call(m_connection.get(), nullptr, "/org/webkit/Inspector", "org.webkit.Inspector",
235             "Setup", g_variant_new("(tt)", m_connectionID, m_target.id), nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, nullptr, nullptr, nullptr);
236         g_main_loop_run(m_mainLoop.get());
237         g_assert(m_target.isPaired);
238     }
239
240     bool createTopLevelBrowsingContext(WebKitWebView* webView)
241     {
242         setupIfNeeded();
243         m_webViewForAutomation = webView;
244         m_createWebViewWasCalled = false;
245         m_message = CString();
246         auto signalID = g_signal_connect(m_session, "create-web-view", G_CALLBACK(createWebViewCallback), this);
247         sendCommandToBackend("createBrowsingContext");
248         g_main_loop_run(m_mainLoop.get());
249         g_signal_handler_disconnect(m_session, signalID);
250         g_assert(m_createWebViewWasCalled);
251         g_assert(!m_message.isNull());
252         m_webViewForAutomation = nullptr;
253
254         if (strstr(m_message.data(), "The remote session failed to create a new browsing context"))
255             return false;
256         if (strstr(m_message.data(), "handle"))
257             return true;
258         return false;
259     }
260
261     GRefPtr<GMainLoop> m_mainLoop;
262     GRefPtr<GDBusConnection> m_connection;
263     WebKitAutomationSession* m_session;
264     guint64 m_connectionID { 0 };
265     Target m_target;
266
267     WebKitWebView* m_webViewForAutomation { nullptr };
268     bool m_createWebViewWasCalled { false };
269     CString m_message;
270 };
271
272 static void testAutomationSessionRequestSession(AutomationTest* test, gconstpointer)
273 {
274     String sessionID = createCanonicalUUIDString();
275     // WebKitAutomationSession::automation-started is never emitted if automation is not enabled.
276     g_assert(!webkit_web_context_is_automation_allowed(test->m_webContext.get()));
277     auto* session = test->requestSession(sessionID.utf8().data());
278     g_assert(!session);
279
280     webkit_web_context_set_automation_allowed(test->m_webContext.get(), TRUE);
281     g_assert(webkit_web_context_is_automation_allowed(test->m_webContext.get()));
282
283     // There can't be more than one context with automation enabled
284     GRefPtr<WebKitWebContext> otherContext = adoptGRef(webkit_web_context_new());
285     test->removeLogFatalFlag(G_LOG_LEVEL_WARNING);
286     webkit_web_context_set_automation_allowed(otherContext.get(), TRUE);
287     test->addLogFatalFlag(G_LOG_LEVEL_WARNING);
288     g_assert(!webkit_web_context_is_automation_allowed(otherContext.get()));
289
290     session = test->requestSession(sessionID.utf8().data());
291     g_assert_cmpstr(webkit_automation_session_get_id(session), ==, sessionID.utf8().data());
292     g_assert_cmpuint(test->m_target.id, >, 0);
293     ASSERT_CMP_CSTRING(test->m_target.name, ==, sessionID.utf8());
294     g_assert(!test->m_target.isPaired);
295
296     // Will fail to create a browsing context when not creating a web view (or not handling the signal).
297     g_assert(!test->createTopLevelBrowsingContext(nullptr));
298
299     // Will also fail if the web view is not controlled by automation.
300     auto webView = Test::adoptView(webkit_web_view_new_with_context(test->m_webContext.get()));
301     g_assert(!webkit_web_view_is_controlled_by_automation(webView.get()));
302     g_assert(!test->createTopLevelBrowsingContext(webView.get()));
303
304     // And will work with a proper web view.
305     webView = Test::adoptView(g_object_new(WEBKIT_TYPE_WEB_VIEW, "web-context", test->m_webContext.get(), "is-controlled-by-automation", TRUE, nullptr));
306     g_assert(webkit_web_view_is_controlled_by_automation(webView.get()));
307     g_assert(test->createTopLevelBrowsingContext(webView.get()));
308
309     webkit_web_context_set_automation_allowed(test->m_webContext.get(), FALSE);
310 }
311
312 static void testAutomationSessionApplicationInfo(Test* test, gconstpointer)
313 {
314     WebKitApplicationInfo* info = webkit_application_info_new();
315     g_assert_cmpstr(webkit_application_info_get_name(info), ==, g_get_prgname());
316     webkit_application_info_set_name(info, "WebKitGTKBrowser");
317     g_assert_cmpstr(webkit_application_info_get_name(info), ==, "WebKitGTKBrowser");
318     webkit_application_info_set_name(info, nullptr);
319     g_assert_cmpstr(webkit_application_info_get_name(info), ==, g_get_prgname());
320
321     guint64 major, minor, micro;
322     webkit_application_info_get_version(info, &major, nullptr, nullptr);
323     g_assert_cmpuint(major, ==, 0);
324     webkit_application_info_set_version(info, 1, 2, 3);
325     webkit_application_info_get_version(info, &major, &minor, &micro);
326     g_assert_cmpuint(major, ==, 1);
327     g_assert_cmpuint(minor, ==, 2);
328     g_assert_cmpuint(micro, ==, 3);
329
330     webkit_application_info_unref(info);
331 }
332
333
334 void beforeAll()
335 {
336     g_setenv("WEBKIT_INSPECTOR_SERVER", "127.0.0.1:2229", TRUE);
337
338     AutomationTest::add("WebKitAutomationSession", "request-session", testAutomationSessionRequestSession);
339     Test::add("WebKitAutomationSession", "application-info", testAutomationSessionApplicationInfo);
340 }
341
342 void afterAll()
343 {
344     g_unsetenv("WEBKIT_INSPECTOR_SERVER");
345 }