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#include "WebKitRemoteInspectorProtocolHandler.h"
22
23#if ENABLE(REMOTE_INSPECTOR)
24
25#include "APIUserContentWorld.h"
26#include "WebKitError.h"
27#include "WebKitNavigationPolicyDecision.h"
28#include "WebKitUserContentManagerPrivate.h"
29#include "WebKitWebContextPrivate.h"
30#include "WebScriptMessageHandler.h"
31#include <wtf/URL.h>
32
33namespace WebKit {
34using namespace WebCore;
35
36class ScriptMessageClient final : public WebScriptMessageHandler::Client {
37public:
38 ScriptMessageClient(RemoteInspectorProtocolHandler& inspectorProtocolHandler)
39 : m_inspectorProtocolHandler(inspectorProtocolHandler)
40 {
41 }
42
43 void didPostMessage(WebPageProxy& page, const FrameInfoData&, WebCore::SerializedScriptValue& serializedScriptValue) override
44 {
45 String message = serializedScriptValue.toString();
46 Vector<String> tokens = message.split(':');
47 if (tokens.size() != 2)
48 return;
49
50 URL requestURL = URL({ }, page.pageLoadState().url());
51 m_inspectorProtocolHandler.inspect(requestURL.hostAndPort(), tokens[0].toUInt64(), tokens[1].toUInt64());
52 }
53
54 ~ScriptMessageClient() { }
55
56private:
57 RemoteInspectorProtocolHandler& m_inspectorProtocolHandler;
58};
59
60RemoteInspectorProtocolHandler::RemoteInspectorProtocolHandler(WebKitWebContext* context)
61 : m_context(context)
62{
63 webkit_web_context_register_uri_scheme(context, "inspector", [](WebKitURISchemeRequest* request, gpointer userData) {
64 static_cast<RemoteInspectorProtocolHandler*>(userData)->handleRequest(request);
65 }, this, nullptr);
66}
67
68RemoteInspectorProtocolHandler::~RemoteInspectorProtocolHandler()
69{
70 for (auto* webView : m_webViews)
71 g_object_weak_unref(G_OBJECT(webView), reinterpret_cast<GWeakNotify>(webViewDestroyed), this);
72
73 for (auto* userContentManager : m_userContentManagers) {
74 webkitUserContentManagerGetUserContentControllerProxy(userContentManager)->removeUserMessageHandlerForName("inspector", API::UserContentWorld::normalWorld());
75 g_object_weak_unref(G_OBJECT(userContentManager), reinterpret_cast<GWeakNotify>(userContentManagerDestroyed), this);
76 }
77}
78
79void RemoteInspectorProtocolHandler::webViewDestroyed(RemoteInspectorProtocolHandler* inspectorProtocolHandler, WebKitWebView* webView)
80{
81 inspectorProtocolHandler->m_webViews.remove(webView);
82}
83
84void RemoteInspectorProtocolHandler::userContentManagerDestroyed(RemoteInspectorProtocolHandler* inspectorProtocolHandler, WebKitUserContentManager* userContentManager)
85{
86 inspectorProtocolHandler->m_userContentManagers.remove(userContentManager);
87}
88
89void RemoteInspectorProtocolHandler::handleRequest(WebKitURISchemeRequest* request)
90{
91 URL requestURL = URL({ }, webkit_uri_scheme_request_get_uri(request));
92 if (!requestURL.port()) {
93 GUniquePtr<GError> error(g_error_new_literal(WEBKIT_POLICY_ERROR, WEBKIT_POLICY_ERROR_CANNOT_SHOW_URI, "Cannot show inspector URL: no port provided"));
94 webkit_uri_scheme_request_finish_error(request, error.get());
95 return;
96 }
97
98 auto* webView = webkit_uri_scheme_request_get_web_view(request);
99 ASSERT(webView);
100 auto webViewResult = m_webViews.add(webView);
101 if (webViewResult.isNewEntry)
102 g_object_weak_ref(G_OBJECT(webView), reinterpret_cast<GWeakNotify>(webViewDestroyed), this);
103
104 auto* userContentManager = webkit_web_view_get_user_content_manager(webView);
105 auto userContentManagerResult = m_userContentManagers.add(userContentManager);
106 if (userContentManagerResult.isNewEntry) {
107 auto handler = WebScriptMessageHandler::create(std::make_unique<ScriptMessageClient>(*this), "inspector", API::UserContentWorld::normalWorld());
108 webkitUserContentManagerGetUserContentControllerProxy(userContentManager)->addUserScriptMessageHandler(handler.get());
109 g_object_weak_ref(G_OBJECT(userContentManager), reinterpret_cast<GWeakNotify>(userContentManagerDestroyed), this);
110 }
111
112 auto* client = m_inspectorClients.ensure(requestURL.hostAndPort(), [this, &requestURL] {
113 return std::make_unique<RemoteInspectorClient>(requestURL.host().utf8().data(), requestURL.port().value(), *this);
114 }).iterator->value.get();
115
116 GString* html = g_string_new(
117 "<html><head><title>Remote inspector</title>"
118 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />"
119 "<style>"
120 " h1 { color: #babdb6; text-shadow: 0 1px 0 white; margin-bottom: 0; }"
121 " html { font-family: -webkit-system-font; font-size: 11pt; color: #2e3436; padding: 20px 20px 0 20px; background-color: #f6f6f4; "
122 " background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #eeeeec), color-stop(1, #f6f6f4));"
123 " background-size: 100% 5em; background-repeat: no-repeat; }"
124 " table { width: 100%; border-collapse: collapse; }"
125 " table, td { border: 1px solid #d3d7cf; border-left: none; border-right: none; }"
126 " p { margin-bottom: 30px; }"
127 " td { padding: 15px; }"
128 " td.data { width: 200px; }"
129 " .targetname { font-weight: bold; }"
130 " .targeturl { color: #babdb6; }"
131 " td.input { width: 64px; }"
132 " input { width: 100%; padding: 8px; }"
133 "</style>"
134 "</head><body><h1>Inspectable targets</h1>");
135 if (client->targets().isEmpty())
136 g_string_append(html, "<p>No targets found</p>");
137 else {
138 g_string_append(html, "<table>");
139 for (auto connectionID : client->targets().keys()) {
140 for (auto& target : client->targets().get(connectionID)) {
141 g_string_append_printf(html,
142 "<tbody><tr>"
143 "<td class=\"data\"><div class=\"targetname\">%s</div><div class=\"targeturl\">%s</div></td>"
144 "<td class=\"input\"><input type=\"button\" value=\"Inspect\" onclick=\"window.webkit.messageHandlers.inspector.postMessage('%" G_GUINT64_FORMAT ":%" G_GUINT64_FORMAT "');\"></td>"
145 "</tr></tbody>", target.name.data(), target.url.data(), connectionID, target.id);
146 }
147 }
148 g_string_append(html, "</table>");
149 }
150 g_string_append(html, "</body></html>");
151 gsize streamLength = html->len;
152 GRefPtr<GInputStream> stream = adoptGRef(g_memory_input_stream_new_from_data(g_string_free(html, FALSE), streamLength, g_free));
153 webkit_uri_scheme_request_finish(request, stream.get(), streamLength, "text/html");
154}
155
156void RemoteInspectorProtocolHandler::inspect(const String& hostAndPort, uint64_t connectionID, uint64_t tatgetID)
157{
158 if (auto* client = m_inspectorClients.get(hostAndPort))
159 client->inspect(connectionID, tatgetID);
160}
161
162void RemoteInspectorProtocolHandler::targetListChanged(RemoteInspectorClient& client)
163{
164 Vector<WebKitWebView*, 4> webViewsToRemove;
165 for (auto* webView : m_webViews) {
166 if (webkit_web_view_is_loading(webView))
167 continue;
168
169 URL webViewURL = URL({ }, webkit_web_view_get_uri(webView));
170 auto clientForWebView = m_inspectorClients.get(webViewURL.hostAndPort());
171 if (!clientForWebView) {
172 // This view is not showing a inspector view anymore.
173 webViewsToRemove.append(webView);
174 } else if (clientForWebView == &client)
175 webkit_web_view_reload(webView);
176 }
177
178 for (auto* webView : webViewsToRemove) {
179 g_object_weak_unref(G_OBJECT(webView), reinterpret_cast<GWeakNotify>(webViewDestroyed), this);
180 m_webViews.remove(webView);
181 }
182}
183
184void RemoteInspectorProtocolHandler::connectionClosed(RemoteInspectorClient& client)
185{
186 targetListChanged(client);
187 m_inspectorClients.remove(client.hostAndPort());
188}
189
190} // namespace WebKit
191
192#endif // ENABLE(REMOTE_INSPECTOR)
193