[GLIB] Make remote inspector DBus protocol common to all glib based ports
[WebKit.git] / Source / JavaScriptCore / inspector / remote / glib / RemoteInspectorServer.cpp
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 COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "RemoteInspectorServer.h"
28
29 #if ENABLE(REMOTE_INSPECTOR)
30
31 #include "RemoteInspector.h"
32 #include "RemoteInspectorUtils.h"
33 #include <gio/gio.h>
34 #include <wtf/Vector.h>
35 #include <wtf/glib/GUniquePtr.h>
36
37 #define INSPECTOR_DBUS_INTERFACE "org.webkit.Inspector"
38 #define INSPECTOR_DBUS_OBJECT_PATH "/org/webkit/Inspector"
39 #define REMOTE_INSPECTOR_DBUS_INTERFACE "org.webkit.RemoteInspector"
40 #define REMOTE_INSPECTOR_DBUS_OBJECT_PATH "/org/webkit/RemoteInspector"
41 #define REMOTE_INSPECTOR_CLIENT_DBUS_INTERFACE "org.webkit.RemoteInspectorClient"
42 #define REMOTE_INSPECTOR_CLIENT_OBJECT_PATH "/org/webkit/RemoteInspectorClient"
43
44 namespace Inspector {
45
46 static uint64_t generateConnectionID()
47 {
48     static uint64_t connectionID = 0;
49     return ++connectionID;
50 }
51
52 static const char introspectionXML[] =
53     "<node>"
54     "  <interface name='" INSPECTOR_DBUS_INTERFACE "'>"
55     "    <method name='SetTargetList'>"
56     "      <arg type='a(tsssb)' name='list' direction='in'/>"
57     "      <arg type='b' name='remoteAutomationEnabled' direction='in'/>"
58     "    </method>"
59     "    <method name='SetupInspectorClient'>"
60     "      <arg type='ay' name='backendCommandsHash' direction='in'/>"
61     "      <arg type='ay' name='backendCommands' direction='out'/>"
62     "    </method>"
63     "    <method name='Setup'>"
64     "      <arg type='t' name='connection' direction='in'/>"
65     "      <arg type='t' name='target' direction='in'/>"
66     "    </method>"
67     "    <method name='FrontendDidClose'>"
68     "      <arg type='t' name='connection' direction='in'/>"
69     "      <arg type='t' name='target' direction='in'/>"
70     "    </method>"
71     "    <method name='SendMessageToFrontend'>"
72     "      <arg type='t' name='target' direction='in'/>"
73     "      <arg type='s' name='message' direction='in'/>"
74     "    </method>"
75     "    <method name='SendMessageToBackend'>"
76     "      <arg type='t' name='connection' direction='in'/>"
77     "      <arg type='t' name='target' direction='in'/>"
78     "      <arg type='s' name='message' direction='in'/>"
79     "    </method>"
80     "    <method name='StartAutomationSession'>"
81     "      <arg type='s' name='sessionID' direction='in'/>"
82     "    </method>"
83     "  </interface>"
84     "</node>";
85
86 const GDBusInterfaceVTable RemoteInspectorServer::s_interfaceVTable = {
87     // method_call
88     [] (GDBusConnection* connection, const gchar* /*sender*/, const gchar* /*objectPath*/, const gchar* /*interfaceName*/, const gchar* methodName, GVariant* parameters, GDBusMethodInvocation* invocation, gpointer userData) {
89         auto* inspectorServer = static_cast<RemoteInspectorServer*>(userData);
90         if (!g_strcmp0(methodName, "SetTargetList")) {
91             inspectorServer->setTargetList(connection, parameters);
92             g_dbus_method_invocation_return_value(invocation, nullptr);
93         } else if (!g_strcmp0(methodName, "SetupInspectorClient")) {
94             GRefPtr<GVariant> backendCommandsHash;
95             g_variant_get(parameters, "(@ay)", &backendCommandsHash.outPtr());
96             auto* backendCommands = inspectorServer->setupInspectorClient(connection, g_variant_get_bytestring(backendCommandsHash.get()));
97             g_dbus_method_invocation_return_value(invocation, g_variant_new("(@ay)", backendCommands));
98         } else if (!g_strcmp0(methodName, "Setup")) {
99             guint64 connectionID, targetID;
100             g_variant_get(parameters, "(tt)", &connectionID, &targetID);
101             inspectorServer->setup(connection, connectionID, targetID);
102             g_dbus_method_invocation_return_value(invocation, nullptr);
103         } else if (!g_strcmp0(methodName, "FrontendDidClose")) {
104             guint64 connectionID, targetID;
105             g_variant_get(parameters, "(tt)", &connectionID, &targetID);
106             inspectorServer->close(connection, connectionID, targetID);
107             g_dbus_method_invocation_return_value(invocation, nullptr);
108         } else if (!g_strcmp0(methodName, "SendMessageToFrontend")) {
109             guint64 targetID;
110             const char* message;
111             g_variant_get(parameters, "(t&s)", &targetID, &message);
112             inspectorServer->sendMessageToFrontend(connection, targetID, message);
113             g_dbus_method_invocation_return_value(invocation, nullptr);
114         } else if (!g_strcmp0(methodName, "SendMessageToBackend")) {
115             guint64 connectionID, targetID;
116             const char* message;
117             g_variant_get(parameters, "(tt&s)", &connectionID, &targetID, &message);
118             inspectorServer->sendMessageToBackend(connection, connectionID, targetID, message);
119             g_dbus_method_invocation_return_value(invocation, nullptr);
120         } else if (!g_strcmp0(methodName, "StartAutomationSession")) {
121             const char* sessionID;
122             g_variant_get(parameters, "(&s)", &sessionID);
123             inspectorServer->startAutomationSession(connection, sessionID);
124             g_dbus_method_invocation_return_value(invocation, nullptr);
125         } else
126             g_dbus_method_invocation_return_value(invocation, nullptr);
127     },
128     // get_property
129     nullptr,
130     // set_property
131     nullptr,
132     // padding
133     nullptr
134 };
135
136 RemoteInspectorServer& RemoteInspectorServer::singleton()
137 {
138     static RemoteInspectorServer server;
139     return server;
140 }
141
142 RemoteInspectorServer::~RemoteInspectorServer()
143 {
144     g_cancellable_cancel(m_cancellable.get());
145     if (m_dbusServer)
146         g_dbus_server_stop(m_dbusServer.get());
147     if (m_introspectionData)
148         g_dbus_node_info_unref(m_introspectionData);
149 }
150
151 GDBusInterfaceInfo* RemoteInspectorServer::interfaceInfo()
152 {
153     if (!m_introspectionData) {
154         m_introspectionData = g_dbus_node_info_new_for_xml(introspectionXML, nullptr);
155         ASSERT(m_introspectionData);
156     }
157     return m_introspectionData->interfaces[0];
158 }
159
160 bool RemoteInspectorServer::start(const char* address, unsigned port)
161 {
162     GUniquePtr<char> dbusAddress(g_strdup_printf("tcp:host=%s,port=%u", address, port));
163     GUniquePtr<char> uid(g_dbus_generate_guid());
164
165     m_cancellable = adoptGRef(g_cancellable_new());
166
167     GUniqueOutPtr<GError> error;
168     m_dbusServer = adoptGRef(g_dbus_server_new_sync(dbusAddress.get(), G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS, uid.get(), nullptr, m_cancellable.get(), &error.outPtr()));
169     if (!m_dbusServer) {
170         if (!g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
171             WTFLogAlways("Failed to start remote inspector server on %s: %s\n", dbusAddress.get(), error->message);
172         return false;
173     }
174
175     g_signal_connect(m_dbusServer.get(), "new-connection", G_CALLBACK(newConnectionCallback), this);
176     g_dbus_server_start(m_dbusServer.get());
177
178     return true;
179 }
180
181 gboolean RemoteInspectorServer::newConnectionCallback(GDBusServer*, GDBusConnection* connection, RemoteInspectorServer* inspectorServer)
182 {
183     inspectorServer->newConnection(connection);
184     return TRUE;
185 }
186
187 void RemoteInspectorServer::connectionClosedCallback(GDBusConnection* connection, gboolean /*remotePeerVanished*/, GError*, RemoteInspectorServer* server)
188 {
189     server->connectionClosed(connection);
190 }
191
192 void RemoteInspectorServer::newConnection(GDBusConnection* connection)
193 {
194     ASSERT(!m_connections.contains(connection));
195     m_connections.add(connection);
196     g_signal_connect(connection, "closed", G_CALLBACK(connectionClosedCallback), this);
197
198     g_dbus_connection_register_object(connection, INSPECTOR_DBUS_OBJECT_PATH, interfaceInfo(), &s_interfaceVTable, this, nullptr, nullptr);
199 }
200
201 static void dbusConnectionCallAsyncReadyCallback(GObject* source, GAsyncResult* result, gpointer)
202 {
203     GUniqueOutPtr<GError> error;
204     GRefPtr<GVariant> resultVariant = adoptGRef(g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error.outPtr()));
205     if (!resultVariant && !g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
206         WTFLogAlways("RemoteInspectorServer failed to send DBus message: %s", error->message);
207 }
208
209 void RemoteInspectorServer::setTargetList(GDBusConnection* remoteInspectorConnection, GVariant* parameters)
210 {
211     ASSERT(m_connections.contains(remoteInspectorConnection));
212     auto addResult = m_remoteInspectorConnectionToIDMap.add(remoteInspectorConnection, 0);
213     if (addResult.isNewEntry) {
214         addResult.iterator->value = generateConnectionID();
215         m_idToRemoteInspectorConnectionMap.add(addResult.iterator->value, remoteInspectorConnection);
216     }
217
218     gboolean remoteAutomationEnabled;
219     GRefPtr<GVariant> targetList;
220     g_variant_get(parameters, "(@a(tsssb)b)", &targetList.outPtr(), &remoteAutomationEnabled);
221     GDBusConnection* clientConnection = remoteAutomationEnabled && m_automationConnection ? m_automationConnection.get() : m_clientConnection.get();
222     if (!clientConnection)
223         return;
224
225     g_dbus_connection_call(clientConnection, nullptr,
226         REMOTE_INSPECTOR_CLIENT_OBJECT_PATH,
227         REMOTE_INSPECTOR_CLIENT_DBUS_INTERFACE,
228         "SetTargetList",
229         g_variant_new("(t@a(tsssb))", m_remoteInspectorConnectionToIDMap.get(remoteInspectorConnection), targetList.get()),
230         nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START,
231         -1, m_cancellable.get(), dbusConnectionCallAsyncReadyCallback, nullptr);
232 }
233
234 void RemoteInspectorServer::clientConnectionClosedCallback(GDBusConnection* connection, gboolean /*remotePeerVanished*/, GError*, RemoteInspectorServer* server)
235 {
236     server->clientConnectionClosed(connection);
237 }
238
239 GVariant* RemoteInspectorServer::setupInspectorClient(GDBusConnection* clientConnection, const char* clientBackendCommandsHash)
240 {
241     ASSERT(!m_clientConnection);
242     m_clientConnection = clientConnection;
243     g_signal_connect(m_clientConnection.get(), "closed", G_CALLBACK(clientConnectionClosedCallback), this);
244
245     GVariant* backendCommands;
246     if (strcmp(clientBackendCommandsHash, backendCommandsHash().data())) {
247         auto bytes = Inspector::backendCommands();
248         backendCommands = g_variant_new_bytestring(static_cast<const char*>(g_bytes_get_data(bytes.get(), nullptr)));
249     } else
250         backendCommands = g_variant_new_bytestring("");
251
252     // Ask all remote inspectors to push their target lists to notify the new client.
253     for (auto* remoteInspectorConnection : m_remoteInspectorConnectionToIDMap.keys()) {
254         g_dbus_connection_call(remoteInspectorConnection, nullptr,
255             REMOTE_INSPECTOR_DBUS_OBJECT_PATH,
256             REMOTE_INSPECTOR_DBUS_INTERFACE,
257             "GetTargetList",
258             nullptr,
259             nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START,
260             -1, m_cancellable.get(), dbusConnectionCallAsyncReadyCallback, nullptr);
261     }
262
263     return backendCommands;
264 }
265
266 void RemoteInspectorServer::setup(GDBusConnection* clientConnection, uint64_t connectionID, uint64_t targetID)
267 {
268     ASSERT(m_clientConnection.get() == clientConnection || m_automationConnection.get() == clientConnection);
269     ASSERT(m_idToRemoteInspectorConnectionMap.contains(connectionID));
270     if (clientConnection == m_automationConnection.get()) {
271         m_automationTargets.add(std::make_pair(connectionID, targetID));
272         RemoteInspector::singleton().setup(targetID);
273         return;
274     }
275
276     m_inspectionTargets.add(std::make_pair(connectionID, targetID));
277     auto* remoteInspectorConnection = m_idToRemoteInspectorConnectionMap.get(connectionID);
278     g_dbus_connection_call(remoteInspectorConnection, nullptr,
279         REMOTE_INSPECTOR_DBUS_OBJECT_PATH,
280         REMOTE_INSPECTOR_DBUS_INTERFACE,
281         "Setup",
282         g_variant_new("(t)", targetID),
283         nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START,
284         -1, m_cancellable.get(), dbusConnectionCallAsyncReadyCallback, nullptr);
285 }
286
287 void RemoteInspectorServer::close(GDBusConnection* clientConnection, uint64_t connectionID, uint64_t targetID)
288 {
289     ASSERT(m_clientConnection.get() == clientConnection || m_automationConnection.get() == clientConnection);
290     ASSERT(m_idToRemoteInspectorConnectionMap.contains(connectionID));
291     if (clientConnection == m_automationConnection.get()) {
292         // FIXME: automation.
293         return;
294     }
295
296     ASSERT(m_inspectionTargets.contains(std::make_pair(connectionID, targetID)));
297     auto* remoteInspectorConnection = m_idToRemoteInspectorConnectionMap.get(connectionID);
298     g_dbus_connection_call(remoteInspectorConnection, nullptr,
299         REMOTE_INSPECTOR_DBUS_OBJECT_PATH,
300         REMOTE_INSPECTOR_DBUS_INTERFACE,
301         "FrontendDidClose",
302         g_variant_new("(t)", targetID),
303         nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START,
304         -1, m_cancellable.get(), dbusConnectionCallAsyncReadyCallback, nullptr);
305     m_inspectionTargets.remove(std::make_pair(connectionID, targetID));
306 }
307
308 void RemoteInspectorServer::clientConnectionClosed(GDBusConnection* clientConnection)
309 {
310     ASSERT(m_clientConnection.get() == clientConnection || m_automationConnection.get() == clientConnection);
311     if (clientConnection == m_automationConnection.get()) {
312         for (auto connectionTargetPair : m_automationTargets)
313             close(clientConnection, connectionTargetPair.first, connectionTargetPair.second);
314         m_automationConnection = nullptr;
315         return;
316     }
317
318     for (auto connectionTargetPair : m_inspectionTargets)
319         close(clientConnection, connectionTargetPair.first, connectionTargetPair.second);
320     m_clientConnection = nullptr;
321 }
322
323 void RemoteInspectorServer::connectionClosed(GDBusConnection* remoteInspectorConnection)
324 {
325     ASSERT(m_connections.contains(remoteInspectorConnection));
326     if (m_remoteInspectorConnectionToIDMap.contains(remoteInspectorConnection)) {
327         uint64_t connectionID = m_remoteInspectorConnectionToIDMap.take(remoteInspectorConnection);
328         m_idToRemoteInspectorConnectionMap.remove(connectionID);
329         // Send an empty target list to the clients.
330         Vector<GRefPtr<GDBusConnection>> clientConnections = { m_automationConnection, m_clientConnection };
331         for (auto& clientConnection : clientConnections) {
332             if (!clientConnection)
333                 continue;
334             g_dbus_connection_call(clientConnection.get(), nullptr,
335                 REMOTE_INSPECTOR_CLIENT_OBJECT_PATH,
336                 REMOTE_INSPECTOR_CLIENT_DBUS_INTERFACE,
337                 "SetTargetList",
338                 g_variant_new("(t@a(tsssb))", connectionID, g_variant_new_array(G_VARIANT_TYPE("(tsssb)"), nullptr, 0)),
339                 nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START,
340                 -1, m_cancellable.get(), dbusConnectionCallAsyncReadyCallback, nullptr);
341         }
342     }
343     m_connections.remove(remoteInspectorConnection);
344 }
345
346 void RemoteInspectorServer::sendMessageToBackend(GDBusConnection* clientConnection, uint64_t connectionID, uint64_t targetID, const char* message)
347 {
348     ASSERT(m_clientConnection.get() == clientConnection || m_automationConnection.get() == clientConnection);
349     ASSERT(m_idToRemoteInspectorConnectionMap.contains(connectionID));
350     if (clientConnection == m_automationConnection.get()) {
351         RemoteInspector::singleton().sendMessageToTarget(targetID, message);
352         return;
353     }
354
355     auto* remoteInspectorConnection = m_idToRemoteInspectorConnectionMap.get(connectionID);
356     g_dbus_connection_call(remoteInspectorConnection, nullptr,
357         REMOTE_INSPECTOR_DBUS_OBJECT_PATH,
358         REMOTE_INSPECTOR_DBUS_INTERFACE,
359         "SendMessageToTarget",
360         g_variant_new("(t&s)", targetID, message),
361         nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START,
362         -1, m_cancellable.get(), dbusConnectionCallAsyncReadyCallback, nullptr);
363 }
364
365 void RemoteInspectorServer::sendMessageToFrontend(GDBusConnection* remoteInspectorConnection, uint64_t targetID, const char* message)
366 {
367     ASSERT(m_connections.contains(remoteInspectorConnection));
368     ASSERT(m_remoteInspectorConnectionToIDMap.contains(remoteInspectorConnection));
369
370     uint64_t connectionID = m_remoteInspectorConnectionToIDMap.get(remoteInspectorConnection);
371     auto connectionTargetPair = std::make_pair(connectionID, targetID);
372     ASSERT(m_automationTargets.contains(connectionTargetPair) || m_inspectionTargets.contains(connectionTargetPair));
373     GDBusConnection* clientConnection = m_inspectionTargets.contains(connectionTargetPair) ? m_clientConnection.get() : m_automationConnection.get();
374     ASSERT(clientConnection);
375
376     g_dbus_connection_call(clientConnection, nullptr,
377         REMOTE_INSPECTOR_CLIENT_OBJECT_PATH,
378         REMOTE_INSPECTOR_CLIENT_DBUS_INTERFACE,
379         "SendMessageToFrontend",
380         g_variant_new("(tt&s)", m_remoteInspectorConnectionToIDMap.get(remoteInspectorConnection), targetID, message),
381         nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START,
382         -1, m_cancellable.get(), dbusConnectionCallAsyncReadyCallback, nullptr);
383 }
384
385 void RemoteInspectorServer::startAutomationSession(GDBusConnection* automationConnection, const char* sessionID)
386 {
387     if (!m_automationConnection)
388         m_automationConnection = automationConnection;
389     ASSERT(m_automationConnection.get() == automationConnection);
390
391     RemoteInspector::singleton().requestAutomationSession(sessionID);
392 }
393
394 } // namespace Inspector
395
396 #endif // ENABLE(REMOTE_INSPECTOR)