How should we deal with a single tag that can have multiple XBL bindings when migrating to Custom Elements?

141 views
Skip to first unread message

Brian Grinstead

unread,
Apr 12, 2018, 7:00:12 PM4/12/18
to firefox-dev
I've been thinking about how we can deal with situation where a single tag name gets multiple bindings attached based on a selector. For instance [0]:

* `textbox` gets the `textbox` binding
* `textbox[type="number"]` gets the `numberbox` binding
* `textbox[type="search"]` gets the `search-textbox` binding
* etc

With Custom Elements, there are two approaches I've thought of:
1) Make either 3 different tags (`textbox`, `textbox-number`, `textbox-search`)
2) Fatten up the base binding to also keep track of if we are a number box, searchbox, etc.

Both could work, but neither seems great - the former means we need to duplicate CSS selectors and modify any JS / C++ that’s looking for the tag name, and the latter could get hard to maintain with the number of widgets we have (including page-specific ones that override the main toolkit ones). Any other ideas on how to deal with this?

One thing I noticed is that the spec is that there's a concept of 'customized builtin elements’ [1] where you can have things like:
```
class PlasticButton extends HTMLButtonElement { }
customElements.define("plastic-button", PlasticButton, { extends: "button" });

<button is="plastic-button">Click Me!</button>
```

Which seems like it could map well onto our XBL inheritance if we could do:
```
class Textbox extends XULElement { }
customElements.define(“textbox", Textbox);

class SearchTextbox extends Textbox { }
customElements.define("search-textbox", SearchTextbox, { extends: "textbox" });

<textbox />
<textbox is="search-textbox" />
```

However, that is not allowed as per 'If extends is a valid custom element name, then throw a "NotSupportedError" DOMException’ [2]. Is there a technical reason why we couldn't expose the `is` semantics used by customized builtins for extending other Custom Elements?

Thanks,
Brian

[0]: https://bgrins.github.io/xbl-analysis/tree/#textbox
[1]: https://www.w3.org/TR/custom-elements/#custom-elements-customized-builtin-example
[2]: https://searchfox.org/mozilla-central/rev/4114ad2cfcbc511705c7865a4a34741812f9a2a9/dom/base/CustomElementRegistry.cpp#731

_______________________________________________
firefox-dev mailing list
firef...@mozilla.org
https://mail.mozilla.org/listinfo/firefox-dev

Message has been deleted

Dave Townsend

unread,
Apr 12, 2018, 7:09:30 PM4/12/18
to Brian Grinstead, firefox-dev
I don't think the customized built-in element is the right option. If I understand the spec correctly you'd just be replacing all the lifecycle events of the underlying element anyway.

Do you know how often do we switch between bindings like this? I'd lean towards just using different tag names, seems likely we could script a rewrite in most cases. Switching at runtime is a problem (in any case I'd imagine).

Emilio Cobos Álvarez

unread,
Apr 12, 2018, 7:21:10 PM4/12/18
to firef...@mozilla.org
On 04/13/2018 01:01 AM, Brian Grinstead wrote:
> I've been thinking about how we can deal with the situation where a single tag name can get multiple bindings attached based on a selector. For instance [0]:
>
> * `textbox` gets the `textbox` binding
> * `textbox[type="number"]` gets the `numberbox` binding
> * `textbox[type="search"]` gets the `search-textbox` binding
> * etc
>
> For Custom Elements there are two approaches I've thought of:
> 1) Make 3 different tags (`textbox`, `textbox-number`, `textbox-search`)
> 2) Fatten up the base element class to also keep track of if we are a number box, searchbox, etc.
>
> Both could work, but neither seems great - the former means we need to duplicate CSS selectors and modify any JS / C++ that’s looking for the tag name to also look for the new ones, and the latter could get hard to maintain with the number of widgets we have (including page-specific ones that override the main toolkit ones). Any other ideas on how to deal with this?

Only some ideas (I don't think they're perfect).

For the purpose of not duplicating selectors, couldn't you add a common
class or something on the connected callback, maybe? It would need to be
a class or what not for now, though :host could make it a bit nicer with
Shadow DOM I guess, at least in terms of styling the component itself.

We support that now, fwiw, but not sure if Shadow DOM is enabled in
chrome yet, I guess not.

For code looking for the tagname and such, I guess you could also modify
them to use instanceof TextBox or something like that, which should be
less repetitive than enumerating tags... But I don't have the context
for how this kind of element is used on the frontend.

-- Emilio

Brian Grinstead

unread,
Apr 13, 2018, 12:17:52 PM4/13/18
to Dave Townsend, firefox-dev

> On Apr 12, 2018, at 4:09 PM, Dave Townsend <dtow...@mozilla.com> wrote:
>
> I don't think the customized built-in element is the right option. If I understand the spec correctly you'd just be replacing all the lifecycle events of the underlying element anyway.

In this case I don’t want ‘customized built-in’ exactly, but I want ‘customized autonomous custom element’ or something like that. So: register an autonomous Custom Element (say, “moz-toolbarbutton"), then be able to support <moz-button is=“ toolbarbutton-badged”>, where toolbarbutton-badged is registered to a class that extends the moz-button class. This is not allowed by the spec, but I’m curious if it’s something that’s worth considering (or if it’s already been discussed).

> Do you know how often do we switch between bindings like this? I'd lean towards just using different tag names, seems likely we could script a rewrite in most cases. Switching at runtime is a problem (in any case I'd imagine).

It's hard to say exactly given the nature of binding via CSS selectors, but from looking at the tree from https://bgrins.github.io/xbl-analysis/tree/ I see the following tags that appear to support more than one binding and approximately how many different bindings get attached to them:

- label (~3)
- menuitem (~3)
- menu (~5)
- toolbarbutton (~5)
- button (~4)
- listitem (~3)
- richlisitem (~15)
- arrowscrollbox (~3)
- menulist (~4)
- richlistbox (~3)
- tree (~2)
- treecol (~2)
- tabs (~2)
- listcell (~3)
- menupopup (~4)
- panel (~6)
- popupnotification (~2)
- textbox (~8)
- browser (~4)
- tabpanels (~2)
- notification (~2)
- progressmeter (~2)
- marquee (~5)
- datetimebox (~2)
- toolbar (~5)
- videocontrols (~2)

Brian

Brian Grinstead

unread,
Apr 13, 2018, 12:24:24 PM4/13/18
to Dave Townsend, firefox-dev

> On Apr 13, 2018, at 9:17 AM, Brian Grinstead <bgrin...@mozilla.com> wrote:
>
> So: register an autonomous Custom Element (say, “moz-toolbarbutton"), then be able to support <moz-button is=“ toolbarbutton-badged”>, where toolbarbutton-badged is registered to a class that extends the moz-button class

Correcting a typo - "moz-button" should have been "moz-toolbarbutton".

Dão Gottwald

unread,
Apr 13, 2018, 12:27:05 PM4/13/18
to Brian Grinstead, firefox-dev, Dave Townsend
2018-04-13 18:17 GMT+02:00 Brian Grinstead <bgrin...@mozilla.com>:
where toolbarbutton-badged is registered to a class that extends the moz-button class. This is not allowed by the spec, but I’m curious if it’s something that’s worth considering (or if it’s already been discussed).

My possibly wrong understanding is that this isn't allowed in order to keep things simple and avoid previous pitfalls that killed XBL2. It does make Custom Elements simpler but also less powerful. Perhaps we can just lift that restriction for chrome.

Paolo Amadini

unread,
Apr 14, 2018, 6:27:24 AM4/14/18
to Brian Grinstead, Dave Townsend, firefox-dev
On 4/13/2018 5:17 PM, Brian Grinstead wrote:
> In this case I don’t want ‘customized built-in’ exactly, but I want ‘customized autonomous custom element’ or something like that.

In fact my impression is that for XUL we'd need a combination of the two
concepts, in order to make a more incremental transition from the
current state to an implementation that is closer to HTML.

As Brian mentioned, the localName is currently used by platform code to
attach special behavior, for example accessibility, to elements like
"listitem" and "richlisitem". We are also looking into subclassing
XULElement in the platform to add C++-implemented methods to elements
like "panel" and "menupopup". This would in fact make them analogous
to "built-in elements" in HTML. My guess is that preserving this
platform behavior, hard-coded on the localName, is why "customized
built-in elements" exist for HTML in the first place.

The only difference from HTML here is that the base element itself,
the one without the "is" attribute, would also be implemented with a
corresponding JavaScript class extending XULElement, or a more specific
subclass for cases like "panel". This is basically the "non-dashed
custom elements" idea that we applied to facilitate the transition.

This also aligns with the concept that in the future some HTML elements
may be implemented in the user agent as JavaScript classes similar to
custom elements, either partially or entirely. So we would have:

class Panel extends XULPanelElement
class ArrowPanel extends Panel

customElements.define("panel", Panel);
customElements.define("arrow-panel", ArrowPanel, { extends: "panel" });

<panel/>
<panel is="arrow-panel"/>

document.createElement("panel");
document.createElement("panel", { is: "arrow-panel" });

How much effort would it be to support this scenario in XUL?

Cheers,
Paolo

Tim Guan-tin Chien

unread,
Apr 15, 2018, 1:04:27 PM4/15/18
to Paolo Amadini, Brian Grinstead, firefox-dev, Dave Townsend
Do we have a concept of XUL base elements in C++? Presentation-wise I can find sXULTagData in nsCSSFrameConstructor [1]. For accessibility, I can find sXULMarkupMapList (the one kept in XULMap.h).


If we want to go this route, should we start locking down that list? That would help us normalize XUL & XUL element inheritance.

Gijs Kruitbosch

unread,
Apr 16, 2018, 5:48:34 AM4/16/18
to Dave Townsend, Brian Grinstead, firefox-dev
On 13/04/2018 00:09, Dave Townsend wrote:
> Do you know how often do we switch between bindings like this? I'd
> lean towards just using different tag names, seems likely we could
> script a rewrite in most cases. Switching at runtime is a problem (in
> any case I'd imagine).
FWIW, heavy +1 on this.

I don't think we (deliberately) switch between bindings at runtime all
that frequently for the XBL case, and if/when it does happen it gives
you headaches trying to reason about what happens when. E.g.: Which (if
any) of the constructors/destructors run? What happens to fields whose
values are the default, with either the same/different default in the
new binding? What about identically named fields with different defaults
where the value has been set to a custom value? To be clear, there are
"expected" answers to those questions (which themselves aren't
necessarily immediately obvious, also to people new to the
codebase/XBL/CE), but I think very few if any people have confidence
that the implementation provides expected behaviour.

For the panel case that Paolo brought up in the subthread, we have some
5-10 attributes on the element that subtly change various aspects of
focus/overlap/shadow/collapsing -- and that mix really doesn't help
readability, and understanding why one panel behaves differently from
another (or which ones you "need" when you're building a new panel). For
the cases where the binding (and/or native behaviour, in the case of
panels!) alters based on an attribute (be it "type" in today's world or
"is" in the custom elements one), I don't think having attributes select
different "subclasses" of custom elements will do anything to help that
problem. Having different tagnames and being conservative in the number
of fundamentally different elements we build/provide will help make
distinctions / expected behaviour more obvious and straightforward.

~ Gijs

Paolo Amadini

unread,
Apr 16, 2018, 7:41:56 AM4/16/18
to Gijs Kruitbosch, Dave Townsend, Brian Grinstead, firefox-dev
On 4/16/2018 10:48 AM, Gijs Kruitbosch wrote:
> On 13/04/2018 00:09, Dave Townsend wrote:
>> Do you know how often do we switch between bindings like this? I'd
>> lean towards just using different tag names, seems likely we could
>> script a rewrite in most cases. Switching at runtime is a problem (in
>> any case I'd imagine).
> FWIW, heavy +1 on this.

Just to clarify, it is not possible to change a Custom Element class at
runtime by changing the "is" attribute, it has to be specified at the
time of the construction of the element.

> For the panel case that Paolo brought up in the subthread, we have some
> 5-10 attributes on the element that subtly change various aspects of
> focus/overlap/shadow/collapsing -- and that mix really doesn't help
> readability, and understanding why one panel behaves differently from
> another (or which ones you "need" when you're building a new panel). For
> the cases where the binding (and/or native behaviour, in the case of
> panels!) alters based on an attribute (be it "type" in today's world or
> "is" in the custom elements one), I don't think having attributes select
> different "subclasses" of custom elements will do anything to help that
> problem. Having different tagnames and being conservative in the number
> of fundamentally different elements we build/provide will help make
> distinctions / expected behaviour more obvious and straightforward.

I can see how different tag names can sometimes help with somewhat
different elements, for example this is already the case for <popup>,
<panel>, and other elements that all get the native "popup" behavior:

https://dxr.mozilla.org/mozilla-central/rev/e96685584bf7d3c1d7a4c1861716da89fd650c51/dom/base/nsDocument.cpp#6576-6580

However, when the number of possible element names is unbound, you would
have to provide the platform with another way of attaching the native
behavior to the element. This may also be needed when the number of
variants is limited, but we still want to customize the inner structure
of some instances. One example is the "popup-scrollbars" binding, which
I hope we can remove, but currently exist to support drag-to-scroll.

So the alternatives would be:

1) <download-richlistitem behavior="richlistitem">
<scrollbars-popup behavior="popup">

2) <richlistitem is="download-richlistitem">
<popup is="scrollbars-popup">

You may have a custom element class set the "behavior" attribute at
runtime, but you would have support changing the behavior after
construction. I'm not sure what the performance impact of this would be.

For (1) you also have to style using the class instead of the tag name,
which means that the binding would have to add class="richlistitem" or
something similar in the connectedCallback. We would have to convert
the styling manually and adjust for specificity, which is what we
tried to avoid by moving XBL stylesheets to UA stylesheets instead of
regular stylesheets.

In fact, while I can see that (1) is closer to the current spec if we
don't consider the native behavior variable, solution (2) would speed up
transitioning the current XBL bindings to privileged Custom Elements
because it would allow us to make much less changes to the platform and
the various element subclasses that we have right now.

Cheers,
Paolo

Paolo Amadini

unread,
Apr 16, 2018, 7:48:54 AM4/16/18
to Gijs Kruitbosch, Dave Townsend, Brian Grinstead, firefox-dev
On 4/16/2018 12:39 PM, Paolo Amadini wrote:
> 1) <download-richlistitem behavior="richlistitem">
>    <scrollbars-popup behavior="popup">
>
> For (1) you also have to style using the class instead of the tag name,
> which means that the binding would have to add class="richlistitem" or
> something similar in the connectedCallback.

And of course we'd have to audit all the usages of in-tree selectors,
like .closest("richlistitem") should become .closest(".richlistitem"),
as Brian mentioned in the original post.

Brian Grinstead

unread,
Apr 18, 2018, 1:35:22 PM4/18/18
to Dão Gottwald, firefox-dev, Dave Townsend

Couldn’t find any previous discussion about this (it’s been this way since v0) so I filed https://github.com/w3c/webcomponents/issues/750.

Brian

Reply all
Reply to author
Forward
0 new messages