| /* |
| * Copyright (C) 2019 Sony Interactive Entertainment Inc. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "RemoteInspectorProtocolHandler.h" |
| |
| #if ENABLE(REMOTE_INSPECTOR) |
| |
| #include "APILoaderClient.h" |
| #include "APINavigation.h" |
| #include "APIUserContentWorld.h" |
| #include "WebPageGroup.h" |
| #include "WebPageProxy.h" |
| #include "WebScriptMessageHandler.h" |
| #include "WebUserContentControllerProxy.h" |
| #include <WebCore/JSDOMExceptionHandling.h> |
| #include <WebCore/SerializedScriptValue.h> |
| #include <wtf/URL.h> |
| #include <wtf/text/StringBuilder.h> |
| |
| namespace WebKit { |
| |
| using namespace WebCore; |
| |
| class ScriptMessageClient final : public WebScriptMessageHandler::Client { |
| WTF_MAKE_FAST_ALLOCATED; |
| public: |
| ScriptMessageClient(RemoteInspectorProtocolHandler& inspectorProtocolHandler) |
| : m_inspectorProtocolHandler(inspectorProtocolHandler) { } |
| |
| ~ScriptMessageClient() { } |
| |
| void didPostMessage(WebPageProxy& page, FrameInfoData&&, WebCore::SerializedScriptValue& serializedScriptValue) override |
| { |
| auto tokens = serializedScriptValue.toString().split(":"); |
| if (tokens.size() != 3) |
| return; |
| |
| URL requestURL { { }, page.pageLoadState().url() }; |
| m_inspectorProtocolHandler.inspect(requestURL.hostAndPort(), tokens[0].toUIntStrict(), tokens[1].toUIntStrict(), tokens[2]); |
| } |
| |
| private: |
| RemoteInspectorProtocolHandler& m_inspectorProtocolHandler; |
| }; |
| |
| class LoaderClient final : public API::LoaderClient { |
| WTF_MAKE_FAST_ALLOCATED; |
| public: |
| LoaderClient(Function<void()>&& loadedCallback) |
| : m_loadedCallback { WTFMove(loadedCallback) } { } |
| |
| void didFinishLoadForFrame(WebKit::WebPageProxy&, WebKit::WebFrameProxy&, API::Navigation*, API::Object*) final |
| { |
| m_loadedCallback(); |
| } |
| |
| private: |
| Function<void()> m_loadedCallback; |
| }; |
| |
| void RemoteInspectorProtocolHandler::inspect(const String& hostAndPort, ConnectionID connectionID, TargetID targetID, const String& type) |
| { |
| if (m_inspectorClient) |
| m_inspectorClient->inspect(connectionID, targetID, type); |
| } |
| |
| void RemoteInspectorProtocolHandler::runScript(const String& script) |
| { |
| m_page.runJavaScriptInMainFrame(script, false, |
| [](API::SerializedScriptValue*, bool hadException, const WebCore::ExceptionDetails& exceptionDetails, CallbackBase::Error) { |
| if (hadException) |
| LOG_ERROR("Exception running script \"%s\"", exceptionDetails.message.utf8().data()); |
| }); |
| } |
| |
| void RemoteInspectorProtocolHandler::targetListChanged(RemoteInspectorClient& client) |
| { |
| StringBuilder html; |
| if (client.targets().isEmpty()) |
| html.append("<p>No targets found</p>"_s); |
| else { |
| html.append("<table>"); |
| for (auto& connectionID : client.targets().keys()) { |
| for (auto& target : client.targets().get(connectionID)) { |
| html.append(makeString( |
| "<tbody><tr>" |
| "<td class=\"data\"><div class=\"targetname\">", target.name, "</div><div class=\"targeturl\">", target.url, "</div></td>" |
| "<td class=\"input\"><input type=\"button\" value=\"Inspect\" onclick=\"window.webkit.messageHandlers.inspector.postMessage(\\'", connectionID, ":", target.id, ":", target.type, "\\');\"></td>" |
| "</tr></tbody>" |
| )); |
| } |
| } |
| html.append("</table>"); |
| } |
| m_targetListsHtml = html.toString(); |
| if (m_pageLoaded) |
| updateTargetList(); |
| } |
| |
| void RemoteInspectorProtocolHandler::updateTargetList() |
| { |
| if (!m_targetListsHtml.isEmpty()) { |
| runScript(makeString("updateTargets(`", m_targetListsHtml, "`);")); |
| m_targetListsHtml = { }; |
| } |
| } |
| |
| void RemoteInspectorProtocolHandler::platformStartTask(WebPageProxy& pageProxy, WebURLSchemeTask& task) |
| { |
| auto& requestURL = task.request().url(); |
| |
| // Destroy the client before creating a new connection so it can connect to the same port |
| m_inspectorClient = nullptr; |
| m_inspectorClient = makeUnique<RemoteInspectorClient>(requestURL, *this); |
| |
| // Setup target postMessage listener |
| auto handler = WebScriptMessageHandler::create(makeUnique<ScriptMessageClient>(*this), "inspector", API::UserContentWorld::normalWorld()); |
| pageProxy.pageGroup().userContentController().addUserScriptMessageHandler(handler.get()); |
| |
| // Setup loader client to get notified of page load |
| m_page.setLoaderClient(makeUnique<LoaderClient>([this] { |
| m_pageLoaded = true; |
| updateTargetList(); |
| })); |
| m_pageLoaded = false; |
| |
| StringBuilder htmlBuilder; |
| htmlBuilder.append( |
| "<html><head><title>Remote Inspector</title>" |
| "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />" |
| "<style>" |
| " h1 { color: #babdb6; text-shadow: 0 1px 0 white; margin-bottom: 0; }" |
| " html { font-family: -webkit-system-font; font-size: 11pt; color: #2e3436; padding: 20px 20px 0 20px; background-color: #f6f6f4; " |
| " background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #eeeeec), color-stop(1, #f6f6f4));" |
| " background-size: 100% 5em; background-repeat: no-repeat; }" |
| " table { width: 100%; border-collapse: collapse; table-layout: fixed; }" |
| " table, td { border: 1px solid #d3d7cf; border-left: none; border-right: none; }" |
| " p { margin-bottom: 30px; }" |
| " td { padding: 15px; }" |
| " td.data { width: 200px; }" |
| " .targetname { font-weight: bold; overflow: hidden; white-space:nowrap; text-overflow: ellipsis; }" |
| " .targeturl { color: #babdb6; background: #eee; word-wrap: break-word; overflow-wrap: break-word; }" |
| " td.input { width: 64px; }" |
| " input { width: 100%; padding: 8px; }" |
| "</style>" |
| "</head><body><h1>Inspectable targets</h1>" |
| "<div id=\"targetlist\"><p>No targets found</p></div></body>" |
| "<script>" |
| "function updateTargets(str) {" |
| "let targetDiv = document.getElementById('targetlist');" |
| "targetDiv.innerHTML = str;" |
| "}" |
| "</script>"); |
| htmlBuilder.append("</html>"); |
| |
| auto html = htmlBuilder.toString().utf8(); |
| auto data = SharedBuffer::create(html.data(), html.length()); |
| ResourceResponse response(requestURL, "text/html"_s, html.length(), "UTF-8"_s); |
| task.didReceiveResponse(response); |
| task.didReceiveData(WTFMove(data)); |
| task.didComplete(ResourceError()); |
| } |
| |
| } // namespace WebKit |
| |
| #endif // ENABLE(REMOTE_INSPECTOR) |