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-...@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.)

Reply via email to