XBL is a technology used to implement reusable widgets in XUL. Web Components are a set of specifications that provide modern and standards-based alternatives for doing the same thing with HTML. For the purposes of this document, “Web Components” refers to the Custom Elements and Shadow DOM specs, which map relatively closely to how the Firefox frontend is using XBL.
What follows is an initial analysis of what a migration away from XBL and to Web Components may look like. We will need to spend more time prototyping this within the Firefox UI to get a clearer picture about the viability of a migration.
Most of the relevant bindings are declared in toolkit/content/widgets.
This analysis is focused on widely used and relatively small XBL components that could potentially be swapped out more-or-less in place with corresponding Web Components. For instance: buttons, labels, input fields, etc.
Components that are only used once or are implemented mostly in C++ are ignored in this analysis. For instance: tabbrowser, trees, etc. Most of the same principles that apply to smaller components should also apply to them, but we may also consider different options for moving them away from XBL.
From the Introduction to XBL, a binding has five types of things that it declares: Content, properties, methods, events, and style. But it’s helpful to look at specific features used in Firefox and comparing how they map back to Web Components.
Which bindings use which features is visualized at https://bgrins.github.io/xbl-analysis/. The scripts to generate that tree and also to semi-automate translation of bindings can be found at https://github.com/bgrins/xbl-analysis.
<property>
: These map to JS getters and setters on the Custom Element class (‘val’ name is magic in xbl, would need to keep same name to translate directly)<method>
: These map to functions on the Custom Element class<constructor>
This maps to connectedCallback on the Custom Element class (which is called when the element is inserted into a document).
In XBL, the constructor fires after the element is inserted into the document (since the binding is attached through a CSS property). However, the constructor will wait some additional time to fire, unless if the element is accessed via JS in which case it will run immediately<destructor>
: These map to disconnectedCallback
on the Custom Element class[extends]
: A way to extend the functionality of another binding, i.e. extends="chrome://global/content/bindings/general.xml#basetext"
. On a surface level this appears to map directly to how the ‘extends’ keyword in ES6 classes behavior with Custom Elements, but more investigation is needed. There’s a small set of tags where you can do extends="xul:button"
but that’s aliasing the same thing.<handlers>
: Can be implemented with addEventListener or possibly a helper on the base element i.e:
helpers: {
"click": () => { /* bubble by default*/ }
"focus|capturing": () => { /* copy in xbl implementation */ }
}
[inherits]
: Is a way to declaratively copy attributes from a binding parent to content and keep them in sync, similar to the <observes>
element. For example, this copies the feedURL from the parent into the ‘value’ field of the label and also observes future changes: <feed feedURL="http://foo.com"><xul:label xbl:inherits="value=feedURL,tooltiptext=feedURL"></feed>
. The same thing could be built on top of Custom Elements using a MutationObserver and some glue code to set it up in the connectedCallback
.[implements]
: This is a way for elements with XBL bindings to implement an XPCOM interface. XBL also auto generates the Qi bits for you. One example usage is that labels implement complex cropping behavior via implements="nsIDOMXULLabelElement"
, but there are many others (panels, menus, scrollbars). There are around 39 instances of this feature, and this will need to be handled on a case-by-case basis. But we have some options:implements="nsIObserver"
with just a separate object that in fact implements it-moz-binding
allows a XBL binding to be attached or detached to any element through CSS. Web Components would be tied to HTML (based on the particular tag being created). This feature is used in at least one place to swap an element to a different binding when setting a property, but we can work around it.How many times are individual bindings used? We’ve written a module to track how often XBL (and XUL) are used, and a modified version of the test runner that keeps a count throughout the mochitest-browser suite. A sorted list of used bindings is attached on the bug, but here are the top few bindings:
url("chrome://global/content/bindings/text.xml#text-label")
url("chrome://global/content/bindings/general.xml#image")
url("chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton")
url("chrome://global/content/bindings/menu.xml#menuitem")
url("chrome://global/content/bindings/scrollbox.xml#autorepeatbutton")
url("chrome://global/content/bindings/button.xml#button")
XUL and XBL both use fastload and they both have prototype cache, so loading them and opening new windows should be quite fast. Custom Elements (and Shadow DOM) don’t have anything like that right now. If we want to use them in browser chrome, we may need to figure out how to support things like this.
The first step is getting some measurements to compare things like element creation time, memory usage, etc. This work is being tracked in Bug 1387125.