1/*
2 * Copyright (C) 2017 Igalia S.L.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "SessionHost.h"
28
29#include "WebDriverService.h"
30#include <gio/gio.h>
31#include <wtf/RunLoop.h>
32#include <wtf/UUID.h>
33#include <wtf/glib/GUniquePtr.h>
34
35#define REMOTE_INSPECTOR_CLIENT_DBUS_INTERFACE "org.webkit.RemoteInspectorClient"
36#define REMOTE_INSPECTOR_CLIENT_OBJECT_PATH "/org/webkit/RemoteInspectorClient"
37#define INSPECTOR_DBUS_INTERFACE "org.webkit.Inspector"
38#define INSPECTOR_DBUS_OBJECT_PATH "/org/webkit/Inspector"
39
40namespace WebDriver {
41
42SessionHost::~SessionHost()
43{
44 if (m_dbusConnection)
45 g_signal_handlers_disconnect_matched(m_dbusConnection.get(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this);
46 g_cancellable_cancel(m_cancellable.get());
47 if (m_browser)
48 g_subprocess_force_exit(m_browser.get());
49}
50
51static const char introspectionXML[] =
52 "<node>"
53 " <interface name='" REMOTE_INSPECTOR_CLIENT_DBUS_INTERFACE "'>"
54 " <method name='SetTargetList'>"
55 " <arg type='t' name='connectionID' direction='in'/>"
56 " <arg type='a(tsssb)' name='list' direction='in'/>"
57 " </method>"
58 " <method name='SendMessageToFrontend'>"
59 " <arg type='t' name='connectionID' direction='in'/>"
60 " <arg type='t' name='target' direction='in'/>"
61 " <arg type='s' name='message' direction='in'/>"
62 " </method>"
63 " </interface>"
64 "</node>";
65
66const GDBusInterfaceVTable SessionHost::s_interfaceVTable = {
67 // method_call
68 [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar* methodName, GVariant* parameters, GDBusMethodInvocation* invocation, gpointer userData) {
69 auto* sessionHost = static_cast<SessionHost*>(userData);
70 if (!g_strcmp0(methodName, "SetTargetList")) {
71 guint64 connectionID;
72 GUniqueOutPtr<GVariantIter> iter;
73 g_variant_get(parameters, "(ta(tsssb))", &connectionID, &iter.outPtr());
74 size_t targetCount = g_variant_iter_n_children(iter.get());
75 Vector<SessionHost::Target> targetList;
76 targetList.reserveInitialCapacity(targetCount);
77 guint64 targetID;
78 const char* type;
79 const char* name;
80 const char* dummy;
81 gboolean isPaired;
82 while (g_variant_iter_loop(iter.get(), "(t&s&s&sb)", &targetID, &type, &name, &dummy, &isPaired)) {
83 if (!g_strcmp0(type, "Automation"))
84 targetList.uncheckedAppend({ targetID, name, static_cast<bool>(isPaired) });
85 }
86 sessionHost->setTargetList(connectionID, WTFMove(targetList));
87 g_dbus_method_invocation_return_value(invocation, nullptr);
88 } else if (!g_strcmp0(methodName, "SendMessageToFrontend")) {
89 guint64 connectionID, targetID;
90 const char* message;
91 g_variant_get(parameters, "(tt&s)", &connectionID, &targetID, &message);
92 sessionHost->sendMessageToFrontend(connectionID, targetID, message);
93 g_dbus_method_invocation_return_value(invocation, nullptr);
94 }
95 },
96 // get_property
97 nullptr,
98 // set_property
99 nullptr,
100 // padding
101 { 0 }
102};
103
104void SessionHost::connectToBrowser(Function<void (Optional<String> error)>&& completionHandler)
105{
106 launchBrowser(WTFMove(completionHandler));
107}
108
109bool SessionHost::isConnected() const
110{
111 // Session is connected when launching or when dbus connection hasn't been closed.
112 return m_browser && (!m_dbusConnection || !g_dbus_connection_is_closed(m_dbusConnection.get()));
113}
114
115struct ConnectToBrowserAsyncData {
116 ConnectToBrowserAsyncData(SessionHost* sessionHost, GUniquePtr<char>&& dbusAddress, GCancellable* cancellable, Function<void (Optional<String> error)>&& completionHandler)
117 : sessionHost(sessionHost)
118 , dbusAddress(WTFMove(dbusAddress))
119 , cancellable(cancellable)
120 , completionHandler(WTFMove(completionHandler))
121 {
122 }
123
124 SessionHost* sessionHost;
125 GUniquePtr<char> dbusAddress;
126 GRefPtr<GCancellable> cancellable;
127 Function<void (Optional<String> error)> completionHandler;
128};
129
130static guint16 freePort()
131{
132 GRefPtr<GSocket> socket = adoptGRef(g_socket_new(G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_DEFAULT, nullptr));
133 GRefPtr<GInetAddress> loopbackAdress = adoptGRef(g_inet_address_new_loopback(G_SOCKET_FAMILY_IPV4));
134 GRefPtr<GSocketAddress> address = adoptGRef(g_inet_socket_address_new(loopbackAdress.get(), 0));
135 g_socket_bind(socket.get(), address.get(), FALSE, nullptr);
136 g_socket_listen(socket.get(), nullptr);
137 address = adoptGRef(g_socket_get_local_address(socket.get(), nullptr));
138 g_socket_close(socket.get(), nullptr);
139 return g_inet_socket_address_get_port(G_INET_SOCKET_ADDRESS(address.get()));
140}
141
142void SessionHost::launchBrowser(Function<void (Optional<String> error)>&& completionHandler)
143{
144 m_cancellable = adoptGRef(g_cancellable_new());
145 GRefPtr<GSubprocessLauncher> launcher = adoptGRef(g_subprocess_launcher_new(G_SUBPROCESS_FLAGS_NONE));
146 guint16 port = freePort();
147 GUniquePtr<char> inspectorAddress(g_strdup_printf("127.0.0.1:%u", port));
148 g_subprocess_launcher_setenv(launcher.get(), "WEBKIT_INSPECTOR_SERVER", inspectorAddress.get(), TRUE);
149#if PLATFORM(GTK)
150 g_subprocess_launcher_setenv(launcher.get(), "GTK_OVERLAY_SCROLLING", m_capabilities.useOverlayScrollbars.value() ? "1" : "0", TRUE);
151#endif
152
153 size_t browserArgumentsSize = m_capabilities.browserArguments ? m_capabilities.browserArguments->size() : 0;
154 GUniquePtr<char*> args(g_new0(char*, browserArgumentsSize + 2));
155 args.get()[0] = g_strdup(m_capabilities.browserBinary.value().utf8().data());
156 for (unsigned i = 0; i < browserArgumentsSize; ++i)
157 args.get()[i + 1] = g_strdup(m_capabilities.browserArguments.value()[i].utf8().data());
158
159 GUniqueOutPtr<GError> error;
160 m_browser = adoptGRef(g_subprocess_launcher_spawnv(launcher.get(), args.get(), &error.outPtr()));
161 if (error) {
162 completionHandler(String::fromUTF8(error->message));
163 return;
164 }
165
166 g_subprocess_wait_async(m_browser.get(), m_cancellable.get(), [](GObject* browser, GAsyncResult* result, gpointer userData) {
167 GUniqueOutPtr<GError> error;
168 g_subprocess_wait_finish(G_SUBPROCESS(browser), result, &error.outPtr());
169 if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
170 return;
171 auto* sessionHost = static_cast<SessionHost*>(userData);
172 sessionHost->m_browser = nullptr;
173 }, this);
174
175 GUniquePtr<char> dbusAddress(g_strdup_printf("tcp:host=%s,port=%u", "127.0.0.1", port));
176 connectToBrowser(std::make_unique<ConnectToBrowserAsyncData>(this, WTFMove(dbusAddress), m_cancellable.get(), WTFMove(completionHandler)));
177}
178
179void SessionHost::connectToBrowser(std::unique_ptr<ConnectToBrowserAsyncData>&& data)
180{
181 if (!m_browser)
182 return;
183
184 RunLoop::main().dispatchAfter(100_ms, [connectToBrowserData = WTFMove(data)]() mutable {
185 auto* data = connectToBrowserData.release();
186 if (g_cancellable_is_cancelled(data->cancellable.get()))
187 return;
188
189 g_dbus_connection_new_for_address(data->dbusAddress.get(), G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, nullptr, data->cancellable.get(),
190 [](GObject*, GAsyncResult* result, gpointer userData) {
191 auto data = std::unique_ptr<ConnectToBrowserAsyncData>(static_cast<ConnectToBrowserAsyncData*>(userData));
192 GUniqueOutPtr<GError> error;
193 GRefPtr<GDBusConnection> connection = adoptGRef(g_dbus_connection_new_for_address_finish(result, &error.outPtr()));
194 if (!connection) {
195 if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
196 return;
197
198 if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED)) {
199 data->sessionHost->connectToBrowser(WTFMove(data));
200 return;
201 }
202
203 data->completionHandler(String::fromUTF8(error->message));
204 return;
205 }
206 data->sessionHost->setupConnection(WTFMove(connection));
207 data->completionHandler(WTF::nullopt);
208 }, data);
209 });
210}
211
212void SessionHost::dbusConnectionClosedCallback(SessionHost* sessionHost)
213{
214 sessionHost->m_browser = nullptr;
215 sessionHost->inspectorDisconnected();
216}
217
218static void dbusConnectionCallAsyncReadyCallback(GObject* source, GAsyncResult* result, gpointer)
219{
220 GUniqueOutPtr<GError> error;
221 GRefPtr<GVariant> resultVariant = adoptGRef(g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error.outPtr()));
222 if (!resultVariant && !g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
223 WTFLogAlways("RemoteInspectorServer failed to send DBus message: %s", error->message);
224}
225
226void SessionHost::setupConnection(GRefPtr<GDBusConnection>&& connection)
227{
228 ASSERT(!m_dbusConnection);
229 ASSERT(connection);
230 m_dbusConnection = WTFMove(connection);
231
232 g_signal_connect_swapped(m_dbusConnection.get(), "closed", G_CALLBACK(dbusConnectionClosedCallback), this);
233
234 static GDBusNodeInfo* introspectionData = nullptr;
235 if (!introspectionData)
236 introspectionData = g_dbus_node_info_new_for_xml(introspectionXML, nullptr);
237
238 g_dbus_connection_register_object(m_dbusConnection.get(), REMOTE_INSPECTOR_CLIENT_OBJECT_PATH, introspectionData->interfaces[0], &s_interfaceVTable, this, nullptr, nullptr);
239}
240
241static bool matchBrowserOptions(const String& browserName, const String& browserVersion, const Capabilities& capabilities)
242{
243 if (capabilities.browserName && capabilities.browserName.value() != browserName)
244 return false;
245
246 if (capabilities.browserVersion && !WebDriverService::platformCompareBrowserVersions(capabilities.browserVersion.value(), browserVersion))
247 return false;
248
249 return true;
250}
251
252bool SessionHost::matchCapabilities(GVariant* capabilities)
253{
254 const char* name;
255 const char* version;
256 g_variant_get(capabilities, "(&s&s)", &name, &version);
257
258 auto browserName = String::fromUTF8(name);
259 auto browserVersion = String::fromUTF8(version);
260 bool didMatch = matchBrowserOptions(browserName, browserVersion, m_capabilities);
261 m_capabilities.browserName = browserName;
262 m_capabilities.browserVersion = browserVersion;
263
264 return didMatch;
265}
266
267bool SessionHost::buildSessionCapabilities(GVariantBuilder* builder) const
268{
269 if (!m_capabilities.acceptInsecureCerts && !m_capabilities.certificates)
270 return false;
271
272 g_variant_builder_init(builder, G_VARIANT_TYPE("a{sv}"));
273 if (m_capabilities.acceptInsecureCerts)
274 g_variant_builder_add(builder, "{sv}", "acceptInsecureCerts", g_variant_new_boolean(m_capabilities.acceptInsecureCerts.value()));
275
276 if (m_capabilities.certificates) {
277 GVariantBuilder arrayBuilder;
278 g_variant_builder_init(&arrayBuilder, G_VARIANT_TYPE("a(ss)"));
279 for (auto& certificate : *m_capabilities.certificates) {
280 g_variant_builder_add_value(&arrayBuilder, g_variant_new("(ss)",
281 certificate.first.utf8().data(), certificate.second.utf8().data()));
282 }
283 g_variant_builder_add(builder, "{sv}", "certificates", g_variant_builder_end(&arrayBuilder));
284 }
285
286 return true;
287}
288
289void SessionHost::startAutomationSession(Function<void (bool, Optional<String>)>&& completionHandler)
290{
291 ASSERT(m_dbusConnection);
292 ASSERT(!m_startSessionCompletionHandler);
293 m_startSessionCompletionHandler = WTFMove(completionHandler);
294 m_sessionID = createCanonicalUUIDString();
295 GVariantBuilder builder;
296 g_dbus_connection_call(m_dbusConnection.get(), nullptr,
297 INSPECTOR_DBUS_OBJECT_PATH,
298 INSPECTOR_DBUS_INTERFACE,
299 "StartAutomationSession",
300 g_variant_new("(sa{sv})", m_sessionID.utf8().data(), buildSessionCapabilities(&builder) ? &builder : nullptr),
301 nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START,
302 -1, m_cancellable.get(), [](GObject* source, GAsyncResult* result, gpointer userData) {
303 GUniqueOutPtr<GError> error;
304 GRefPtr<GVariant> resultVariant = adoptGRef(g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error.outPtr()));
305 if (!resultVariant && g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
306 return;
307
308 auto sessionHost = static_cast<SessionHost*>(userData);
309 if (!resultVariant) {
310 auto completionHandler = std::exchange(sessionHost->m_startSessionCompletionHandler, nullptr);
311 completionHandler(false, makeString("Failed to start automation session: ", String::fromUTF8(error->message)));
312 return;
313 }
314
315 if (!sessionHost->matchCapabilities(resultVariant.get())) {
316 auto completionHandler = std::exchange(sessionHost->m_startSessionCompletionHandler, nullptr);
317 completionHandler(false, WTF::nullopt);
318 return;
319 }
320 }, this
321 );
322}
323
324void SessionHost::setTargetList(uint64_t connectionID, Vector<Target>&& targetList)
325{
326 // The server notifies all its clients when connection is lost by sending an empty target list.
327 // We only care about automation connection.
328 if (m_connectionID && m_connectionID != connectionID)
329 return;
330
331 ASSERT(targetList.size() <= 1);
332 if (targetList.isEmpty()) {
333 m_target = Target();
334 if (m_connectionID) {
335 if (m_dbusConnection)
336 g_dbus_connection_close(m_dbusConnection.get(), nullptr, nullptr, nullptr);
337 m_connectionID = 0;
338 }
339 return;
340 }
341
342 m_target = targetList[0];
343 if (m_connectionID) {
344 ASSERT(m_connectionID == connectionID);
345 return;
346 }
347
348 if (!m_startSessionCompletionHandler) {
349 // Session creation was already rejected.
350 return;
351 }
352
353 m_connectionID = connectionID;
354 g_dbus_connection_call(m_dbusConnection.get(), nullptr,
355 INSPECTOR_DBUS_OBJECT_PATH,
356 INSPECTOR_DBUS_INTERFACE,
357 "Setup",
358 g_variant_new("(tt)", m_connectionID, m_target.id),
359 nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START,
360 -1, m_cancellable.get(), dbusConnectionCallAsyncReadyCallback, nullptr);
361
362 auto startSessionCompletionHandler = std::exchange(m_startSessionCompletionHandler, nullptr);
363 startSessionCompletionHandler(true, WTF::nullopt);
364}
365
366void SessionHost::sendMessageToFrontend(uint64_t connectionID, uint64_t targetID, const char* message)
367{
368 if (connectionID != m_connectionID || targetID != m_target.id)
369 return;
370 dispatchMessage(String::fromUTF8(message));
371}
372
373struct MessageContext {
374 long messageID;
375 SessionHost* host;
376};
377
378void SessionHost::sendMessageToBackend(long messageID, const String& message)
379{
380 ASSERT(m_dbusConnection);
381 ASSERT(m_connectionID);
382 ASSERT(m_target.id);
383
384 auto messageContext = std::make_unique<MessageContext>(MessageContext { messageID, this });
385 g_dbus_connection_call(m_dbusConnection.get(), nullptr,
386 INSPECTOR_DBUS_OBJECT_PATH,
387 INSPECTOR_DBUS_INTERFACE,
388 "SendMessageToBackend",
389 g_variant_new("(tts)", m_connectionID, m_target.id, message.utf8().data()),
390 nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START,
391 -1, m_cancellable.get(), [](GObject* source, GAsyncResult* result, gpointer userData) {
392 auto messageContext = std::unique_ptr<MessageContext>(static_cast<MessageContext*>(userData));
393 GUniqueOutPtr<GError> error;
394 GRefPtr<GVariant> resultVariant = adoptGRef(g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error.outPtr()));
395 if (!resultVariant && !g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
396 auto responseHandler = messageContext->host->m_commandRequests.take(messageContext->messageID);
397 if (responseHandler) {
398 auto errorObject = JSON::Object::create();
399 errorObject->setInteger("code"_s, -32603);
400 errorObject->setString("message"_s, String::fromUTF8(error->message));
401 responseHandler({ WTFMove(errorObject), true });
402 }
403 }
404 }, messageContext.release());
405}
406
407} // namespace WebDriver
408