WebInspectorServer for WebKit2.
authorjocelyn.turcotte@nokia.com <jocelyn.turcotte@nokia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 3 Apr 2012 13:24:32 +0000 (13:24 +0000)
committerjocelyn.turcotte@nokia.com <jocelyn.turcotte@nokia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 3 Apr 2012 13:24:32 +0000 (13:24 +0000)
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.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@113027 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebKit2/ChangeLog
Source/WebKit2/Target.pri
Source/WebKit2/UIProcess/InspectorServer/WebInspectorServer.cpp [new file with mode: 0644]
Source/WebKit2/UIProcess/InspectorServer/WebInspectorServer.h [new file with mode: 0644]
Source/WebKit2/UIProcess/InspectorServer/qt/WebInspectorServerQt.cpp [new file with mode: 0644]
Source/WebKit2/WebKit2.qrc [new file with mode: 0644]
Source/WebKit2/qt/Resources/inspectorPageIndex.html [new file with mode: 0644]

index d9dceb4..07ddbf2 100644 (file)
@@ -1,5 +1,48 @@
 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
 
index 498c469..e8b3b77 100644 (file)
@@ -16,6 +16,8 @@ QT += declarative quick quick-private
 
 CONFIG += staticlib
 
+RESOURCES += $$PWD/WebKit2.qrc
+
 HEADERS += \
     Platform/CoreIPC/ArgumentDecoder.h \
     Platform/CoreIPC/ArgumentEncoder.h \
@@ -204,6 +206,7 @@ HEADERS += \
     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 \
@@ -545,8 +548,10 @@ SOURCES += \
     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 \
diff --git a/Source/WebKit2/UIProcess/InspectorServer/WebInspectorServer.cpp b/Source/WebKit2/UIProcess/InspectorServer/WebInspectorServer.cpp
new file mode 100644 (file)
index 0000000..f335849
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * 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)
diff --git a/Source/WebKit2/UIProcess/InspectorServer/WebInspectorServer.h b/Source/WebKit2/UIProcess/InspectorServer/WebInspectorServer.h
new file mode 100644 (file)
index 0000000..f0bbc21
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * 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
diff --git a/Source/WebKit2/UIProcess/InspectorServer/qt/WebInspectorServerQt.cpp b/Source/WebKit2/UIProcess/InspectorServer/qt/WebInspectorServerQt.cpp
new file mode 100644 (file)
index 0000000..a86fad9
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * 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";
+}
+
+}
diff --git a/Source/WebKit2/WebKit2.qrc b/Source/WebKit2/WebKit2.qrc
new file mode 100644 (file)
index 0000000..450416c
--- /dev/null
@@ -0,0 +1,5 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource prefix="/webkit/resources">
+    <file alias="inspectorPageIndex.html">qt/Resources/inspectorPageIndex.html</file>
+</qresource>
+</RCC>
diff --git a/Source/WebKit2/qt/Resources/inspectorPageIndex.html b/Source/WebKit2/qt/Resources/inspectorPageIndex.html
new file mode 100644 (file)
index 0000000..d074541
--- /dev/null
@@ -0,0 +1,31 @@
+<!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>