document.register and ES6

The way document.register is currently proposed makes it
future-hostile to ES6. I've heard several people from different
organizations say that this is a blocking issue.

Over the last couple of days we (me, Dimitri and others) have worked
on some alterations to the current spec proposal. The discussion got
pretty extensive so I'll try to summarize the main points.

https://www.w3.org/Bugs/Public/show_bug.cgi?id=20831

With ES6 we really want to be able to write code like this:

class MyButton extends HTMLButtonElement {
  ...
}
document.register('x-button', MyButton);

In ES6 speak, we have split the "new Foo(...args)" expression into
"Foo.call(Foo[@@create](), ...args)" which means that creating the
instance has been separated from the call to the function. This allows
us to subclass Array etc. It also opens up possibilities to subclass
Element. All Element need is a @@create method that creates the
instance (and sets the internal pointer to the underlying C++ object
like we do today). For custom elements we can therefore generate the
@@create method for the function passed to document.register. This
function would create an instance in the same way as previously speced
at https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/index.html#dfn-custom-element-instantiation

== What about ES5/3? ==

In ES5/3 speak we don't have the @@create refactoring but we do have
an internal [[Construct]] method. In the reformed version of
document.create we can override [[Construct]] of the passed in
function to create the instance and then do the [[Call]].

This also means API change from what is currently specified. Instead of

document.register(‘x-button’, { prototype:
Object.create(HTMLButtonElement.prototype, {
  ...
});

We will have:

function MyButton() {
  HTMLButtonElement.call(this);
  // yay, I can have a real constructor!
  // do constructor stuff ...
}
MyButton.prototype = Object.create(HTMLButtonElement.prototype, {
  ...
});
document.register(‘x-button’, MyButton);

We think it’s much better, because:
a) we no longer have to spit out some magic generated constructor
b) we can let developers have a constructor in their custom element
c) there will be no API changes when ES6 classes arrive
d) there is no longer a need for crazy callbacks “created” and
“shadowRootCreated”, because they can just be code in the constructor

== Does this mean that the user code now runs while parsing? ==

We’ve heard in the past that allowing user code execution while the
parser is building a tree is undesirable due to performance and
specific design issues. However, now that the custom element
constructor is no longer generated, it may appear as if the
user-specified constructor would run when each element is
instantiated.

We intend to address this as follows:
* When the parser builds a tree, it only creates underlying C++ objects
* Just before entering script,
* we first instantiate all custom elements (think Object.create, but
with all the baggage of being a wrapper around a C++ object), so that
they all have the right prototype chains, in tree order
* then, we invoke the respective internal [[Call]] methods of all
custom elements, in tree order

How does template fit into this?

The dependencies on template and shadow DOM are now removed from
document.register API. If people want to use a template and shadow DOM
they can easily do this in code:

class MyButton extends HTMLButtonElement {
  constructor() {
    super();
    var template = ...
    var shadowRoot = this.createShadowRoot();
    shadowRoot.appendChild(template.content.cloneNode(true));
  }
}
document.register('x-button', MyButton);


--
erik

Received on Tuesday, 5 February 2013 22:13:38 UTC