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 "RemoteInspector.h"
28
29#if ENABLE(REMOTE_INSPECTOR)
30
31#include "RemoteAutomationTarget.h"
32#include "RemoteConnectionToTarget.h"
33#include "RemoteInspectionTarget.h"
34#include <gio/gio.h>
35#include <wtf/NeverDestroyed.h>
36#include <wtf/RunLoop.h>
37#include <wtf/glib/GUniquePtr.h>
38
39#define REMOTE_INSPECTOR_DBUS_INTERFACE "org.webkit.RemoteInspector"
40#define REMOTE_INSPECTOR_DBUS_OBJECT_PATH "/org/webkit/RemoteInspector"
41#define INSPECTOR_DBUS_INTERFACE "org.webkit.Inspector"
42#define INSPECTOR_DBUS_OBJECT_PATH "/org/webkit/Inspector"
43
44namespace Inspector {
45
46RemoteInspector& RemoteInspector::singleton()
47{
48 static NeverDestroyed<RemoteInspector> shared;
49 return shared;
50}
51
52RemoteInspector::RemoteInspector()
53{
54 if (g_getenv("WEBKIT_INSPECTOR_SERVER"))
55 start();
56}
57
58void RemoteInspector::start()
59{
60 LockHolder lock(m_mutex);
61
62 if (m_enabled)
63 return;
64
65 m_enabled = true;
66 m_cancellable = adoptGRef(g_cancellable_new());
67
68 GUniquePtr<char> inspectorAddress(g_strdup(g_getenv("WEBKIT_INSPECTOR_SERVER")));
69 char* portPtr = g_strrstr(inspectorAddress.get(), ":");
70 ASSERT(portPtr);
71 *portPtr = '\0';
72 portPtr++;
73 unsigned port = g_ascii_strtoull(portPtr, nullptr, 10);
74 GUniquePtr<char> dbusAddress(g_strdup_printf("tcp:host=%s,port=%u", inspectorAddress.get(), port));
75 g_dbus_connection_new_for_address(dbusAddress.get(), G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, nullptr, m_cancellable.get(),
76 [](GObject*, GAsyncResult* result, gpointer userData) {
77 RemoteInspector* inspector = static_cast<RemoteInspector*>(userData);
78 GUniqueOutPtr<GError> error;
79 if (GRefPtr<GDBusConnection> connection = adoptGRef(g_dbus_connection_new_for_address_finish(result, &error.outPtr())))
80 inspector->setupConnection(WTFMove(connection));
81 else if (!g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
82 g_warning("RemoteInspector failed to connect to inspector server at: %s: %s", g_getenv("WEBKIT_INSPECTOR_SERVER"), error->message);
83 }, this);
84}
85
86void RemoteInspector::stopInternal(StopSource)
87{
88 if (!m_enabled)
89 return;
90
91 m_enabled = false;
92 m_pushScheduled = false;
93 g_cancellable_cancel(m_cancellable.get());
94 m_cancellable = nullptr;
95
96 for (auto targetConnection : m_targetConnectionMap.values())
97 targetConnection->close();
98 m_targetConnectionMap.clear();
99
100 updateHasActiveDebugSession();
101
102 m_automaticInspectionPaused = false;
103 m_dbusConnection = nullptr;
104}
105
106static const char introspectionXML[] =
107 "<node>"
108 " <interface name='" REMOTE_INSPECTOR_DBUS_INTERFACE "'>"
109 " <method name='GetTargetList'>"
110 " </method>"
111 " <method name='Setup'>"
112 " <arg type='t' name='target' direction='in'/>"
113 " </method>"
114 " <method name='SendMessageToTarget'>"
115 " <arg type='t' name='target' direction='in'/>"
116 " <arg type='s' name='message' direction='in'/>"
117 " </method>"
118 " <method name='FrontendDidClose'>"
119 " <arg type='t' name='target' direction='in'/>"
120 " </method>"
121 " </interface>"
122 "</node>";
123
124const GDBusInterfaceVTable RemoteInspector::s_interfaceVTable = {
125 // method_call
126 [] (GDBusConnection*, const gchar* /*sender*/, const gchar* /*objectPath*/, const gchar* /*interfaceName*/, const gchar* methodName, GVariant* parameters, GDBusMethodInvocation* invocation, gpointer userData) {
127 auto* inspector = static_cast<RemoteInspector*>(userData);
128 if (!g_strcmp0(methodName, "GetTargetList")) {
129 inspector->receivedGetTargetListMessage();
130 g_dbus_method_invocation_return_value(invocation, nullptr);
131 } else if (!g_strcmp0(methodName, "Setup")) {
132 guint64 targetID;
133 g_variant_get(parameters, "(t)", &targetID);
134 inspector->receivedSetupMessage(targetID);
135 g_dbus_method_invocation_return_value(invocation, nullptr);
136 } else if (!g_strcmp0(methodName, "SendMessageToTarget")) {
137 guint64 targetID;
138 const char* message;
139 g_variant_get(parameters, "(t&s)", &targetID, &message);
140 inspector->receivedDataMessage(targetID, message);
141 g_dbus_method_invocation_return_value(invocation, nullptr);
142 } else if (!g_strcmp0(methodName, "FrontendDidClose")) {
143 guint64 targetID;
144 g_variant_get(parameters, "(t)", &targetID);
145 inspector->receivedCloseMessage(targetID);
146 g_dbus_method_invocation_return_value(invocation, nullptr);
147 }
148 },
149 // get_property
150 nullptr,
151 // set_property
152 nullptr,
153 // padding
154 { 0 }
155};
156
157void RemoteInspector::setupConnection(GRefPtr<GDBusConnection>&& connection)
158{
159 LockHolder lock(m_mutex);
160
161 ASSERT(connection);
162 ASSERT(!m_dbusConnection);
163 m_dbusConnection = WTFMove(connection);
164
165 static GDBusNodeInfo* introspectionData = nullptr;
166 if (!introspectionData)
167 introspectionData = g_dbus_node_info_new_for_xml(introspectionXML, nullptr);
168
169 g_dbus_connection_register_object(m_dbusConnection.get(), REMOTE_INSPECTOR_DBUS_OBJECT_PATH,
170 introspectionData->interfaces[0], &s_interfaceVTable, this, nullptr, nullptr);
171
172 if (!m_targetMap.isEmpty())
173 pushListingsSoon();
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 g_warning("RemoteInspector failed to send DBus message: %s", error->message);
182}
183
184TargetListing RemoteInspector::listingForInspectionTarget(const RemoteInspectionTarget& target) const
185{
186 if (!target.remoteDebuggingAllowed())
187 return nullptr;
188
189 // FIXME: Support remote debugging of a ServiceWorker.
190 if (target.type() == RemoteInspectionTarget::Type::ServiceWorker)
191 return nullptr;
192
193 ASSERT(target.type() == RemoteInspectionTarget::Type::Web || target.type() == RemoteInspectionTarget::Type::JavaScript);
194 return g_variant_new("(tsssb)", static_cast<guint64>(target.targetIdentifier()),
195 target.type() == RemoteInspectionTarget::Type::Web ? "Web" : "JavaScript",
196 target.name().utf8().data(), target.type() == RemoteInspectionTarget::Type::Web ? target.url().utf8().data() : "null",
197 target.hasLocalDebugger());
198}
199
200TargetListing RemoteInspector::listingForAutomationTarget(const RemoteAutomationTarget& target) const
201{
202 return g_variant_new("(tsssb)", static_cast<guint64>(target.targetIdentifier()),
203 "Automation", target.name().utf8().data(), "null", target.isPaired());
204}
205
206void RemoteInspector::pushListingsNow()
207{
208 if (!m_dbusConnection)
209 return;
210
211 m_pushScheduled = false;
212
213 GVariantBuilder builder;
214 g_variant_builder_init(&builder, G_VARIANT_TYPE("(a(tsssb)b)"));
215 g_variant_builder_open(&builder, G_VARIANT_TYPE("a(tsssb)"));
216 for (auto listing : m_targetListingMap.values())
217 g_variant_builder_add_value(&builder, listing.get());
218 g_variant_builder_close(&builder);
219 g_variant_builder_add(&builder, "b", m_clientCapabilities && m_clientCapabilities->remoteAutomationAllowed);
220 g_dbus_connection_call(m_dbusConnection.get(), nullptr,
221 INSPECTOR_DBUS_OBJECT_PATH, INSPECTOR_DBUS_INTERFACE, "SetTargetList",
222 g_variant_builder_end(&builder), nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START,
223 -1, m_cancellable.get(), dbusConnectionCallAsyncReadyCallback, nullptr);
224}
225
226void RemoteInspector::pushListingsSoon()
227{
228 if (!m_dbusConnection)
229 return;
230
231 if (m_pushScheduled)
232 return;
233
234 m_pushScheduled = true;
235
236 RunLoop::current().dispatch([this] {
237 LockHolder lock(m_mutex);
238 if (m_pushScheduled)
239 pushListingsNow();
240 });
241}
242
243void RemoteInspector::sendAutomaticInspectionCandidateMessage()
244{
245 ASSERT(m_enabled);
246 ASSERT(m_automaticInspectionEnabled);
247 ASSERT(m_automaticInspectionPaused);
248 ASSERT(m_automaticInspectionCandidateTargetIdentifier);
249 ASSERT(m_dbusConnection);
250 // FIXME: Implement automatic inspection.
251}
252
253void RemoteInspector::sendMessageToRemote(TargetID targetIdentifier, const String& message)
254{
255 LockHolder lock(m_mutex);
256 if (!m_dbusConnection)
257 return;
258
259 g_dbus_connection_call(m_dbusConnection.get(), nullptr,
260 INSPECTOR_DBUS_OBJECT_PATH, INSPECTOR_DBUS_INTERFACE, "SendMessageToFrontend",
261 g_variant_new("(ts)", static_cast<guint64>(targetIdentifier), message.utf8().data()),
262 nullptr, G_DBUS_CALL_FLAGS_NO_AUTO_START,
263 -1, m_cancellable.get(), dbusConnectionCallAsyncReadyCallback, nullptr);
264}
265
266void RemoteInspector::receivedGetTargetListMessage()
267{
268 LockHolder lock(m_mutex);
269 pushListingsNow();
270}
271
272void RemoteInspector::receivedSetupMessage(TargetID targetIdentifier)
273{
274 setup(targetIdentifier);
275}
276
277void RemoteInspector::receivedDataMessage(TargetID targetIdentifier, const char* message)
278{
279 RefPtr<RemoteConnectionToTarget> connectionToTarget;
280 {
281 LockHolder lock(m_mutex);
282 connectionToTarget = m_targetConnectionMap.get(targetIdentifier);
283 if (!connectionToTarget)
284 return;
285 }
286 connectionToTarget->sendMessageToTarget(String::fromUTF8(message));
287}
288
289void RemoteInspector::receivedCloseMessage(TargetID targetIdentifier)
290{
291 RefPtr<RemoteConnectionToTarget> connectionToTarget;
292 {
293 LockHolder lock(m_mutex);
294 RemoteControllableTarget* target = m_targetMap.get(targetIdentifier);
295 if (!target)
296 return;
297
298 connectionToTarget = m_targetConnectionMap.take(targetIdentifier);
299 updateHasActiveDebugSession();
300 }
301
302 if (connectionToTarget)
303 connectionToTarget->close();
304}
305
306void RemoteInspector::setup(TargetID targetIdentifier)
307{
308 RemoteControllableTarget* target;
309 {
310 LockHolder lock(m_mutex);
311 target = m_targetMap.get(targetIdentifier);
312 if (!target)
313 return;
314 }
315
316 auto connectionToTarget = adoptRef(*new RemoteConnectionToTarget(*target));
317 ASSERT(is<RemoteInspectionTarget>(target) || is<RemoteAutomationTarget>(target));
318 if (!connectionToTarget->setup()) {
319 connectionToTarget->close();
320 return;
321 }
322
323 LockHolder lock(m_mutex);
324 m_targetConnectionMap.set(targetIdentifier, WTFMove(connectionToTarget));
325
326 updateHasActiveDebugSession();
327}
328
329void RemoteInspector::sendMessageToTarget(TargetID targetIdentifier, const char* message)
330{
331 if (auto connectionToTarget = m_targetConnectionMap.get(targetIdentifier))
332 connectionToTarget->sendMessageToTarget(String::fromUTF8(message));
333}
334
335void RemoteInspector::requestAutomationSession(const char* sessionID, const Client::SessionCapabilities& capabilities)
336{
337 if (!m_client)
338 return;
339
340 if (!m_clientCapabilities || !m_clientCapabilities->remoteAutomationAllowed)
341 return;
342
343 if (!sessionID || !sessionID[0])
344 return;
345
346 m_client->requestAutomationSession(String::fromUTF8(sessionID), capabilities);
347 updateClientCapabilities();
348}
349
350} // namespace Inspector
351
352#endif // ENABLE(REMOTE_INSPECTOR)
353