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 "RemoteInspectorClient.h"
28
29#if ENABLE(REMOTE_INSPECTOR)
30
31#include "RemoteWebInspectorProxy.h"
32#include <JavaScriptCore/RemoteInspectorUtils.h>
33#include <gio/gio.h>
34#include <wtf/glib/GUniquePtr.h>
35#include <wtf/text/Base64.h>
36
37#define REMOTE_INSPECTOR_CLIENT_DBUS_INTERFACE "org.webkit.RemoteInspectorClient"
38#define REMOTE_INSPECTOR_CLIENT_OBJECT_PATH "/org/webkit/RemoteInspectorClient"
39#define INSPECTOR_DBUS_INTERFACE "org.webkit.Inspector"
40#define INSPECTOR_DBUS_OBJECT_PATH "/org/webkit/Inspector"
41
42namespace WebKit {
43
44class RemoteInspectorProxy final : public RemoteWebInspectorProxyClient {
45 WTF_MAKE_FAST_ALLOCATED();
46public:
47 RemoteInspectorProxy(RemoteInspectorClient& inspectorClient, uint64_t connectionID, uint64_t targetID)
48 : m_proxy(RemoteWebInspectorProxy::create())
49 , m_inspectorClient(inspectorClient)
50 , m_connectionID(connectionID)
51 , m_targetID(targetID)
52 {
53 m_proxy->setClient(this);
54 }
55
56 ~RemoteInspectorProxy()
57 {
58 m_proxy->setClient(nullptr);
59 m_proxy->invalidate();
60 }
61
62 void load()
63 {
64 m_proxy->load("web", m_inspectorClient.backendCommandsURL());
65 }
66
67 void show()
68 {
69 m_proxy->show();
70 }
71
72 void setTargetName(const CString& name)
73 {
74#if PLATFORM(GTK)
75 m_proxy->updateWindowTitle(name);
76#endif
77 }
78
79 void sendMessageToFrontend(const String& message)
80 {
81 m_proxy->sendMessageToFrontend(message);
82 }
83
84 void sendMessageToBackend(const String& message) override
85 {
86 m_inspectorClient.sendMessageToBackend(m_connectionID, m_targetID, message);
87 }
88
89 void closeFromFrontend() override
90 {
91 m_inspectorClient.closeFromFrontend(m_connectionID, m_targetID);
92 }
93
94private:
95 Ref<RemoteWebInspectorProxy> m_proxy;
96 RemoteInspectorClient& m_inspectorClient;
97 uint64_t m_connectionID;
98 uint64_t m_targetID;
99};
100
101static const char introspectionXML[] =
102 "<node>"
103 " <interface name='" REMOTE_INSPECTOR_CLIENT_DBUS_INTERFACE "'>"
104 " <method name='SetTargetList'>"
105 " <arg type='t' name='connectionID' direction='in'/>"
106 " <arg type='a(tsssb)' name='list' direction='in'/>"
107 " </method>"
108 " <method name='SendMessageToFrontend'>"
109 " <arg type='t' name='connectionID' direction='in'/>"
110 " <arg type='t' name='target' direction='in'/>"
111 " <arg type='s' name='message' direction='in'/>"
112 " </method>"
113 " </interface>"
114 "</node>";
115
116const GDBusInterfaceVTable RemoteInspectorClient::s_interfaceVTable = {
117 // method_call
118 [](GDBusConnection* connection, const gchar* sender, const gchar* objectPath, const gchar* interfaceName, const gchar* methodName, GVariant* parameters, GDBusMethodInvocation* invocation, gpointer userData) {
119 auto* client = static_cast<RemoteInspectorClient*>(userData);
120 if (!g_strcmp0(methodName, "SetTargetList")) {
121 guint64 connectionID;
122 GUniqueOutPtr<GVariantIter> iter;
123 g_variant_get(parameters, "(ta(tsssb))", &connectionID, &iter.outPtr());
124 size_t targetCount = g_variant_iter_n_children(iter.get());
125 Vector<RemoteInspectorClient::Target> targetList;
126 targetList.reserveInitialCapacity(targetCount);
127 guint64 targetID;
128 const char* type;
129 const char* name;
130 const char* url;
131 gboolean hasLocalDebugger;
132 while (g_variant_iter_loop(iter.get(), "(t&s&s&sb)", &targetID, &type, &name, &url, &hasLocalDebugger)) {
133 if (!g_strcmp0(type, "Web") || !g_strcmp0(type, "JavaScript"))
134 targetList.uncheckedAppend({ targetID, type, name, url });
135 }
136 client->setTargetList(connectionID, WTFMove(targetList));
137 g_dbus_method_invocation_return_value(invocation, nullptr);
138 } else if (!g_strcmp0(methodName, "SendMessageToFrontend")) {
139 guint64 connectionID, targetID;
140 const char* message;
141 g_variant_get(parameters, "(tt&s)", &connectionID, &targetID, &message);
142 client->sendMessageToFrontend(connectionID, targetID, message);
143 g_dbus_method_invocation_return_value(invocation, nullptr);
144 } else
145 g_dbus_method_invocation_return_value(invocation, nullptr);
146 },
147 // get_property
148 nullptr,
149 // set_property
150 nullptr,
151 // padding
152 { 0 }
153};
154
155RemoteInspectorClient::RemoteInspectorClient(const char* address, unsigned port, RemoteInspectorObserver& observer)
156 : m_hostAndPort(String::fromUTF8(address) + ':' + String::number(port))
157 , m_observer(observer)
158 , m_cancellable(adoptGRef(g_cancellable_new()))
159{
160 GUniquePtr<char> dbusAddress(g_strdup_printf("tcp:host=%s,port=%u", address, port));
161 g_dbus_connection_new_for_address(dbusAddress.get(), G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, nullptr, m_cancellable.get(),
162 [](GObject*, GAsyncResult* result, gpointer userData) {
163 auto* client = static_cast<RemoteInspectorClient*>(userData);
164 GUniqueOutPtr<GError> error;
165 GRefPtr<GDBusConnection> connection = adoptGRef(g_dbus_connection_new_for_address_finish(result, &error.outPtr()));
166 if (!connection && !g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
167 WTFLogAlways("RemoteInspectorClient failed to connect to inspector server: %s", error->message);
168 client->setupConnection(WTFMove(connection));
169 }, this);
170}
171
172RemoteInspectorClient::~RemoteInspectorClient()
173{
174}
175
176static void dbusConnectionCallAsyncReadyCallback(GObject* source, GAsyncResult* result, gpointer)
177{
178 GUniqueOutPtr<GError> error;
179 GRefPtr<GVariant> resultVariant = adoptGRef(g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error.outPtr()));
180 if (!resultVariant && !g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
181 WTFLogAlways("RemoteInspectorClient failed to send DBus message: %s", error->message);
182}
183
184void RemoteInspectorClient::connectionClosedCallback(GDBusConnection* connection, gboolean /*remotePeerVanished*/, GError*, RemoteInspectorClient* client)
185{
186 ASSERT_UNUSED(connection, client->m_dbusConnection.get() == connection);
187 client->connectionClosed();
188}
189
190void RemoteInspectorClient::setupConnection(GRefPtr<GDBusConnection>&& connection)
191{
192 m_dbusConnection = WTFMove(connection);
193 if (!m_dbusConnection) {
194 m_observer.connectionClosed(*this);
195 return;
196 }
197 g_signal_connect(m_dbusConnection.get(), "closed", G_CALLBACK(connectionClosedCallback), this);
198
199 static GDBusNodeInfo* introspectionData = nullptr;
200 if (!introspectionData)
201 introspectionData = g_dbus_node_info_new_for_xml(introspectionXML, nullptr);
202
203 g_dbus_connection_register_object(m_dbusConnection.get(), REMOTE_INSPECTOR_CLIENT_OBJECT_PATH, introspectionData->interfaces[0], &s_interfaceVTable, this, nullptr, nullptr);
204
205 g_dbus_connection_call(m_dbusConnection.get(), nullptr,
206 INSPECTOR_DBUS_OBJECT_PATH,
207 INSPECTOR_DBUS_INTERFACE,
208 "SetupInspectorClient",
209 g_variant_new("(@ay)", g_variant_new_bytestring(Inspector::backendCommandsHash().data())),
210 nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START,
211 -1, m_cancellable.get(), [](GObject* source, GAsyncResult* result, gpointer userData) {
212 GUniqueOutPtr<GError> error;
213 GRefPtr<GVariant> resultVariant = adoptGRef(g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error.outPtr()));
214 if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
215 return;
216 if (!resultVariant) {
217 WTFLogAlways("RemoteInspectorClient failed to send DBus message: %s", error->message);
218 return;
219 }
220
221 auto* client = static_cast<RemoteInspectorClient*>(userData);
222 GRefPtr<GVariant> backendCommandsVariant;
223 g_variant_get(resultVariant.get(), "(@ay)", &backendCommandsVariant.outPtr());
224 client->setBackendCommands(g_variant_get_bytestring(backendCommandsVariant.get()));
225 }, this);
226}
227
228void RemoteInspectorClient::setBackendCommands(const char* backendCommands)
229{
230 if (!backendCommands || !backendCommands[0]) {
231 m_backendCommandsURL = String();
232 return;
233 }
234
235 Vector<char> base64Data;
236 base64Encode(backendCommands, strlen(backendCommands), base64Data);
237 m_backendCommandsURL = "data:text/javascript;base64," + base64Data;
238}
239
240void RemoteInspectorClient::connectionClosed()
241{
242 g_cancellable_cancel(m_cancellable.get());
243 m_targets.clear();
244 m_inspectorProxyMap.clear();
245 m_dbusConnection = nullptr;
246 m_observer.connectionClosed(*this);
247}
248
249void RemoteInspectorClient::inspect(uint64_t connectionID, uint64_t targetID)
250{
251 auto addResult = m_inspectorProxyMap.ensure(std::make_pair(connectionID, targetID), [this, connectionID, targetID] {
252 return std::make_unique<RemoteInspectorProxy>(*this, connectionID, targetID);
253 });
254 if (!addResult.isNewEntry) {
255 addResult.iterator->value->show();
256 return;
257 }
258
259 g_dbus_connection_call(m_dbusConnection.get(), nullptr,
260 INSPECTOR_DBUS_OBJECT_PATH,
261 INSPECTOR_DBUS_INTERFACE,
262 "Setup",
263 g_variant_new("(tt)", connectionID, targetID),
264 nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START,
265 -1, m_cancellable.get(), dbusConnectionCallAsyncReadyCallback, nullptr);
266
267 addResult.iterator->value->load();
268}
269
270void RemoteInspectorClient::sendMessageToBackend(uint64_t connectionID, uint64_t targetID, const String& message)
271{
272 g_dbus_connection_call(m_dbusConnection.get(), nullptr,
273 INSPECTOR_DBUS_OBJECT_PATH,
274 INSPECTOR_DBUS_INTERFACE,
275 "SendMessageToBackend",
276 g_variant_new("(tts)", connectionID, targetID, message.utf8().data()),
277 nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START,
278 -1, m_cancellable.get(), dbusConnectionCallAsyncReadyCallback, nullptr);
279}
280
281void RemoteInspectorClient::closeFromFrontend(uint64_t connectionID, uint64_t targetID)
282{
283 ASSERT(m_inspectorProxyMap.contains(std::make_pair(connectionID, targetID)));
284 g_dbus_connection_call(m_dbusConnection.get(), nullptr,
285 INSPECTOR_DBUS_OBJECT_PATH,
286 INSPECTOR_DBUS_INTERFACE,
287 "FrontendDidClose",
288 g_variant_new("(tt)", connectionID, targetID),
289 nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START,
290 -1, m_cancellable.get(), dbusConnectionCallAsyncReadyCallback, nullptr);
291 m_inspectorProxyMap.remove(std::make_pair(connectionID, targetID));
292}
293
294void RemoteInspectorClient::setTargetList(uint64_t connectionID, Vector<Target>&& targetList)
295{
296 // Find closed targets to remove them.
297 Vector<uint64_t, 4> targetsToRemove;
298 for (auto& connectionTargetPair : m_inspectorProxyMap.keys()) {
299 if (connectionTargetPair.first != connectionID)
300 continue;
301 bool found = false;
302 for (const auto& target : targetList) {
303 if (target.id == connectionTargetPair.second) {
304 m_inspectorProxyMap.get(connectionTargetPair)->setTargetName(target.name);
305 found = true;
306 break;
307 }
308 }
309 if (!found)
310 targetsToRemove.append(connectionTargetPair.second);
311 }
312 for (auto targetID : targetsToRemove)
313 m_inspectorProxyMap.remove(std::make_pair(connectionID, targetID));
314
315 m_targets.set(connectionID, WTFMove(targetList));
316 m_observer.targetListChanged(*this);
317}
318
319void RemoteInspectorClient::sendMessageToFrontend(uint64_t connectionID, uint64_t targetID, const char* message)
320{
321 auto proxy = m_inspectorProxyMap.get(std::make_pair(connectionID, targetID));
322 ASSERT(proxy);
323 if (!proxy)
324 return;
325 proxy->sendMessageToFrontend(String::fromUTF8(message));
326}
327
328} // namespace WebKit
329
330#endif // ENABLE(REMOTE_INSPECTOR)
331