In Igalia we have been contributing to the WebKit project for many years. Starting with WebKitGTK+ and progressively reaching other areas such as WebCore, improving accessibility, implementing new APIs, tooling, fixing tons of bugs, etc. The Web Inspector is another area were we have contributed, although much more modestly. This is a post I wanted to write since a long time ago. It’s a brief tour through the Web Inspector. Here I discuss its architecture, history and main features. Hopefully this information can be useful for other people who would like to start hacking on this important component of the WebKit project.
What’s the Web Inspector?
The difference between the Web Inspector and other tools, such as Firefox’s famous Web Developer extension, is that the Web Inspector is not an external plugin, but it’s part of WebKit. That means that every WebKit port features the Web Inspector. Nowadays, all major open-source browsers include their own Web Inspector alike tool. Chrome features its Developer tools and Firefox has its own Developer tools too.
Throughtout the years
The Web Inspector was shipped in WebKit for the first time in January 2006. Since then it has gone through big and small changes.
The first big change came in June 2007. There was a big redesign, the network panel was included for the first time, syntax highlighting, better error reporting, etc.
One year later, in September 2008, the Inspector went through another big redesign and more panels were included (Elements, Resources, Scripts, Profile, Database).
November 2009 brought better edition of DOM elements, inline creation of CSS rules and selectors, a widget for color editing, JSON and CSS syntax highlighting, etc.
In June 2012 a brand-new Web Inspector was introduced but only for Safari 6. Exactly one year later, Safari 6’s Web Inspector was open-sourced. During some time the new and old versions of the Web Inspector lived together in the codebase. This new inspector brought a major visual redesign and new layout elements: toolbar, navigation bar, quick console, content, sidebar, etc. The panels were structured in: Resources, Timeline, Debugger, Styles, Layers and Node. The current Web Inspector it’s still based on this release.
Two months before Apple open-sourced the new Web Inspector, Google forked WebKit and created Blink. The Web Inspector is known as Developer Tools in Chrome. Since Blink was forked two months before the new Web Inspector release, Chrome’s Developer Tools are actually based in the old Web Inspector code. Although probably it has gone through many changes since the last two years. The bottom line is that WebKit’s Web Inspector and Chrome’s Developer Tools look in fact very similar.
A first glimpse
The Web Inspector source code lives at
The WebInspectorUI, which represents the frontend, is structured in several folders:
Base/: The main classes (
Configurations/: XCode configuration files.
Controllers/: Catch events and call Model classes to perform business logic. Important files: XXXManager.js (
Images/: Images for icons, visual elements, etc. The images for GTK are different due to license restrictions.
Localications/: Contains file with localized strings in English.
Models/: Classes that perform business logic (
Protocol/: Observers that respond to notifications emitted by the backend.
Scripts/: Several scripts in Perl, Ruby and Python to perform automatic tasks (update CodeMirror, update resources, minimize CSS, etc).
Tools/PrettyPrinting/: External tool for pretty print source code in the Console tab.
Versions/: Description of protocols for different iOS Versions.
View/: Classes for visual element objects.
But this only works in not CMake ports (Apple). It has the inconvenient of no updating localized strings too. In other ports, such as WebKitGTK+, there’s no –inspector-frontend flag so it’s necessary to build WebKit. As usual, only the changed files are built:
Once it’s built, open MiniBrowser and the Inspector (Ctrl+I) to see your changes:
How to debug?
Definitively when starting hacking in a new project it’s fundamental to be able to see what’s going on inside. If you try to inspect a Web Inspector variable using
console.log(var) nothing will be printed out. It’s necessary to build WebKit in debug mode and enable the flag developerExtrasEnabled:
In the case of the WebKitGTK+ port, developerExtrasEnabled is always set to TRUE.
It’s also possible to do the same in release mode, just by removing an #ifdef block in WebKit2/UIProcess/gtk/WebInspectorProxyGtk.cpp:
Now all console.log(var) messages will be printed out in the shell. With this setting enabled, it’s also possible to open a new Web Inspector to inspect the Web Inspector. With the Web Inspector open, righ-click on one of its elements and select Inspect Element, just like in a normal web page.
The Web Inspector is a multi-tier application. It’s divided into 3 layers: a frontend, a backend and a target. This division detaches the inspector from the inspected browser. In other words, it’s possible to use the inspector to inspect a browser running in a remote device. This can be useful to debug an iPhone web application or a WebKitGTK+ based browser running in an embedded environment, such as the RaspberryPi.
On the browser to be inspected, first define the
On the client side, open WebKit:
Go to the server URL, in this case http://127.0.0.1:9222, and you will see a Web Inspector which is actually inspecting a remote browser.
Architecture of the Web Inspector
As mentioned before, the Web Inspector is a 3-tier application divided into several layers:
- Frontend, also known as the client.
- Target, also known as the debugee.
- Backend, also known as the server.
The frontend (
The target is the program being debugged. In normal operation of the Web Inspector, the target program is the WebKit loaded in the browser. In remote debugging mode, the inspected target is a WebKit loaded in a remote machine.
The backend (
Let’s dive into the frontend first. If you grep for HTML code hoping to find the layout of the inspector elements, you’re not going to find any code actually. The reason why it’s because all the layout elements in the inspector are via DOM operations (createElement, appendChild,…). For instance, in
Everything starts with Main.html (
Base/XXX.js), others implement business logic operations (
Model/XXX.js), other implements programmatic logic (
Controllers/XXX.js) and others implement visual elements and widgets (
View/XXX.js). Usually UI classes have a CSS file of the same associated, for instance
The WebInspector namespace (
UserInterface/Base/WebInspector.js) is the central element of the frontend. Everything is going to be accessed from there:
As the model, controller and view classes are processed they are going to hook themselves to the WebInspector namespace. Here’s the definition of
It’s a classical Model-View-Controller pattern. The view accesses the controller to execute business logic operations, which are implemented by models. On the same files there’s:
frameResourceManager is an instance of
Controllers/FrameResourceManager.js. Views can also access Models, not usually to perform operations on them but to query them. It’s the case on the same file of inspect method:
DOMNode is a model class (
WebKit Remote Debugging Protocol
Until this point, most of the frontend architecture is covered. I mentioned earlier that another layer of the inspector is the backend. The backend is what mediates between the target program and the frontend. It consists of several C++ classes that expose properties of WebCore (
The answer to that is the WebKit Remote Debugging Protocol, a JSON formatted protocol than enables communication between the frontend and the backend, and vice versa. This protocol is based on the JSON-RPC 2.0 specification. Currently there’s an attempt, under the RemoteDebug initiative, to standardize all the remote debugging protocols that major browsers use. Remote debugging is a bidirectional protocol: clients send asynchronous requests to the server, the server responds to these request and/or generates notifications. The protocol is divided into a different number of domains.
- DOM: Exposes DOM read/write operations.
- Network: Allows tracking network activities of the page; exposes information about HTTP and WebSocket requests and responses, their headers, bodies, raw timing, etc.
- Page: Actions and events related to the inspected page.
- Timeline: Provides its clients with instrumentation records that are generated during the page runtime.
Each domain defines a number of commands it implements and events it generates. For instance, when setting a breakpoint in the frontend’s console, the following message is sent:
For this command, the backend will generate the following response:
Frontend-to-backend communication: an example
Let’s use the clearMessages command and messagesCleared event defined in the Console domain (
In the frontend, the LogManager class (
Controllers/LogManager.js) sends a clearMessages command through ConsoleAgent:
Domain commands are implemented in the backend by agents, which are located at
The glue code that communicates the frontend with the backend is implemented by a set of dispatcher classes. These classes are generated automatically during the build process, out of the definition of the protocol domains. Here is an excerpt of
This is something common in WebKit, where there are many classes for which there is not existing code in the
Source/ directory but are generated automatically and placed at
InspectorFrontDispatchers.h implements an interface for the domain notifications:
The dispatching of a command is done by matching a command name with a method name and calling that method.
Backend-to-frontend communication: response
The frontend dispatcher is the mechanism by which the backend can send information to the backend. The frontend dispatcher implements the protocol notifications of a domain:
In order to react to backend notifications, the frontend needs to register observers of backend events. This registration happens in
The frontend class
UserInterface/Protocol/ConsoleObserver.js) will react to messagesCleared event and trigger some programmatic or business logic:
Web Inspector strings are localized. Localized strings are stored at
Localizations/en.lproj/localizedStrings.js). All UI strings are wrapped by the WebInspector.UIString() method, so they are printed localized:
The contents of
localizedStrings.js are not created manually but by running the script
update-webkit-localizable-strings. This script parses all the strings marked to be localized and updates
Bear this in mind if you send a patch with a new or modified string.
Sending a patch
When sending a patch subject should be prefixed by Web Inspector:. Before the patch is sent, some style checkers are run (
Tools/Scripts/check-webkit-style), to verify the patch complies Web Inspector coding style. It’s also possible to run the script manually:
As usual, modify the updated ChangeLogs and run webkit-patch upload to send your patch.
Community and resources
There’s a very convenient Wiki with very valuable sources of information such as pointers to blog posts, how to debug, open bugs, etc. The Web Inspector has also it’s own IRC channel at freenode: #webkit-inspector. Most of the work in Web Inspector is carried by Timothy Hatcher, Joseph Pecoraro and Brian Burg, with contributions of other WebKit hackers and other collaborators. I can tell patches and bug reports are very welcomed and reviews go very fast.
I also want to mention this post from Brian Burg that discusses Web Inspector architecture and I used as a basis for this post.
The Web Inspector is an important component of the WebKit project. It’s an application structured in 3 layers: a frontend, a backend and a target. This abstraction allows detaching the inspector from the target (inspected WebKit browser).
It has been a rather long post. I hope it can serve as a starting point for anyone interested in understanding or hacking in this important component of the WebKit project.