XPCOM components and the move to WebIDL
Published:
If you have ever read through the Firefox source code, you have run into two kinds of interface files: .idl files full of nsI-prefixed interfaces, and .webidl files that look almost exactly like the specs on the W3C and WHATWG sites. Both describe APIs that C++ and JavaScript can use, but they come from two different eras of Mozilla’s architecture. This post walks through what XPCOM is, why the DOM outgrew it, and how the move to WebIDL works.
What XPCOM is
XPCOM, the Cross Platform Component Object Model, is the component system Mozilla built in the late 1990s for the Gecko engine. It is modelled on Microsoft’s COM. Everything is built around interfaces, and every interface ultimately inherits from nsISupports, which provides three methods: QueryInterface, to ask an object whether it supports another interface, and AddRef and Release, for reference counting.
Interfaces are written in XPIDL, Mozilla’s interface definition language, in .idl files. Each interface has a unique IID, a UUID that identifies it. A build step turns each .idl file into a C++ header with an abstract class, plus type information that describes the interface at runtime.
Concrete implementations are components. A component has a class ID, also a UUID, and usually a human-readable contract ID such as @mozilla.org/network/io-service;1. Code asks the component manager for a service or a new instance by contract ID, then calls QueryInterface to get the interface it needs. The caller never has to know which class, library or language actually implements it.
XPConnect: the bridge to JavaScript
The piece that made XPCOM so powerful for Firefox was XPConnect. Using the type information generated from XPIDL, XPConnect lets JavaScript call XPCOM objects written in C++, and lets C++ call components written in JavaScript. For years that type information shipped as binary .xpt files; around 2018 Mozilla replaced them with data generated at build time and compiled straight into the binary, which made startup faster and simpler.
That bridge is why so much of Firefox itself, including its front end, preferences, bookmarks and add-on system, could be written in JavaScript while talking to a C++ engine. It is also why classic Firefox extensions were so capable: they could reach almost any internal interface. That ended with Firefox 57 in 2017, when legacy XPCOM-based add-ons were retired in favour of WebExtensions.
Why the DOM outgrew XPCOM
Originally the DOM itself was exposed to web pages through XPCOM. There were interfaces such as nsIDOMNode, nsIDOMElement and nsIDOMDocument, and every call from a page’s script into the DOM went through XPConnect. That design had real costs.
It was slow. Each call went through a generic layer that looked up type information, converted arguments and dispatched through virtual methods. Mozilla added hand-tuned fast paths, known as quick stubs, for the hottest methods, but the overhead never fully went away, and it mattered more as sites became JavaScript-heavy.
It also did not match the web. Web standards describe their APIs in Web IDL, the interface language used throughout the HTML, DOM and other specs. Web IDL has concepts XPIDL never had, such as dictionaries, overloads, union types, sequences, promises and precise rules for how JavaScript values convert to each type. Translating every spec into XPIDL by hand invited subtle differences from other browsers, and web compatibility depends on getting those details exactly right.
The new DOM bindings
Starting around 2012, Mozilla built a new binding layer, sometimes called the Paris bindings after the work week where the design took shape. Instead of routing the DOM through XPCOM, each web API is described in a .webidl file, most of them in the dom/webidl directory, copied as closely as possible from the specs.
At build time a Python code generator in dom/bindings reads the WebIDL and produces C++ binding code for every interface: the JavaScript prototype objects, getters, setters and methods, argument conversion that follows the Web IDL rules exactly, and error handling that throws the right kind of exception. The binding code then calls a plain C++ class that implements the interface. That class does not have to inherit from any XPCOM interface; it just provides methods with the signatures the generator expects.
Because the bindings are generated specifically for each method, they are much faster than going through XPConnect. The generator can also give the SpiderMonkey JIT information about each getter and method, such as whether it has side effects, so hot DOM calls can be optimised directly from compiled JavaScript.
What moving an interface looks like
Converting an API from XPCOM to WebIDL follows a fairly consistent pattern. The interface is rewritten as a .webidl file that matches the spec. The C++ class drops its nsIDOM interface and gains a WrapObject method, along with the wrapper cache that ties the C++ object to its JavaScript reflection. Methods switch from the XPCOM style, where every call returns an nsresult error code and passes results back through pointer arguments, to ordinary return values, with errors reported through an ErrorResult parameter that the binding turns into a JavaScript exception.
Callers inside Gecko are updated to use the concrete C++ class instead of calling QueryInterface for an nsIDOM interface, and the old .idl file is eventually deleted. Over many years this clean-up, jokingly called deCOMtamination, removed the vast majority of the nsIDOM interfaces from the tree.
WebIDL in Gecko also grew extensions for browser-internal needs. Extended attributes such as [Pref] expose an interface only when a preference is enabled, [Func] runs a check function to decide whether an API is visible, and [ChromeOnly] hides members from web pages while keeping them available to Firefox’s own privileged code. That meant many internal APIs could move to WebIDL too, not just public web APIs.
Where XPCOM still fits
XPCOM has not gone away. It is still the backbone of Gecko’s internal services: networking, preferences, the observer service, storage and many more are XPCOM components defined in XPIDL, and Firefox’s front-end JavaScript still reaches them through XPConnect. The dividing line is roughly this: APIs the web can see, and privileged APIs that behave like DOM objects, use WebIDL; internal engine services use XPCOM.
Registration has modernised as well. Components that used to be registered through manifests at runtime are now mostly declared in components.conf files and turned into static tables at build time, which again cuts startup cost.
Why it matters
The move from XPCOM to WebIDL is a good example of a large codebase paying down architectural debt without a rewrite. The generic, language-neutral component system that made early Mozilla so flexible was the wrong tool for a DOM that has to be fast and match the specs to the letter. By generating bindings from the same interface language the standards use, Gecko got faster DOM calls, closer spec compliance and far less hand-written glue, while keeping XPCOM for the internal plumbing it still does well.