Hi everyone,
Mozilla DevTools is exploring implementing parts of the Chrome DevTools
Protocol ("CDP") [0] in Firefox. This is an HTTP, WebSockets, and JSON
based protocol for automating and inspecting running browser pages.
Originally built for the Chrome DevTools, it has seen wider adoption
with outside developers. In addition to Chrome/Chromium, the CDP is
supported by WebKit, Safari, Node.js, and soon Edge, and an ecosystem of
libraries and tools already exists which plug into it, for debugging,
extracting performance data, providing live-preview functionality like
the Brackets editor, and so on. We believe it would be beneficial if
these could be leveraged with Firefox as well.
The initial implementation we have in mind is an alternate target for
third-party integrations to connect to, in addition to the existing
Firefox DevTools Server. The Servo project has also expressed interest
in adding CDP support to improve its own devtools story, and a PR is in
flight to land a CDP server implementation there [1].
I've been working on this project with guidance from Jim Blandy. We've
come up with the following approach:
- A complete, typed Rust implementation of the CDP protocol messages and
(de)serialization lives in the "cdp" crate [2], automatically generated
from the protocol's JSON specification [3] using a build script (this
happens transparently as part of the normal Cargo compilation process).
This comes with Rustdoc API documentation of all messages/types in the
protocol [4] including textual descriptions bundled with the
specification JSON. The cdp crate will likely track the Chrome stable
release for which version of the protocol is supported. A maintainers'
script exists which can find and fetch the appropriate JSON [5].
- The "tokio-cdp" crate [6] builds on the types and (de)serialization
implementation in the cdp crate to provide a server implementation built
on the Tokio asynchronous I/O system. The server side provides traits
for consuming incoming CDP RPC commands, executing them concurrently and
sending back responses, and simultaneously pushing events to the client.
They are generic over the underlying transport, so the same backend
implementation could provide support for "remote" clients plugging in
over HTTP/WebSockets/JSON or, for example, a browser-local client
communicating over IPDL.
- In Servo, a new component plugs into the cdp and tokio-cdp crates and
acts on behalf of connected CDP clients in response to their commands,
communicating with the rest of the Servo constellation. This server is
disabled by default and can be started by passing a "--cdp" flag to the
Servo binary, binding a TCP listener to the loopback interface at the
standard CDP port 9222 (a different port can be specified as an option
to the flag).
- The implementation we envision in Firefox/Gecko would act similarly: a
new Rust component, disabled by default and switched on via a command
line flag, which binds to a local port and mediates between Gecko
internals and clients connected via tokio-cdp.
We chose to build this on Rust and the Tokio event loop, along with the
hyper HTTP library and rust-websocket which plug into Tokio.
Rust and Cargo provide excellent facilities for compile-time code
generation which integrate transparently into the normal build process,
avoiding the need to invoke scripts by hand to keep generated artifacts
in sync. The Rust ecosystem provides libraries such as quote [7] and
serde [8] which allow us to auto-generate an efficient, typed, and
self-contained interface for the entire protocol. This moves the
complexity of ingesting, validating, and extracting information from
client messages out of the Servo- and Gecko-specific backend
implementations, helps to ensure they conform correctly to the protocol
specification, and provides a structured way of upgrading to new
protocol versions.
As for Tokio, the event loop and Futures-based model of concurrency it
offers maps well to the Chrome DevTools Protocol. RPC commands typically
execute simultaneously, returning responses in order of completion,
while the server continuously generates events to which the client has
subscribed. Under Tokio we can spawn multiple lightweight Tasks,
dispatch messages to them, and multiplex their responses back over the
single client connection. The Tokio event loop is nicely self-contained
to the one or, optionally, more threads it is allocated, so the rest of
the application doesn't need to be aware of it.
Use of Tokio is becoming a standard in the Rust ecosystem---it's worth
mentioning that Mozilla funds Tokio development [9] and employs some of
its primary developers. Servo currently depends on an older version of
the hyper HTTP client/server library, and consequently this is already
present in the Firefox tree. The current release of hyper is built on
top of Tokio, so upgrading hyper, either as maintenance or to take
advantage of the forthcoming HTTP/2 support, would require pulling in
Tokio anyway. The current release of rust-websocket, from which Servo
derives its WebSockets implementation, also supports Tokio.
The alternative to Tokio for the networking layer would likely be to
build on top of Necko. This presents its own share of problems.
No Rust bindings to Necko currently exist, so writing those in the first
place would become a prerequisite for the rest of the work, and may
require integration with technologies like XPCOM which also lacks Rust
support. Then rewriting the CDP networking layer on top of Necko would
duplicate effort between the CDP implementations for Gecko and Servo,
the latter of which does not presently use Necko and may not be able to
take it on as a dependency.
Furthermore, it is my understanding through conversations with others
that while Necko's HTTP client implementation is mature, what HTTP
*server* implementation exists is bare-bones, and that at present there
is no support for upgrading HTTP connections to WebSockets in order to
implement a WebSocket server. Then building on top of Necko would entail
effectively developing a new library within Necko, when we could instead
adopt existing technologies (Tokio, hyper, rust-websocket) which are
already in use by other Mozilla projects and the surrounding Rust ecosystem.
Feedback, suggestions, and guidance are very much welcome!
https://bugzil.la/1391465 is the bug for the CDP server implementation
in Firefox.
-Michael Smith [:mismith]
[0]
https://chromedevtools.github.io/devtools-protocol/
[1]
https://github.com/servo/servo/pull/18133
[2]
https://github.com/devtools-html/cdp
[3]
https://github.com/devtools-html/cdp/blob/master/json/browser_protocol.json
[4]
https://www.spinda.net/files/mozilla/cdp/doc/cdp/tools/index.html
[5]
https://github.com/devtools-html/cdp/blob/master/update_json.sh
[6]
https://github.com/devtools-html/tokio-cdp
[7]
https://github.com/dtolnay/quote
[8]
https://github.com/serde-rs/serde
[9]
https://blog.mozilla.org/blog/2017/04/10/mozilla-awards-365000-to-open-source-projects-as-part-of-moss/