Note:

weinre has 'graduated' from the prototype described in this paper to an open source project currently available at Apache.

weinre - WEb INspector REmote

Author: Patrick Mueller - [mail] [blog] [web]
History: 2012-08-09 - added note above about current code at Apache 2011-01-31 - added note above about code at Github (no longer there)
2010-04-27 - initial write-up

what is weinre?

weinre (WEb INspector REmote) is an experiment I undertook in early 2010 to run the existing Web Inspector debug client as a plain old web page (the debug client written in HTML/CSS/JS) have it debug a web page running in a browser window running on a different machine. The complete running story would allow for some amount of debugging of a web page running on an iPhone device (or simulator) in Mobile Safari, and debug it on a desktop using Web Inspector running in a Safari or Chrome.

If you aren't already familiar with Web Inspector, there's a tutorial available for the Chrome-flavor of Web Inspector called Google Chrome Developer Tools.

A movie showing weinre in action is available on YouTube.

You can download the source and built runtimes for weinre, on the Web Inspector page of the WebKit wiki.

This report provides introductory information on the implementation of weinre. Spelunk through the code for more detail. If you have additional questions about weinre, look for me on #webkit-inspector on irc.freenode.net.

terms and basic structure

When using weinre, there are three different programs / web pages running simultaneously:

The client and target are both running in a web browser, and as such act as HTTP clients, and have no way of acting as HTTP servers. The only HTTP server is the agent, which serves as a gateway between the target and client. The message flow between these three will be described later, but here's a sneak peek.

client -> agent:  HTTP POST command from client to target
activate agent
agent  -> target: HTTP GET  command from client to target
deactivate agent
target -> agent:  HTTP POST command from target to client
activate agent
agent  -> client: HTTP GET  command from target to client
deactivate agent
-->

running

files

Note: before running, please read the section on security, or rather, the lack of it.

After expanding the weinre.zip archive, you should see the following files/directory structure:

weinred.py

This file implements the agent/server. Directions to launch it are below.

client/*

This directory contains the bulk of the Web Inspector client web page. Accessed via the /client/ resource, which redirects to the client/weinre-client.html file.

weinre-target.js

This file is the JavaScript file that debug target pages need to include to enabled debugging.

test-app/*

A test application to run as a target.

The weinred.py file implements an HTTP server in Python, which is used for the following:

  • serves all of the HTML, CSS, and JS files used by the debug client
  • serves the weinre-target.js file used by the debug target
  • acts as the intermediary between the client and target for exchanging messages.

starting the server

To get started, pick a tcp/ip port to use to run the server. I picked 8081 while I was working. Launch the server with the command:

python weinred.py 8081

Replace 8081 with whatever port you want to use.

You should see something similar to the following when it starts:

weinred.py: agent/server running at: http://[hostname]:8081/
weinred.py: for targets include:     http://[hostname]:8081/weinre-target.js
weinred.py: run client at:           http://[hostname]:8081/client/

weinred.py attempts to determine your host name, which in this example is shown as "[hostname]".

Previously, I had mentioned running the Web Inspector client through the agent server, as well as accessing the weinre-target.js file through the agent server. The console messages are telling you exactly how to do that. Specifically, use the following URL in your web browser to launch the Web Inspector debug client:

http://[hostname]:8081/client/

Note that the Web Inspector client code currently only runs in Webkit-based browsers; eg, Safari, Chrome, and WebKit nightlies.

The following should appear in your browser:

Included with the archive is a small HTML file as test-app/test.html, which is ready for being debugged using weinre. You will need to change the src attribute of the script element pointing to weinre-target.js per the message in weinred.py's console.

After making this change, load the test-app/test.html in your browser. Note, you will probably need to do this with an http:// URL as opposed to file: URL; in other words, load this file through a web server you have installed on your local machine (since I know you have one).

The test application, when loaded will look like this:

Once both the debug client and target are both running, they should connect to each other and you can begin debugging. You can tell by looking at the "status indicator" available in the test application and in the debug client. In the test application, the green box floating to the right is the indicator, as as this screen shot indicates, the debug target is connected. If you switch to the debug client, you should see the indicator in the Remote panel has changed from a yellow "waiting" to a green "connected".

test drive

Now that everything's running, you can actually try some things.

In the debug client, switch to the Elements panel; you should see the DOM elements for the sample page displayed. You can currently interact in a read-only fashion with the elements, styles, etc. In addition, the element which the cursor is currently over will be highlighted in the page being debugged (via hacking it's CSS background - not great).

For further things you can do, see the supported and not supported function section below.

In general, either of the test application and debug client can be started first - it will wait till it's peer has been started (as indicated by the status indicator). You can also reload either page at any time. In particular, reloading the debug client will allow you to see an DOM changes that may have occurred since the page was first loaded by the debugger.

supported and not supported function

security

not supported: any security

This is a bit of a problem, at least to proceed from experiment stage to something useful. Currently there is no security used anywhere. In addition, the agent server allows cross-origin XHR access to itself from other origins, so it's even more vulnerable than a typical web setup.

Until some security is implemented, only run the weinred.py server while you are playing with or testing the system. weinred.py should obviously not be used in a production environment.

panels: scripts, resources, timeline, profile, etc

not supported: any

Most of the Web Inspector panels have been disabled since there is no information to display on them. The scripts panel is shown only because other code in Web Inspector requires it to be shown.

panel: console

supported: basic console actions

You can interact with the console as a REPL as well see output from the target when it invokes console.log().

panel: elements

supported: read-only introspection

The DOM is populated in a lazy fashion, but is not updated as it changes. You can refresh the entire debug client to see the current DOM.

panel: storage

supported: read-only introspection

The localStorage storage area is updated when it's changed via the localStorage.setItem() or if manually refreshed.

building

A Makefile is provided to build and run weinre. Use the following command to get brief help on how to invoke the Makefile:

make help

To update the version of the Web Inspector client that weinre uses, run the script third-party/WebKit/copy-inspector-source.py. After updating the Web Inspector client code, you should note the version of that code in the file third-party/WebKit/WebKit-version.txt.

how the innards work

The innards of weinre can be broken down into the following pieces, described below:

  • bridging to existing Web Inspector code
  • implement debugging function in JavaScript instead of C++
  • message format for messages between client and target
  • using HTTP to flow messages between client and target

bridging to existing Web Inspector code

One of the primary goals of this experiment was to reuse existing Web Inspector code. Web Inspector consists of the following bits:

  • HTML, CSS, and JS files used in the Web Inspector user interface
  • C++ code running in the user interface coordinating with the page being debugged
  • C++ code running in the page being debugged coordinating with the Web Inspector user interface.

Since the primary goal was to reuse Web Inspector goal, and another goal is to run the code using user-land facilities, the implicit goal was to reuse as much of the HTML, CSS, and JS code, and replace the C++ code.

You might think that handling the separation of the client and target running in different web pages would be a big problem. But it wasn't, since the Web Inspector code is largely already split along these lines to handle Google Chrome's multi-process architecture - in Chrome's Developer Tools (their version of Web Inspector), the page being debugged and the debug window are running in separate processes.

As such, it turns out that "all you need to do" is plug in some code to handle the multi-process message handling. The bits and bytes of how this is done is described in the sections below.

But the "plug in some code" bits can be described here.

Web Inspector (the version of code used for this experiment) uses two "classes" to provide the multi-process message handling. One runs in the debug client, and is called InspectorBackend. The other runs in the debug target and is called InspectorFrontendHost. In traditional Web Inspector, these classes are implemented in C++ code and made available to the respective JavaScript environments as plain old JavaScript objects. In the case of weinre, these classes are implemented in JavaScript and injected into the JavaScript environment before the rest of Web Inspector is loaded. Kind of a hacky "plug-in" model, but good enough for rock-n-roll.

The files where these classes are implemented in the source distribution are:

  • weinre-src/src/client/InspectorBackend-weinre.js
  • weinre-src/src/client/InspectorFrontendHost-weinre.js

implement debugging function in JavaScript instead of C++

Some of the function in Web Inspector to introspect over your web page being debugged is implemented in JavaScript. Most of this code lives in the file weinre-src/third-party/WebKit/WebCore/inspector/front-end/InjectedScript.js. Other function can't be implemented in user-land JavaScript, or is unwieldy to implement in JavaScript, so it's implemented in C++ instead.

So the trick is to find that function which is implemented in C++, and re-implement it in JavaScript.

Sometimes this is impossible - for instance, being able to set or query JavaScript breakpoints, or be notified that a breakpoint is hit. Other times, cheap imitations can be made to work - for instance, highlighting nodes can be done by temporarily whacking the background-color of a node.

Most of the code to do this is in the file weinre-src/src/weinre/target.js. Shield your eyes - some of this code is painful.

message format for messages between client and target

So we know there are messages being sent between the client and target, which means somehow a message needs to be serialized and sent over an interprocess communication channel from one to another. This serialized format needs to be formalized.

For this version of weinre, a JSON version of the Google Chrome Developer Tools Protocol (CDTP) was used as the message format. One of the reasons for using this was that perhaps other Google Chrome Developer Tools, that use the same protocol, could also make use of this work, or weinre could maybe make use of other Google Chrome Developer Tools. The format was somewhat arbitrary, so using such a flimsy excuse seemed fine.

An example message sent from the client to the target, to get a list of child nodes from an already identified node is:

{

"Headers": [
    ["Tool",        "WebInspectorRemote"],
    ["Destination", "target"],
    ["Origin",      "client"],
    ["OriginURL",   "/channel/client/1272379028.611741-0"]
],

"Content": {
    "command":  "getChildNodes",
    "args":     [5, 3]
}

}

Note that more headers are sent than are sent with CDTP.

The message above is actually wrapped in a another delicious layer of cake, which looks like:

{
"msgTo":            "target",
"text":             "message delivered to: target",
"channelId":        "1272379028.611741-1",
"channelMessage":   <<CDTP message above>>,
"date":             "2010-04-27 10:46:38.464747",
"id":               26
}

There's obviously some verbosity here.

using HTTP to flow messages between client and target

As already mentioned, HTTP was used as the transport for sending messages between the client and server. Since both the debug client and debug target are running in standard browser environments, neither can act as a server. Instead, the debug agent acts as the HTTP server to transfer messages between the client and target.

In order for this to work, both the client and target always have an outstanding HTTP GET request to poll for new messages from the other side. These GET requests are potentially long-lived, though in practice we kept the maximum wait time in the "couple of seconds" range. This made it easier to determine when one side of the connection "went away".

So the actual message flow would be:

  • client sends a message to the target by POSTing a message to the agent
  • the agent holds onto the message until the target asks for it; in practice, there is always an outstanding request for a command. The agent passes the message to the outstanding request to be returned, and so the target receives the message via it's GET request for the next command.
  • message sends are one-way; so the client ignores any response it got back from the POST request (nothing is returned anyway).

There are a number of subtleties to the "message passing via HTTP" scheme. The python code in weinre-src/weinred.py handles most of this complexity.

Before communicating with each other, a client and target must be connected. Connection is handled by the agent, which waits for both a client and target to send messages indicating they wish to connect, at which point the two will be connected and messages can be passed between them. The agent informs the client and target that peers have connected, and disconnected, by messages where the Origin property of the Header property has value of "agent", and the Content property contains the literal object {"event": "peerConnected"} or {"event": "peerDisconnected"}.

Connecting involves POSTing a message to /channel/target or (/channel/client), which will result in a URL being sent in the response that should be used to POST further messages and to receive messages via GET

While the client page always interacts with the agent, the target is designed so that it can be used with a different page origin than the agent. This means that you can load your page to be debugged from any URL, and have the weinre interaction take place with the agent. But this requires cross-origin XMLHttpRequest support! Luckily, such a thing exists, called CORS. For more information on CORS, see W3 Cross-Origin Resource Sharing spec draft. This implies you can only debug targets that support CORS. Otherwise, you will have to arrange to run your application's server code and the agent code from the same server (perhaps proxying).