2012-03-29 Joseph Pecoraro <joepeck@webkit.org> and Jocelyn Turcotte <jocelyn.turcotte@nokia.com>
+ WebInspectorServer for WebKit2.
+ https://bugs.webkit.org/show_bug.cgi?id=73855
+
+ Reviewed by Simon Hausmann.
+
+ The server uses WebSocket for communication with the remote client and
+ normal HTTP requests are handled to optionally send the frontend on the
+ wire. Those decision are handled per platform and Qt currently maps:
+
+ - "/" to a static listing page.
+ - "/pagelist.json" to the page list for the listing page or tools.
+ - anything else, for example "/inspector.js" to the file with the
+ same name in the dynamic libraries built resources.
+ Invalid files should return a 404.
+
+ * Target.pri:
+ * UIProcess/InspectorServer/WebInspectorServer.cpp: Added.
+ (WebKit):
+ (WebKit::pageIdFromRequestPath):
+ (WebKit::WebInspectorServer::server):
+ (WebKit::WebInspectorServer::WebInspectorServer):
+ (WebKit::WebInspectorServer::~WebInspectorServer):
+ (WebKit::WebInspectorServer::registerPage):
+ (WebKit::WebInspectorServer::unregisterPage):
+ (WebKit::WebInspectorServer::sendMessageOverConnection):
+ (WebKit::WebInspectorServer::didReceiveUnrecognizedHTTPRequest):
+ (WebKit::WebInspectorServer::didReceiveWebSocketUpgradeHTTPRequest):
+ (WebKit::WebInspectorServer::didEstablishWebSocketConnection):
+ (WebKit::WebInspectorServer::didReceiveWebSocketMessage):
+ (WebKit::WebInspectorServer::didCloseWebSocketConnection):
+ (WebKit::WebInspectorServer::closeConnection):
+ * UIProcess/InspectorServer/WebInspectorServer.h: Added.
+ (WebKit):
+ (WebInspectorServer):
+ * UIProcess/InspectorServer/qt/WebInspectorServerQt.cpp: Added.
+ (WebKit):
+ (WebKit::WebInspectorServer::platformResourceForPath):
+ (WebKit::WebInspectorServer::buildPageList):
+ * WebKit2.qrc: Added.
+ * qt/Resources/inspectorPageIndex.html: Added.
+
+2012-03-29 Joseph Pecoraro <joepeck@webkit.org> and Jocelyn Turcotte <jocelyn.turcotte@nokia.com>
+
Add a Generic WebSocket Server.
https://bugs.webkit.org/show_bug.cgi?id=73093
CONFIG += staticlib
+RESOURCES += $$PWD/WebKit2.qrc
+
HEADERS += \
Platform/CoreIPC/ArgumentDecoder.h \
Platform/CoreIPC/ArgumentEncoder.h \
UIProcess/GenericCallback.h \
UIProcess/GeolocationPermissionRequestManagerProxy.h \
UIProcess/GeolocationPermissionRequestProxy.h \
+ UIProcess/InspectorServer/WebInspectorServer.h \
UIProcess/InspectorServer/WebSocketServer.h \
UIProcess/InspectorServer/WebSocketServerClient.h \
UIProcess/InspectorServer/WebSocketServerConnection.h \
UIProcess/FindIndicator.cpp \
UIProcess/GeolocationPermissionRequestManagerProxy.cpp \
UIProcess/GeolocationPermissionRequestProxy.cpp \
+ UIProcess/InspectorServer/WebInspectorServer.cpp \
UIProcess/InspectorServer/WebSocketServer.cpp \
UIProcess/InspectorServer/WebSocketServerConnection.cpp \
+ UIProcess/InspectorServer/qt/WebInspectorServerQt.cpp \
UIProcess/InspectorServer/qt/WebSocketServerQt.cpp \
UIProcess/Launcher/ProcessLauncher.cpp \
UIProcess/Launcher/ThreadLauncher.cpp \
--- /dev/null
+/*
+ * Copyright (C) 2011 Apple Inc. All Rights Reserved.
+ * Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies)
+ *
+ * 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. ``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
+ * 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"
+
+#if ENABLE(INSPECTOR_SERVER)
+
+#include "WebInspectorServer.h"
+
+#include "WebInspectorProxy.h"
+#include "WebSocketServerConnection.h"
+#include <WebCore/HTTPRequest.h>
+
+using namespace WebCore;
+
+namespace WebKit {
+
+static unsigned pageIdFromRequestPath(const String& path)
+{
+ size_t start = path.reverseFind('/');
+ String numberString = path.substring(start + 1, path.length() - start - 1);
+
+ bool ok = false;
+ unsigned number = numberString.toUIntStrict(&ok);
+ if (!ok)
+ return 0;
+ return number;
+}
+
+WebInspectorServer& WebInspectorServer::shared()
+{
+ static WebInspectorServer& server = *new WebInspectorServer;
+ return server;
+}
+
+WebInspectorServer::WebInspectorServer()
+ : WebSocketServer(this)
+ , m_nextAvailablePageId(1)
+{
+}
+
+WebInspectorServer::~WebInspectorServer()
+{
+ // Close any remaining open connections.
+ HashMap<unsigned, WebSocketServerConnection*>::iterator end = m_connectionMap.end();
+ for (HashMap<unsigned, WebSocketServerConnection*>::iterator it = m_connectionMap.begin(); it != end; ++it) {
+ WebSocketServerConnection* connection = it->second;
+ WebInspectorProxy* client = m_clientMap.get(connection->identifier());
+ closeConnection(client, connection);
+ }
+}
+
+int WebInspectorServer::registerPage(WebInspectorProxy* client)
+{
+#ifndef ASSERT_DISABLED
+ ClientMap::iterator end = m_clientMap.end();
+ for (ClientMap::iterator it = m_clientMap.begin(); it != end; ++it)
+ ASSERT(it->second != client);
+#endif
+
+ int pageId = m_nextAvailablePageId++;
+ m_clientMap.set(pageId, client);
+ return pageId;
+}
+
+void WebInspectorServer::unregisterPage(int pageId)
+{
+ m_clientMap.remove(pageId);
+ WebSocketServerConnection* connection = m_connectionMap.get(pageId);
+ if (connection)
+ closeConnection(0, connection);
+}
+
+void WebInspectorServer::sendMessageOverConnection(unsigned pageIdForConnection, const String& message)
+{
+ WebSocketServerConnection* connection = m_connectionMap.get(pageIdForConnection);
+ if (connection)
+ connection->sendWebSocketMessage(message);
+}
+
+void WebInspectorServer::didReceiveUnrecognizedHTTPRequest(WebSocketServerConnection* connection, PassRefPtr<HTTPRequest> request)
+{
+ // request->url() contains only the path extracted from the HTTP request line
+ // and KURL is poor at parsing incomplete URLs, so extract the interesting parts manually.
+ String path = request->url();
+ size_t pathEnd = path.find('?');
+ if (pathEnd == notFound)
+ pathEnd = path.find('#');
+ if (pathEnd != notFound)
+ path.truncate(pathEnd);
+
+ // Ask for the complete payload in memory for the sake of simplicity. A more efficient way would be
+ // to ask for header data and then let the platform abstraction write the payload straight on the connection.
+ Vector<char> body;
+ String contentType;
+ bool found = platformResourceForPath(path, body, contentType);
+
+ HTTPHeaderMap headerFields;
+ headerFields.set("Connection", "close");
+ headerFields.set("Content-Length", String::number(body.size()));
+ if (found)
+ headerFields.set("Content-Type", contentType);
+
+ // Send when ready and close immediately afterwards.
+ connection->sendHTTPResponseHeader(found ? 200 : 404, found ? "OK" : "Not Found", headerFields);
+ connection->sendRawData(body.data(), body.size());
+ connection->shutdownAfterSendOrNow();
+}
+
+bool WebInspectorServer::didReceiveWebSocketUpgradeHTTPRequest(WebSocketServerConnection*, PassRefPtr<HTTPRequest> request)
+{
+ String path = request->url();
+
+ // NOTE: Keep this in sync with WebCore/inspector/front-end/inspector.js.
+ DEFINE_STATIC_LOCAL(const String, inspectorWebSocketConnectionPathPrefix, ("/devtools/page/"));
+
+ // Unknown path requested.
+ if (!path.startsWith(inspectorWebSocketConnectionPathPrefix))
+ return false;
+
+ int pageId = pageIdFromRequestPath(path);
+ // Invalid page id.
+ if (!pageId)
+ return false;
+
+ // There is no client for that page id.
+ WebInspectorProxy* client = m_clientMap.get(pageId);
+ if (!client)
+ return false;
+
+ return true;
+}
+
+void WebInspectorServer::didEstablishWebSocketConnection(WebSocketServerConnection* connection, PassRefPtr<HTTPRequest> request)
+{
+ String path = request->url();
+ unsigned pageId = pageIdFromRequestPath(path);
+ ASSERT(pageId);
+
+ // Ignore connections to a page that already have a remote inspector connected.
+ if (m_connectionMap.contains(pageId)) {
+ LOG_ERROR("A remote inspector connection already exist for page ID %d. Ignoring.", pageId);
+ connection->shutdownNow();
+ return;
+ }
+
+ // Map the pageId to the connection in case we need to close the connection locally.
+ connection->setIdentifier(pageId);
+ m_connectionMap.set(pageId, connection);
+
+ WebInspectorProxy* client = m_clientMap.get(pageId);
+ client->remoteFrontendConnected();
+}
+
+void WebInspectorServer::didReceiveWebSocketMessage(WebSocketServerConnection* connection, const String& message)
+{
+ // Dispatch incoming remote message locally.
+ unsigned pageId = connection->identifier();
+ ASSERT(pageId);
+ WebInspectorProxy* client = m_clientMap.get(pageId);
+ client->dispatchMessageFromRemoteFrontend(message);
+}
+
+void WebInspectorServer::didCloseWebSocketConnection(WebSocketServerConnection* connection)
+{
+ // Connection has already shut down.
+ unsigned pageId = connection->identifier();
+ if (!pageId)
+ return;
+
+ // The socket closing means the remote side has caused the close.
+ WebInspectorProxy* client = m_clientMap.get(pageId);
+ closeConnection(client, connection);
+}
+
+void WebInspectorServer::closeConnection(WebInspectorProxy* client, WebSocketServerConnection* connection)
+{
+ // Local side cleanup.
+ if (client)
+ client->remoteFrontendDisconnected();
+
+ // Remote side cleanup.
+ m_connectionMap.remove(connection->identifier());
+ connection->setIdentifier(0);
+ connection->shutdownNow();
+}
+
+}
+
+#endif // ENABLE(INSPECTOR_SERVER)
--- /dev/null
+/*
+ * Copyright (C) 2011 Apple Inc. All Rights Reserved.
+ * Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies)
+ *
+ * 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. ``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
+ * 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.
+ */
+
+#ifndef WebInspectorServer_h
+#define WebInspectorServer_h
+
+#if ENABLE(INSPECTOR_SERVER)
+
+#include "WebSocketServer.h"
+#include "WebSocketServerClient.h"
+#include <wtf/HashMap.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebKit {
+
+class WebInspectorProxy;
+
+class WebInspectorServer : public WebSocketServer, public WebSocketServerClient {
+public:
+ typedef HashMap<unsigned, WebInspectorProxy*> ClientMap;
+ static WebInspectorServer& shared();
+
+ // Page registry to manage known pages.
+ int registerPage(WebInspectorProxy* client);
+ void unregisterPage(int pageId);
+ void sendMessageOverConnection(unsigned pageIdForConnection, const String& message);
+
+private:
+ WebInspectorServer();
+ ~WebInspectorServer();
+
+ // WebSocketServerClient implementation. Events coming from remote connections.
+ virtual void didReceiveUnrecognizedHTTPRequest(WebSocketServerConnection*, PassRefPtr<WebCore::HTTPRequest>);
+ virtual bool didReceiveWebSocketUpgradeHTTPRequest(WebSocketServerConnection*, PassRefPtr<WebCore::HTTPRequest>);
+ virtual void didEstablishWebSocketConnection(WebSocketServerConnection*, PassRefPtr<WebCore::HTTPRequest>);
+ virtual void didReceiveWebSocketMessage(WebSocketServerConnection*, const String& message);
+ virtual void didCloseWebSocketConnection(WebSocketServerConnection*);
+
+ bool platformResourceForPath(const String& path, Vector<char>& data, String& contentType);
+#if PLATFORM(QT)
+ void buildPageList(Vector<char>& data, String& contentType);
+#endif
+
+ void closeConnection(WebInspectorProxy*, WebSocketServerConnection*);
+
+ unsigned m_nextAvailablePageId;
+ ClientMap m_clientMap;
+ HashMap<unsigned, WebSocketServerConnection*> m_connectionMap;
+};
+
+}
+
+#endif // ENABLE(INSPECTOR_SERVER)
+
+#endif // WebInspectorServer_h
--- /dev/null
+/*
+ * Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+#include "config.h"
+
+#include "WebInspectorServer.h"
+
+#include "WebInspectorProxy.h"
+#include "WebPageProxy.h"
+#include <WebCore/MIMETypeRegistry.h>
+#include <wtf/text/CString.h>
+#include <wtf/text/StringBuilder.h>
+
+namespace WebKit {
+
+bool WebInspectorServer::platformResourceForPath(const String& path, Vector<char>& data, String& contentType)
+{
+ // The page list contains an unformated list of pages that can be inspected with a link to open a session.
+ if (path == "/pagelist.json") {
+ buildPageList(data, contentType);
+ return true;
+ }
+
+ // Point the default path to a formatted page that queries the page list and display them.
+ String localPath = (path == "/") ? "/webkit/resources/inspectorPageIndex.html" : path;
+ // All other paths are mapped directly to a resource, if possible.
+ QFile file(QString::fromLatin1(":%1").arg(localPath));
+ if (file.exists()) {
+ file.open(QIODevice::ReadOnly);
+ data.grow(file.size());
+ file.read(data.data(), data.size());
+
+ size_t extStart = localPath.reverseFind('.');
+ String ext = localPath.substring(extStart != notFound ? extStart + 1 : 0);
+ contentType = WebCore::MIMETypeRegistry::getMIMETypeForExtension(ext);
+ return true;
+ }
+ return false;
+}
+
+void WebInspectorServer::buildPageList(Vector<char>& data, String& contentType)
+{
+ StringBuilder builder;
+ builder.append("[ ");
+ ClientMap::iterator end = m_clientMap.end();
+ for (ClientMap::iterator it = m_clientMap.begin(); it != end; ++it) {
+ WebPageProxy* webPage = it->second->page();
+ if (it != m_clientMap.begin())
+ builder.append(", ");
+ builder.append("{ \"description\": \"");
+ builder.append(webPage->pageTitle());
+ builder.append("\", \"url\": \"");
+ builder.append(webPage->activeURL());
+ builder.append("\", \"inspectorLocation\": \"");
+ builder.append("/webkit/inspector/inspector.html?page=" + String::number(it->first));
+ builder.append("\" }");
+ }
+ builder.append(" ]");
+ CString cstr = builder.toString().utf8();
+ data.append(cstr.data(), cstr.length());
+ contentType = "application/json; charset=utf-8";
+}
+
+}
--- /dev/null
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource prefix="/webkit/resources">
+ <file alias="inspectorPageIndex.html">qt/Resources/inspectorPageIndex.html</file>
+</qresource>
+</RCC>
--- /dev/null
+<!DOCTYPE html>
+<html><head>
+<script type="text/javascript">
+function createPageList() {
+ var xhr = new XMLHttpRequest;
+ xhr.open("GET", "/pagelist.json");
+ xhr.onload = function(e) {
+ if (xhr.status == 200) {
+ var pages = JSON.parse(xhr.responseText);
+ if (pages.length)
+ document.getElementById("noPageNotice").style.display = "none";
+ for (var i in pages) {
+ var link = document.createElement("a");
+ var description = pages[i].description ? pages[i].description : ("Page " + (Number(i)+1));
+ var url = pages[i].url;
+ link.appendChild(document.createTextNode(description + (url ? (" [" + url + "]") : "" )));
+ link.setAttribute("href", pages[i].inspectorLocation);
+ document.body.appendChild(link);
+ document.body.appendChild(document.createElement("br"));
+ }
+ }
+ };
+ xhr.send();
+}
+
+document.addEventListener("DOMContentLoaded", createPageList, false);
+</script>
+</head><body>
+<h1>Inspectable web views</h1>
+<p id="noPageNotice" style="color:grey">None found, make sure that you have set the developerExtrasEnabled preference property on your WebView.</p>
+</body></html>