RE: Defining a constructor for Element and friends

I've updated my element constructors sketch at https://github.com/domenic/element-constructors/blob/master/element-constructors.js with a design that means no subclasses of HTMLElement (including the built-in elements) need to override their constructor or [Symbol.species](). It also uses an options argument for the constructors so it is more extensible in the future (e.g. Yehuda's attributes (or was it properties?) argument).

It mostly doesn't delve into custom elements, but does contain a small sample to illustrate how their don't need to override the constructor or [Symbol.species] either, and how their constructor works exactly the same as that of e.g. HTMLParagraphElement.

One interesting thing that falls out of the design is that it's trivial to allow a custom element class to be registered for multiple names; it requires no more work on the part of the class author than writing a class that corresponds to a single name, and is painless to use for consumers.

-----Original Message-----
From: Domenic Denicola [mailto:d@domenic.me] 
Sent: Friday, January 9, 2015 20:01
To: Anne van Kesteren; WebApps WG; www-dom@w3.org
Subject: RE: Defining a constructor for Element and friends

OK, so I've thought about this a lot, and there was some discussion on an unfortunately-TC39-private thread that I want to put out in the open. In [1] I outlined some initial thoughts, but that was actually a thread on a different topic, and my thinking has evolved.
 
[1]: http://lists.w3.org/Archives/Public/public-webapps/2015JanMar/0035.html


I was writing up my ideas in an email but it kind of snowballed into something bigger so now it's a repo: https://github.com/domenic/element-constructors


One primary concern of mine is the one you mention:

> whether it is acceptable to have an element whose name is "a", namespace is the HTML namespace, and interface is Element

I do not really think this is acceptable, and furthermore I think it is avoidable.

In the private thread Boris suggested a design where you can do `new Element(localName, namespace, prefix)`. This seems necessary to explain how `createElementNS` works, so we do want that. He also suggested the following invariants:

1.  The localName and namespace of an element determine its set of internal slots.
2.  The return value of `new Foo` has `Foo.prototype` as the prototype.

I agree we should preserve these invariants, but added a few more to do with keeping the existing (localName, namespace) <-> constructor links solid.

I've outlined the added invariants in the readme of the above repo. Other points of interest:

- Explainer for a very-recently-agreed-upon ES6 feature that helps support the design: https://github.com/domenic/element-constructors/blob/master/new-target-explainer.md

- Jump straight to the code: https://github.com/domenic/element-constructors/blob/master/element-constructors.js

- Jump straight to the examples of what works and what doesn't: https://github.com/domenic/element-constructors/blob/master/element-constructors.js#L194


One ugly point of my design is that the constructor signature is `new Element(localName, document, namespace, prefix)`, i.e. I require the document to be passed in. I am not sure this is necessary but am playing it safe until someone with better understanding tells me one way or the other. See https://github.com/domenic/element-constructors/issues/1 for that discussion.

---

As for how this applies to custom elements, in the private thread Boris asked: 

> what is the use case for producing something that extends HTMLImageElement (and presumably has its internal slots?) but doesn't have "img" as the tag name and hence will not have anything ever look at those internal slots?

Elsehwere on this thread or some related one IIRC he pointed out code that looks at the local name, finds "img", and casts to the C++ backing representation of HTMLImageElement. So from what I am gathering in his view the parts of the platform that treat <img> elements specially currently work by checking explicitly that something has local name "img" (and HTML namespace).

From a naïve authoring point of view that seems suboptimal. I'd rather be able to do `class MyImg extends HTMLImageElement { constructor(document) { super(document); } }` and have MyImg instances treated specially by the platform in all the ways "img" currently is.

Or, for an easier example, I'd like to be able to do `class MyQ extends HTMLQuoteElement { constructor(document) { super(document); } }` and have `(new MyQ()).cite` actually work, instead of throw a "cite getter incompatible with MyQ" error because I didn't get the HTMLQuoteElement internal slots.

The logical extension of this, then, is that if after that `document.registerElement` call I do `document.body.innerHTML = <my-q cite="foo">blah</my-q>` I'd really like to see `document.querySelector("my-q").cite` return `"foo"`.

However this idea that we'd like custom elements which inherit from existing elements to have their internal slots ties in to the whole upgrading mess, which seems quite hard to work around. So maybe it is not a good idea? On the other hand upgrading might be borked no matter what, i.e. it might not be possible at all to make upgraded elements behave anything like parsed-from-scratch elements. (I am planning to think harder about the upgrading problem but I am not hopeful.)

Received on Thursday, 15 January 2015 23:35:13 UTC