Re: Custom element design with ES6 classes and Element constructors

2015-01-27 Thread Yehuda Katz
Agree with this completely.

Yehuda Katz
(ph) 718.877.1325

On Tue, Jan 27, 2015 at 9:32 PM, Domenic Denicola  wrote:

> From: Elliott Sprehn [mailto:espr...@google.com]
>
> > Perhaps, but that logically boils down to "never use string properties
> ever" just in case some library conflicts with a different meaning. We'd
> have $[jQuery.find](...) and so on for plugins.
>
> Nah, it boils down to "don't use string properties for meta-programming
> hooks."
>
> > Or more concretely isn't the new DOM Element#find() method going to
> conflict with my 's find() method? So why not make that
> [Element.find] so polymer never conflicts?
>
> You can overwrite methods on your prototype with no problem. The issue
> comes when the browser makes assumptions about what user-supplied methods
> (i.e., metaprogramming hooks) are supposed to behave like.
>
> More concretely, if there was browser code that *called*
> arbitraryElement.find(), then we'd be in a lot more trouble. But as-is
> we're not.
>
> (BTW find() was renamed to query(), IIRC because of conflicts with
> HTMLSelectElement or something?)
>


RE: Custom element design with ES6 classes and Element constructors

2015-01-27 Thread Domenic Denicola
From: Elliott Sprehn [mailto:espr...@google.com] 

> Perhaps, but that logically boils down to "never use string properties ever" 
> just in case some library conflicts with a different meaning. We'd have 
> $[jQuery.find](...) and so on for plugins.

Nah, it boils down to "don't use string properties for meta-programming hooks."

> Or more concretely isn't the new DOM Element#find() method going to conflict 
> with my 's find() method? So why not make that 
> [Element.find] so polymer never conflicts?

You can overwrite methods on your prototype with no problem. The issue comes 
when the browser makes assumptions about what user-supplied methods (i.e., 
metaprogramming hooks) are supposed to behave like. 

More concretely, if there was browser code that *called* 
arbitraryElement.find(), then we'd be in a lot more trouble. But as-is we're 
not.

(BTW find() was renamed to query(), IIRC because of conflicts with 
HTMLSelectElement or something?)


Re: Custom element design with ES6 classes and Element constructors

2015-01-27 Thread Elliott Sprehn
On Tuesday, January 27, 2015, Domenic Denicola  wrote:

> It does. If a framework says “use clonedCallback and we will implementing
> cloning for you,” we cannot add a clonedCallback with our own semantics.
>
> Whereas, if a framework says “use [Framework.cloned] and we will implement
> cloning for you,” we’re in the clear.
>
> Better yet! If a framework is a bad citizen and says “we did
> Element.cloned = Symbol() for you; now use [Element.cloned] and we will
> implement cloning for you,” we are still in the clear, since the original
> Element.cloned we supply with the browser is not === to the Element.cloned
> supplied by the framework.
>
> This last is not at all possible with string-valued properties, since the
> string “clonedCallback” is the same no matter who supplies it.


Perhaps, but that logically boils down to "never use string properties
ever" just in case some library conflicts with a different meaning. We'd
have $[jQuery.find](...) and so on for plugins.

Or more concretely isn't the new DOM Element#find() method going to
conflict with my 's find() method? So why not make that
[Element.find] so polymer never conflicts?

- E


RE: Custom element design with ES6 classes and Element constructors

2015-01-27 Thread Domenic Denicola
It does. If a framework says “use clonedCallback and we will implementing 
cloning for you,” we cannot add a clonedCallback with our own semantics.

Whereas, if a framework says “use [Framework.cloned] and we will implement 
cloning for you,” we’re in the clear.

Better yet! If a framework is a bad citizen and says “we did Element.cloned = 
Symbol() for you; now use [Element.cloned] and we will implement cloning for 
you,” we are still in the clear, since the original Element.cloned we supply 
with the browser is not === to the Element.cloned supplied by the framework.

This last is not at all possible with string-valued properties, since the 
string “clonedCallback” is the same no matter who supplies it.



Re: Custom element design with ES6 classes and Element constructors

2015-01-27 Thread Elliott Sprehn
On Thursday, January 15, 2015, Domenic Denicola  wrote:

> Just to clarify, this argument for symbols is not dependent on modules.
> Restated, the comparison is between:
>
> ```js
> class MyButton extends HTMLElement {
>   createdCallback() {}
> }
> ```
>
> vs.
>
> ```js
> class MyButton extends HTMLElement {
>   [Element.create]() {}
> }
> ```


This doesn't save you anything, classes can have statics and the statics
inherit, so the .create will cause issues with name conflicts anyway.

We should probably introduce a new namespace if we want to do this.


>
> > We're already doing some crude namespacing with *Callback. I'd expect
> that as soon as the first iteration of Custom Elements is out, people will
> copy the *Callback style in user code.
>
> This is a powerful point that I definitely agree with. I would not be
> terribly surprised to find some library on the web already that asks you to
> create custom elements but encourages you supply a few more
> library-specific hooks with -Callback suffixes.
>
>


Re: Custom element design with ES6 classes and Element constructors

2015-01-27 Thread Goktug Gokdogan
I'm also curios why DOM mutation is a problem.

I read arguments like using the nodes as a key in a Map but such code is
already broken as a node can also be replaced with some user code; so such
code should put into account the node replacement.

I also don't understand how the two-tier construction ( +
createCallback) fixes the issue about accessing siblings and parents. One
might still write problematic code in createdCallback as they will use it
as a replacement for constructor (e.g. accessing a sibling and calling an
instance method can easily fail as createdCallback not called on the
element yet).

I think, overall, trying to abstract the upgrade process is a big band-aid;
makes the upgrade more magical while not hiding away all implications.
Developers still need to be aware of the upgrade process and program
accordingly so I think it is better to be more explicit.

To me it sounds saner to create custom elements as HtmlUnknownElement at
the parsing stage (or HtmlUnitializedElement if it is necessary to
distinguish them) and then explicitly upgrade them bottom up by first
running the constructors and then replace the existing nodes with newly
created ones. After the construction the event listeners and properties can
be copied over to the new nodes. By this way, anyone who wants to traverse
tree can easily identify uninitialized nodes and act accordingly (e.g. add
a listener for an 'initialized' event).


On Tue, Jan 13, 2015 at 1:07 PM, Yehuda Katz  wrote:

> On Tue, Jan 13, 2015 at 9:50 AM, Domenic Denicola  wrote:
>
>> From: Boris Zbarsky [mailto:bzbar...@mit.edu]
>>
>> > Just to be clear, this still didn't allow you to upgrade a  to
>> be a subclass of , because that required a change in allocation, right?
>>
>> Agreed. That needs to be done with , IMO. (Assuming the
>> upgrading design doesn't get switched to DOM mutation, of course.)
>>
>
> Can someone help outline for me exactly why DOM mutation is a problem
> here? I can definitely see downsides, but DOM mutation is a fact of life
> when scripts are involved on today's web, and it sidesteps a lot of the
> problems that we encounter by trying to make in-place upgrading (upgrades
> without changing the reference at all) work sanely.
>
> I mean, qSA might not work the way someone might expect, but it also might
> not work if you go `$("my-button").button()` using jQuery. What expectation
> do we imagine someone has here that we think is missing if we use DOM
> mutation (rather than object-model mutation) for upgrades.
>
> -- Yehuda
>
>


Re: Custom element design with ES6 classes and Element constructors

2015-01-16 Thread Anne van Kesteren
On Thu, Jan 15, 2015 at 8:02 PM, Dimitri Glazkov  wrote:
> Thanks for starting this page! One thing that seems missing is Steve
> Faulkner's concern about removing "is".

I added a note. It would be interesting to see what developers have
been doing so far. From what I've seen is="" is not used much or well
liked. And the example in the specification does not really see to
require is="" (it would work identically if that attribute was not
there).


-- 
https://annevankesteren.nl/



Re: Custom element design with ES6 classes and Element constructors

2015-01-15 Thread Dimitri Glazkov
>
>
> > We're already doing some crude namespacing with *Callback. I'd expect
> that as soon as the first iteration of Custom Elements is out, people will
> copy the *Callback style in user code.
>
> This is a powerful point that I definitely agree with. I would not be
> terribly surprised to find some library on the web already that asks you to
> create custom elements but encourages you supply a few more
> library-specific hooks with -Callback suffixes.
>
>
That makes sense. I am okay with that.

:DG<


Re: Custom element design with ES6 classes and Element constructors

2015-01-15 Thread Dimitri Glazkov
On Thu, Jan 15, 2015 at 2:37 AM, Anne van Kesteren  wrote:

> On Thu, Jan 15, 2015 at 5:11 AM, Yehuda Katz  wrote:
> > Can you say more about why same-identity upgrading is critical to the
> design
> > (as opposed to dom-mutation upgrading)? I asked up-thread but didn't get
> any
> > takers.
>
> I tried to summarize the various upgrade scenarios here (as well as
> the other issues):
>
>   https://wiki.whatwg.org/wiki/CustomElements


Thanks for starting this page! One thing that seems missing is Steve
Faulkner's concern about removing "is".

:DG<


RE: Custom element design with ES6 classes and Element constructors

2015-01-15 Thread Domenic Denicola
Just to clarify, this argument for symbols is not dependent on modules. 
Restated, the comparison is between:

```js
class MyButton extends HTMLElement {
  createdCallback() {}
}
```

vs.

```js
class MyButton extends HTMLElement {
  [Element.create]() {}
}
```

> We're already doing some crude namespacing with *Callback. I'd expect that as 
> soon as the first iteration of Custom Elements is out, people will copy the 
> *Callback style in user code.

This is a powerful point that I definitely agree with. I would not be terribly 
surprised to find some library on the web already that asks you to create 
custom elements but encourages you supply a few more library-specific hooks 
with -Callback suffixes.



Re: Custom element design with ES6 classes and Element constructors

2015-01-15 Thread Yehuda Katz
It's not clear to me that:

import HTMLElement from "web/element";

class MyButton extends HTMLElement {
  readyCallback() {}
}

is that much more usable than

import HTMLElement, { ready } from "web/element";

class MyButton extends HTMLElement {
  [ready]() {}
}

In other words, in a modules world, you're already importing the class, and
adding on a symbol isn't too bad.

We're already doing some crude namespacing with *Callback. I'd expect that
as soon as the first iteration of Custom Elements is out, people will copy
the *Callback style in user code.

We can always work around it with more obscure and more verbose names
(since libraries will slurp up the good names) but that damages the
ergonomic argument.

On Thu, Jan 15, 2015, 9:32 AM Dimitri Glazkov  wrote:

>
>> I'm sympathetic to this but I think it is fine that DOM continues to
>> define new string based property names. Anytime we add a new property
>> to an existing class we run into this issue and I don't think we want
>> to give up on the superior usability of string based property names.
>>
>
> I agree, FWIW.
>
> :DG<
>
>


Re: Custom element design with ES6 classes and Element constructors

2015-01-15 Thread Dimitri Glazkov
>
>
> I'm sympathetic to this but I think it is fine that DOM continues to
> define new string based property names. Anytime we add a new property
> to an existing class we run into this issue and I don't think we want
> to give up on the superior usability of string based property names.
>

I agree, FWIW.

:DG<


Re: Custom element design with ES6 classes and Element constructors

2015-01-15 Thread Erik Arvidsson
On Thu, Jan 15, 2015 at 1:09 AM, Dmitry Lomov  wrote:
>
>
> On Thu, Jan 15, 2015 at 5:11 AM, Yehuda Katz  wrote:
>>
>> On Wed, Jan 14, 2015 at 3:47 PM, Domenic Denicola  wrote:
>>
>> Isn't this at least a little future-hostile to things like `new
>> MyElement(attrs)`? Is there a way we could get back to that in the future,
>> in your mind?
>
>
> If this is what is desired, HTMLElement can pass all its arguments to
> createdCallback, i.e. call createdCallback(...args) in its constructor.
>
> Since default constructors pass all their arguments upstream,
>new MyElement(attrs)
> will pass attrs as arguments to HTMLElement constructor. HTMLElement
> constructor needs no arguments itself, so it will pass all of those to
> MyElement's createdCallback.

Another option is to provide a species function, which would be what
the UA calls.

One of the problems with just forwarding the args is that the default
arguments to HTMLElement constructor already has a bunch of
parameters, as in the strawman at
https://github.com/domenic/element-constructors/blob/master/element-constructors.js#L88

>> Can you say more about why same-identity upgrading is critical to the
>> design (as opposed to dom-mutation upgrading)? I asked up-thread but didn't
>> get any takers.

I think these points have been pointed out before but I'll list them again:

 * Identity - Elements used as keys in maps etc
 * Hidden state - cloneNode copies some internal state but generally
not enough (scroll position, selection, event listeners etc)
 * Mutation record spam
 * JS properties that are not reflected are lost

>>> On a final note, we could further bikeshed the name of createdCallback(),
>>> e.g. to either use a symbol or to be named something short and appealing
>>> like initialize() or create(), if that would make it even more appealing :)
>>
>>
>> I think symbols are actually pretty important here.
>>
>> To elaborate, I envision a future where Ember tries to move its Component
>> API to be "just a DOM subclass". So when you inherit from Ember's
>> `Component`, you're actually inheriting from a DOM `HTMLElement` too.
>>
>> document.register('my-element', class extends EmberComponent {
>>
>> });
>>
>>
>> But Ember already has a whole suite of methods and properties on
>> `Ember.Component`, some of which may accidentally conflict with these
>> callbacks (now and in the future).
>>
>> More generally, once people start writing libraries of HTMLElement
>> subclasses, our ability to add new callback names for all elements is going
>> to become pretty dicey, and we'll probably be forced into symbols anyway. We
>> may as well avoid a future inconsistency and just namespace DOM-supplied
>> callbacks separately from user-supplied properties and methods.

I'm sympathetic to this but I think it is fine that DOM continues to
define new string based property names. Anytime we add a new property
to an existing class we run into this issue and I don't think we want
to give up on the superior usability of string based property names.


-- 
erik



Re: Custom element design with ES6 classes and Element constructors

2015-01-15 Thread Anne van Kesteren
On Thu, Jan 15, 2015 at 5:11 AM, Yehuda Katz  wrote:
> Can you say more about why same-identity upgrading is critical to the design
> (as opposed to dom-mutation upgrading)? I asked up-thread but didn't get any
> takers.

I tried to summarize the various upgrade scenarios here (as well as
the other issues):

  https://wiki.whatwg.org/wiki/CustomElements

None of them seem particularly attractive :-(


> More generally, once people start writing libraries of HTMLElement
> subclasses, our ability to add new callback names for all elements is going
> to become pretty dicey, and we'll probably be forced into symbols anyway. We
> may as well avoid a future inconsistency and just namespace DOM-supplied
> callbacks separately from user-supplied properties and methods.

Agreed that now that we have symbols we should start using them to
avoid collisions.


-- 
https://annevankesteren.nl/



Re: Custom element design with ES6 classes and Element constructors

2015-01-14 Thread Dmitry Lomov
On Thu, Jan 15, 2015 at 5:11 AM, Yehuda Katz  wrote:

> On Wed, Jan 14, 2015 at 3:47 PM, Domenic Denicola  wrote:
>
>> I had a chat with Dmitry Lomov (V8 team/TC39, helped shape the new ES6
>> classes design, CC'ed). His perspective was helpful. He suggested a way of
>> evolving the current createdCallback design that I think makes it more
>> palatable, and allows us to avoid all of the teeth-gnashing we've been
>> doing in this thread.
>>
>> - Define a default constructor for HTMLElement that includes something
>> like:
>>
>>   ```js
>>   const createdCallback = this.createdCallback;
>>   if (typeof createdCallback === "function") {
>> createdCallback();
>>   }
>>   ```
>>
>> - Detect whether the constructor passed to document.registerElement is
>> the default ES class constructor or not. (By default, classes generate a
>> constructor like `constructor(...args) { super(...args); }`.) If it is not
>> the default constructor, i.e. if an author tried to supply one, throw an
>> error. This functionality doesn't currently exist in ES, but it exists in
>> V8 and seems like a useful addition to ES (e.g. as
>> `Reflect.isDefaultConstructor`).
>>
>
> This seems useful to me too. Something is nagging me about it though, as
> if there's a better way to help people with this use-case, but I'm not sure
> what.
>
>
>> - Define the HTMLElement constructor to be smart enough to work without
>> any arguments. It could do this by e.g. looking up `new.target` in the
>> registry. I can prototype this in
>> https://github.com/domenic/element-constructors.
>>
>
> Isn't this at least a little future-hostile to things like `new
> MyElement(attrs)`? Is there a way we could get back to that in the future,
> in your mind?
>

If this is what is desired, HTMLElement can pass all its arguments to
createdCallback, i.e. call createdCallback(...args) in its constructor.

Since default constructors pass all their arguments upstream,
   new MyElement(attrs)
will pass attrs as arguments to HTMLElement constructor. HTMLElement
constructor needs no arguments itself, so it will pass all of those to
MyElement's createdCallback.



>
>> With these tweaks, the syntax for registering an element becomes:
>>
>> ```js
>> class MyEl extends HTMLElement {
>>   createdCallback() {
>> // initialization code goes here
>>   }
>> }
>>
>> document.registerElement("my-el", MyEl);
>> ```
>>
>> Note how we don't need to save the return value of
>> `document.registerElement`. `new MyEl()` still works, since it just calls
>> the default `HTMLElement` constructor. (It can get its tag name by looking
>> up `new.target === MyEl` in the custom element registry.) And, `new MyEl()`
>> is equivalent to the parse-then-upgrade dance, since parsing corresponds to
>> the main body of the HTMLElement constructor, and upgrading corresponds to
>> proto-munging plus calling `this.createdCallback()`.
>>
>> Compare this to the "ideal" syntax that we've been searching for a way to
>> make work throughout this thread:
>>
>> ```js
>> class MyEl extends HTMLElement {
>>   constructor() {
>> super();
>> // initialization code goes here
>>   }
>> }
>>
>> document.registerElement("my-el", MyEl);
>> ```
>>
>> It's arguable just as good. You can't use the normal constructor
>> mechanism, which feels sad. But let's talk about that.
>>
>
> I think this works *perfectly* (kudos!)
>
Thanks!


> as long as we don't want to support constructor args being sent to
> readyCallback.
>

It seems to me this can still be supported, if I understand correctly what
do you want to to do.

Dmitry


> As a "framework" hook, that seems totally fine with me.
>

>
>> Whenever you're working within a framework, be it Rails or ASP.NET or
>> Polymer or "the DOM", sometimes the extension mechanism for the framework
>> is to allow you to derive from a given base class. When you do so, there's
>> a contract of what methods you implement, what methods you *don't*
>> override, and so on.
>
>
> Indeed. I've working on both Rails and Ember, and in both cases, it's *very
> rare* to subclass from the internal constructor. There's usually a hook
> (or event) API that subclasses can use so that the superclass
> implementation can provide a more ergonomic API to implementations.
>
>
>> When doing this kind of base-class extension, you're implementing a
>> specific protocol that the framework tells you to, and you don't have
>> complete freedom. So, it seems pretty reasonable if you're working within a
>> framework that says, "you must not override the constructor; that's my
>> domain. Instead, I've provided a protocol for how you can do
>> initialization." Especially for a framework as complicated and full of
>> initialization issues as the DOM.
>>
>
> Agreed.
>
>
>> This design seems pretty nice to me. It means we can get upgrading
>> (which, a few people have emphasized, is key in an ES6 modules world),
>
>
> Can you say more about why same-identity upgrading is critical to the
> design (as opposed to dom-m

Re: Custom element design with ES6 classes and Element constructors

2015-01-14 Thread Yehuda Katz
On Wed, Jan 14, 2015 at 3:47 PM, Domenic Denicola  wrote:

> I had a chat with Dmitry Lomov (V8 team/TC39, helped shape the new ES6
> classes design, CC'ed). His perspective was helpful. He suggested a way of
> evolving the current createdCallback design that I think makes it more
> palatable, and allows us to avoid all of the teeth-gnashing we've been
> doing in this thread.
>
> - Define a default constructor for HTMLElement that includes something
> like:
>
>   ```js
>   const createdCallback = this.createdCallback;
>   if (typeof createdCallback === "function") {
> createdCallback();
>   }
>   ```
>
> - Detect whether the constructor passed to document.registerElement is the
> default ES class constructor or not. (By default, classes generate a
> constructor like `constructor(...args) { super(...args); }`.) If it is not
> the default constructor, i.e. if an author tried to supply one, throw an
> error. This functionality doesn't currently exist in ES, but it exists in
> V8 and seems like a useful addition to ES (e.g. as
> `Reflect.isDefaultConstructor`).
>

This seems useful to me too. Something is nagging me about it though, as if
there's a better way to help people with this use-case, but I'm not sure
what.


> - Define the HTMLElement constructor to be smart enough to work without
> any arguments. It could do this by e.g. looking up `new.target` in the
> registry. I can prototype this in
> https://github.com/domenic/element-constructors.
>

Isn't this at least a little future-hostile to things like `new
MyElement(attrs)`? Is there a way we could get back to that in the future,
in your mind?


> With these tweaks, the syntax for registering an element becomes:
>
> ```js
> class MyEl extends HTMLElement {
>   createdCallback() {
> // initialization code goes here
>   }
> }
>
> document.registerElement("my-el", MyEl);
> ```
>
> Note how we don't need to save the return value of
> `document.registerElement`. `new MyEl()` still works, since it just calls
> the default `HTMLElement` constructor. (It can get its tag name by looking
> up `new.target === MyEl` in the custom element registry.) And, `new MyEl()`
> is equivalent to the parse-then-upgrade dance, since parsing corresponds to
> the main body of the HTMLElement constructor, and upgrading corresponds to
> proto-munging plus calling `this.createdCallback()`.
>
> Compare this to the "ideal" syntax that we've been searching for a way to
> make work throughout this thread:
>
> ```js
> class MyEl extends HTMLElement {
>   constructor() {
> super();
> // initialization code goes here
>   }
> }
>
> document.registerElement("my-el", MyEl);
> ```
>
> It's arguable just as good. You can't use the normal constructor
> mechanism, which feels sad. But let's talk about that.
>

I think this works *perfectly* (kudos!) as long as we don't want to support
constructor args being sent to readyCallback. As a "framework" hook, that
seems totally fine with me.


> Whenever you're working within a framework, be it Rails or ASP.NET or
> Polymer or "the DOM", sometimes the extension mechanism for the framework
> is to allow you to derive from a given base class. When you do so, there's
> a contract of what methods you implement, what methods you *don't*
> override, and so on.


Indeed. I've working on both Rails and Ember, and in both cases, it's *very
rare* to subclass from the internal constructor. There's usually a hook (or
event) API that subclasses can use so that the superclass implementation
can provide a more ergonomic API to implementations.


> When doing this kind of base-class extension, you're implementing a
> specific protocol that the framework tells you to, and you don't have
> complete freedom. So, it seems pretty reasonable if you're working within a
> framework that says, "you must not override the constructor; that's my
> domain. Instead, I've provided a protocol for how you can do
> initialization." Especially for a framework as complicated and full of
> initialization issues as the DOM.
>

Agreed.


> This design seems pretty nice to me. It means we can get upgrading (which,
> a few people have emphasized, is key in an ES6 modules world),


Can you say more about why same-identity upgrading is critical to the
design (as opposed to dom-mutation upgrading)? I asked up-thread but didn't
get any takers.


> we don't need to generate new constructors, and we can still use class
> syntax without it becoming just an indirect way of generating a `__proto__`
> to munge later. Via the isDefaultConstructor detection, we can protect
> people from the footgun of overriding the constructor.
>

This is pretty nice and pretty usable, I agree.


> On a final note, we could further bikeshed the name of createdCallback(),
> e.g. to either use a symbol or to be named something short and appealing
> like initialize() or create(), if that would make it even more appealing :)
>

I think symbols are actually pretty important here.

To elaborate, I envision a fu

RE: Custom element design with ES6 classes and Element constructors

2015-01-14 Thread Domenic Denicola
I had a chat with Dmitry Lomov (V8 team/TC39, helped shape the new ES6 classes 
design, CC'ed). His perspective was helpful. He suggested a way of evolving the 
current createdCallback design that I think makes it more palatable, and allows 
us to avoid all of the teeth-gnashing we've been doing in this thread.

- Define a default constructor for HTMLElement that includes something like:

  ```js
  const createdCallback = this.createdCallback;
  if (typeof createdCallback === "function") {
createdCallback();
  }
  ```

- Detect whether the constructor passed to document.registerElement is the 
default ES class constructor or not. (By default, classes generate a 
constructor like `constructor(...args) { super(...args); }`.) If it is not the 
default constructor, i.e. if an author tried to supply one, throw an error. 
This functionality doesn't currently exist in ES, but it exists in V8 and seems 
like a useful addition to ES (e.g. as `Reflect.isDefaultConstructor`).

- Define the HTMLElement constructor to be smart enough to work without any 
arguments. It could do this by e.g. looking up `new.target` in the registry. I 
can prototype this in https://github.com/domenic/element-constructors.

With these tweaks, the syntax for registering an element becomes:

```js
class MyEl extends HTMLElement {
  createdCallback() {
// initialization code goes here
  }
}

document.registerElement("my-el", MyEl);
```

Note how we don't need to save the return value of `document.registerElement`. 
`new MyEl()` still works, since it just calls the default `HTMLElement` 
constructor. (It can get its tag name by looking up `new.target === MyEl` in 
the custom element registry.) And, `new MyEl()` is equivalent to the 
parse-then-upgrade dance, since parsing corresponds to the main body of the 
HTMLElement constructor, and upgrading corresponds to proto-munging plus 
calling `this.createdCallback()`.

Compare this to the "ideal" syntax that we've been searching for a way to make 
work throughout this thread:

```js
class MyEl extends HTMLElement {
  constructor() {
super();
// initialization code goes here
  }
}

document.registerElement("my-el", MyEl);
```

It's arguable just as good. You can't use the normal constructor mechanism, 
which feels sad. But let's talk about that.

Whenever you're working within a framework, be it Rails or ASP.NET or Polymer 
or "the DOM", sometimes the extension mechanism for the framework is to allow 
you to derive from a given base class. When you do so, there's a contract of 
what methods you implement, what methods you *don't* override, and so on. When 
doing this kind of base-class extension, you're implementing a specific 
protocol that the framework tells you to, and you don't have complete freedom. 
So, it seems pretty reasonable if you're working within a framework that says, 
"you must not override the constructor; that's my domain. Instead, I've 
provided a protocol for how you can do initialization." Especially for a 
framework as complicated and full of initialization issues as the DOM.

This design seems pretty nice to me. It means we can get upgrading (which, a 
few people have emphasized, is key in an ES6 modules world), we don't need to 
generate new constructors, and we can still use class syntax without it 
becoming just an indirect way of generating a `__proto__` to munge later. Via 
the isDefaultConstructor detection, we can protect people from the footgun of 
overriding the constructor.

On a final note, we could further bikeshed the name of createdCallback(), e.g. 
to either use a symbol or to be named something short and appealing like 
initialize() or create(), if that would make it even more appealing :)



Re: Custom element design with ES6 classes and Element constructors

2015-01-14 Thread Dimitri Glazkov
On Wed, Jan 14, 2015 at 9:39 AM, Boris Zbarsky  wrote:

> On 1/14/15 11:52 AM, Dimitri Glazkov wrote:
>
>> Would like to point out that we're not talking about a general case
>> here. The actual proto munging in custom elements spec is minimized to a
>> pretty small set.
>>
>
> Pretty small set of which?  Possible mutations, elements, something else.


Had trouble with words here :) Something about minimizing the
impact/effect, and the next paragraph was meant to explain that.


>
>
>  Also, the current design doesn't change the prototype chain arbitrarily:
>>
>
> This is the most important point.  It's possible engines could optimize
> such proto chain insertions better than they do now.  Some feedback from
> engine implementors on how feasible that is would be good to have.
>
>  the effect is limited to inserting a sub-chain into the existing chain.
>>
>
> Is it, though?
>

Yes. I can certainly abuse the machinery to step outside of this rule, but
then I won't be creating useful/well-behaving custom elements.


> I don't see that this is always true, though I would be fine with the
> cases in which it's not true falling off performance cliffs: those would
> only happen when proto chains get munged after element registration.
>
> If we ignore those cases, it's possible JS engines could optimize this
> better than they do now.  JS engine implementor feedback would be pretty
> useful on this matter.



BTW, I agree that we should not hold on to the legacy of wrapper+object
design. That point was probably more about the second point -- the internal
slots of the base types are set at the time of instantiating.

:DG<


Re: Custom element design with ES6 classes and Element constructors

2015-01-14 Thread Boris Zbarsky

On 1/14/15 11:52 AM, Dimitri Glazkov wrote:

Would like to point out that we're not talking about a general case
here. The actual proto munging in custom elements spec is minimized to a
pretty small set.


Pretty small set of which?  Possible mutations, elements, something else.


Given that most engines use lazily created wrappers


I don't think we should be designing around this.  I believe that this 
is a carryover from the way the engines were initially implemented in a 
C++-first manner.  As a a particular example, Servo doesn't even have a 
concept of "wrappers"; it just has a single memory area that is both the 
JS and Rust representation of the object involved.



the actual setting
of the prototype won't even need to happen unless the developer first
accessed the element, thus creating a wrapper.


What that really means is that the performance cliff is randomly 
unpredictable, right?  That may or may not be better than always being slow.



Also, the current design doesn't change the prototype chain arbitrarily:


This is the most important point.  It's possible engines could optimize 
such proto chain insertions better than they do now.  Some feedback from 
engine implementors on how feasible that is would be good to have.



the effect is limited to inserting a sub-chain into the existing chain.


Is it, though?

I don't see that this is always true, though I would be fine with the 
cases in which it's not true falling off performance cliffs: those would 
only happen when proto chains get munged after element registration.


If we ignore those cases, it's possible JS engines could optimize this 
better than they do now.  JS engine implementor feedback would be pretty 
useful on this matter.


-Boris




Re: Custom element design with ES6 classes and Element constructors

2015-01-14 Thread Dimitri Glazkov
On Mon, Jan 12, 2015 at 9:11 PM, Boris Zbarsky  wrote:

> On 1/12/15 12:20 PM, Tab Atkins Jr. wrote:
>
>> Proto munging isn't even that big of a deal.
>>
>
> That really depends.
>
> For example, dynamically changing __proto__ on something somewhat
> permanently deoptimizes that object in at least some JS engines. Whether
> that's a big deal depends on what you do with your objects.


This is true.

Would like to point out that we're not talking about a general case here.
The actual proto munging in custom elements spec is minimized to a pretty
small set.

Given that most engines use lazily created wrappers, the actual setting of
the prototype won't even need to happen unless the developer first accessed
the element, thus creating a wrapper.

Also, the current design doesn't change the prototype chain arbitrarily:
the effect is limited to inserting a sub-chain into the existing chain.
IOW:  A-E + B-C-D-E  --> A-B-C-D-E. Thus, the base type is always the same.

:DG<


Re: Custom element design with ES6 classes and Element constructors

2015-01-14 Thread Boris Zbarsky

On 1/14/15 8:28 AM, Anne van Kesteren wrote:

On Wed, Jan 14, 2015 at 2:00 PM, Boris Zbarsky  wrote:

Wanting things like custom buttons is _very_ common.  What is your proposal
for addressing this use case?


I have no good answer. appearance:button seems to work okayish


I think we're having a miscommunication here.

appearance:button can give you something that looks like a button.  But 
it doesn't _act_ like one.


What I'm asking is what people who want an actual button but with some 
additional APIs on it are supposed to do.  This is the basic idea of 
subclassing built-ins: get something that acts like the built-in as far 
as the browser is concerned but has some additional functionality on it.


-Boris



Re: Custom element design with ES6 classes and Element constructors

2015-01-14 Thread Anne van Kesteren
On Wed, Jan 14, 2015 at 2:00 PM, Boris Zbarsky  wrote:
> Wanting things like custom buttons is _very_ common.  What is your proposal
> for addressing this use case?

I have no good answer. appearance:button seems to work okayish (prefix
needed though). However, this also seems like a different problem than
letting developers create an element from scratch and exposing the
necessary lifecycle hooks to enable that. Furthermore, if the solution
is this much of a hack, perhaps addressing a smaller problem first
(just custom elements, not subclassing normal elements) is a good
idea. And then once that has matured somewhat we can revisit. There's
no need to do both at once, I think.


-- 
https://annevankesteren.nl/



Re: Custom element design with ES6 classes and Element constructors

2015-01-14 Thread Boris Zbarsky

On 1/14/15 7:51 AM, Anne van Kesteren wrote:

I would rather we gave up on subclassing normal elements


Wanting things like custom buttons is _very_ common.  What is your 
proposal for addressing this use case?


-Boris



Re: Custom element design with ES6 classes and Element constructors

2015-01-14 Thread Anne van Kesteren
On Tue, Jan 13, 2015 at 6:50 PM, Domenic Denicola  wrote:
> Agreed. That needs to be done with , IMO.

I would really like to avoid having this is="" design. It does not
explain anything about the world today. I would rather we gave up on
subclassing normal elements (there's no normal element precedent for
that anyway).

I also agree with Yehuda that we need to explore alternatives to the
current upgrading design. It just feels like too much of a hack.
Perhaps we should support the synchronous version (invoking the custom
element constructor) and come up with something new for asynchronous
loaded definitions.


-- 
https://annevankesteren.nl/



Re: Custom element design with ES6 classes and Element constructors

2015-01-13 Thread Yehuda Katz
On Tue, Jan 13, 2015 at 9:50 AM, Domenic Denicola  wrote:

> From: Boris Zbarsky [mailto:bzbar...@mit.edu]
>
> > Just to be clear, this still didn't allow you to upgrade a  to
> be a subclass of , because that required a change in allocation, right?
>
> Agreed. That needs to be done with , IMO. (Assuming the
> upgrading design doesn't get switched to DOM mutation, of course.)
>

Can someone help outline for me exactly why DOM mutation is a problem here?
I can definitely see downsides, but DOM mutation is a fact of life when
scripts are involved on today's web, and it sidesteps a lot of the problems
that we encounter by trying to make in-place upgrading (upgrades without
changing the reference at all) work sanely.

I mean, qSA might not work the way someone might expect, but it also might
not work if you go `$("my-button").button()` using jQuery. What expectation
do we imagine someone has here that we think is missing if we use DOM
mutation (rather than object-model mutation) for upgrades.

-- Yehuda


Re: Custom element design with ES6 classes and Element constructors

2015-01-13 Thread Boris Zbarsky

On 1/13/15 12:50 PM, Domenic Denicola wrote:

Agreed. That needs to be done with , IMO. (Assuming the 
upgrading design doesn't get switched to DOM mutation, of course.)

Although! Briefly yesterday Arv mentioned that for Blink's DOM implementation there's no real 
difference in "internal slots" between  and : both just have a 
single internal slot pointing to the C++ backing object.


Just a few notes:

1)  This is not universal across implementations.  For example, Gecko 
will handle some property accesses by storing the values in additional 
internal slots on IDL objects.  We're not doing that on any elements 
right now, but would likely start doing that for HTMLMediaElement's 
TimeRanges attributes, for example, if those ever switch away from the 
"return a new object on every get" behavior.


2)  Updating the value in the internal slot means changing the C++ 
object identity, which means finding all the C++ places that have 
pointers to it (like the DOM tree) and ... something.  Not updating the 
value in the internal slot means you now have an  pointing to the 
wrong C++ class or something.


In any case, we seem agreed that for now this case needs to stick to the 
is="" syntax.



The argument for different constructors is that: **assuming we want a design 
such that parsing-then-upgrading an element is the same as calling its 
constructor**, then we need the constructor to be split into two pieces: 
allocation, which happens on parse and is not overridden by author-defined 
subclasses, and initialization, which happens at upgrade time and is what 
authors define.


Right, that makes sense.


However, when authors design constructors with `class C1 extends HTMLElement { 
constructor(...) { ... } }`, their constructor will do both allocation and 
initialization. We can't separately call the initialization part of it at 
upgrade time without also allocating a new object.


Hmm.

So this is a situation where having the [[Call]] of the constructor do 
something "magic" (like ignoring the super() call in the script) might 
be nice, but I agree it would be really weird.



Thus, given a parse-then-upgrade scenario, we can essentially never call C1's 
constructor.


Yes, agreed.


We *could* call some other method of C1 at upgrade time. Say, createdCallback.


Right.


We could even generate a constructor, call it C2, that does HTMLElement 
allocation + calls createdCallback. That's what the current spec does.


OK. Or we could leave that up to the class implementor.  That is, they 
could do:


  class C1 extends HTMLElement {
constructor(...args) { super(...args); this.createdCallback(); }
  };

if they want a nontrivial createdCallback.  It does mean more 
boilerplate, though.


I guess that's basically Arv's proposal, yes?

I'm less worried about the extra leeway in this setup per se and more 
worried about whether the extra leeway would be a footgun in practice...


Thank you for explaining; I see the issues more clearly now.

-Boris



RE: Custom element design with ES6 classes and Element constructors

2015-01-13 Thread Domenic Denicola
From: Boris Zbarsky [mailto:bzbar...@mit.edu] 

> Just to be clear, this still didn't allow you to upgrade a  to be a 
> subclass of , because that required a change in allocation, right?

Agreed. That needs to be done with , IMO. (Assuming the 
upgrading design doesn't get switched to DOM mutation, of course.)

Although! Briefly yesterday Arv mentioned that for Blink's DOM implementation 
there's no real difference in "internal slots" between  and : both 
just have a single internal slot pointing to the C++ backing object. So in 
practice maybe it could. But, in theory the internal slots would be quite 
different between  and , so I wouldn't really want to go down this 
road.

> Well, I was skipping several steps and making a few assumptions. 
> Roughly, my thought process was that you want *some* constructor that 
> corresponds to parser/document.createElement behavior. And, since as 
> discussed it definitely can't be your own constructor

> This is the part I'm not quite following.  Why can't it be your own 
> constructor?  Sorry for losing the thread of the argument here

No problem, I'm still skipping steps. There is an alternative design, which Arv 
outlined, which does allow the constructor to be the same.

The argument for different constructors is that: **assuming we want a design 
such that parsing-then-upgrading an element is the same as calling its 
constructor**, then we need the constructor to be split into two pieces: 
allocation, which happens on parse and is not overridden by author-defined 
subclasses, and initialization, which happens at upgrade time and is what 
authors define.

However, when authors design constructors with `class C1 extends HTMLElement { 
constructor(...) { ... } }`, their constructor will do both allocation and 
initialization. We can't separately call the initialization part of it at 
upgrade time without also allocating a new object. Thus, given a 
parse-then-upgrade scenario, we can essentially never call C1's constructor.

We *could* call some other method of C1 at upgrade time. Say, createdCallback. 
(Or upgradedCallback.) We could even generate a constructor, call it C2, that 
does HTMLElement allocation + calls createdCallback. That's what the current 
spec does.

In summary, the current spec design is:

 - parse-then-upgrade: HTMLElement allocation, then createdCallback.
 - parse an already-registered element: HTMLElement allocation, then 
createdCallback.
 - generated constructor: HTMLElement allocation, then createdCallback.
 - author-supplied constructor: ignored

Arv's message had a different design, that does indeed give C1 === C2:

 - parse-then-upgrade: HTMLElement allocation, then upgradedCallback.
 - parse an already-registered element: call author-supplied constructor 
(requires some trickiness to avoid executing user code during parse, but can 
use the same tricks createdCallback uses today)
 - generated constructor = author-supplied constructor

This requires a bit of manual synchronization to ensure that parse-then-upgrade 
behaves the same as the constructor/already-registered element case, as he 
illustrates in his message. In other words, it *doesn't* assume we want 
parsing-then-upgrading to be the same as calling the constructor. That might be 
the right tradeoff. Yesterday I was convinced it was. Today I have written so 
many emails that I'm not sure what I think anymore.



Re: Custom element design with ES6 classes and Element constructors

2015-01-13 Thread Boris Zbarsky

On 1/13/15 12:10 PM, Domenic Denicola wrote:

Hmm.  So given the current direction whereby ES6 constructors may not even be 
[[Call]]-able at all, I'm not sure we have any great options here.  :(  
Basically, ES6 is moving toward coupling allocation and initialization but the 
upgrade scenario can't really be expressed by coupled alloc+init if it 
preserves object identity, right?


Yes, that is my feeling exactly. The old @@create design was perfect for our 
purposes, since its two-stage allocation-then-initialization could be staged 
appropriately


Just to be clear, this still didn't allow you to upgrade a  to 
be a subclass of , because that required a change in allocation, right?



Well, I was skipping several steps and making a few assumptions. Roughly, my 
thought process was that you want *some* constructor that corresponds to 
parser/document.createElement behavior. And, since as discussed it definitely 
can't be your own constructor


This is the part I'm not quite following.  Why can't it be your own 
constructor?  Sorry for losing the thread of the argument here


-Boris



RE: Custom element design with ES6 classes and Element constructors

2015-01-13 Thread Domenic Denicola
From: Gabor Krizsanits [mailto:gkrizsan...@mozilla.com] 

> Isn't there a chance to consider our use-case in ES6 spec. then?

I kind of feel like I and others dropped the ball on this one. Until this 
thread I didn't realize how important the dual-stage allocation + 
initialization was, for upgrading in particular. So, we happily helped redesign 
away from the old ES6 dual-stage @@create design to a new ES6 coupled design, 
over the last few weeks.

The @@create design met with heavy implementer resistance from V8, for IMO 
valid reasons. But I think in the rush to fix it we forgot part of why it was 
done in the first place :(

> Yes, and it seems to me that we are trying to hack around the fact that ES6 
> classes are not compatible with what we are trying to do.
> ...
> And if there is no official way to do it people will start to try and hack 
> their way through in 1000 horrible ways...

The interesting thing is, ES5 classes also have the coupled allocation + 
initialization. So, the recent coupled ES6 class design is seen as a natural 
extension that fits well with ES5, while being more flexible and allowing 
subclassable built-ins.

One of the benefits of the new coupled ES6 class design over the coupled ES5 
design is that it exposes enough hooks so that you can, indeed, do such hacks. 
They might not even be so horrible.

For example, if you can just guarantee that everyone uses the constructor only 
for allocation, and puts their initialization code into a initialize() method 
that they call as the last line of their constructor, you can get an 
author-code version of the decoupled @@create design. You could even consider 
calling that initialize() method, say, createdCallback().

Viewed from this perspective, the real benefit of the old ES6 @@create design 
was that it standardized on exactly how that pattern would work: it would 
always be `new C(..args) <=> C.call(C[Symbol.create](), ...args)`. These days, 
if we were to invent our own pattern, so that e.g. `new C(...args) <=> 
C.prototype.createdCallback.call(new C(...args))`, this would only work for 
constructors whose definition we control, instead of all constructors ever. 
This is what leads to the idea (present in the current custom elements spec) of 
`document.registerElement` generating the constructor and ignoring any 
constructor that is passed in.



Re: Custom element design with ES6 classes and Element constructors

2015-01-13 Thread Boris Zbarsky

On 1/13/15 12:06 PM, Gabor Krizsanits wrote:

I think this part of the spec was largely written before ES6 class stuff
stabilized, fwiw.  Which is not hard, since it's still not stabilized.  ;)


Isn't there a chance to consider our use-case in ES6 spec. then?


I suspect not in time for ES6.  The "not stabilized" bit here is 
supposed to be rectified on a timeframe of hours-to-days.



(+ internally I'm sure most engines does transformations in some cases
already, like for array optimisations...)


The feedback from the V8/Blink folks was that they are in fact unable to 
do such a transformation for DOM objects.  That's one of the things that 
led to the re-coupling of allocation and initialization in ES6.


A more limited class transformation that doesn't involve changing the 
set of internal slots might be easier to do.  But if you have to change 
internal slots, things get complicated.


-Boris




RE: Custom element design with ES6 classes and Element constructors

2015-01-13 Thread Domenic Denicola
From: Boris Zbarsky [mailto:bzbar...@mit.edu] 

> Hmm.  So given the current direction whereby ES6 constructors may not even be 
> [[Call]]-able at all, I'm not sure we have any great options here.  :(  
> Basically, ES6 is moving toward coupling allocation and initialization but 
> the upgrade scenario can't really be expressed by coupled alloc+init if it 
> preserves object identity, right?

Yes, that is my feeling exactly. The old @@create design was perfect for our 
purposes, since its two-stage allocation-then-initialization could be staged 
appropriately by doing allocation initially, then initialization upon 
upgrading. But the new coupled design defeats that idea.

>> I was hopeful that ES6 would give us a way out of this, but after thinking 
>> things through, I don't see any improvement at all. In particular it seems 
>> you're always going to have to have `var C2 = 
>> document.registerElement("my-el", C1)` giving `C2 !== C1`.
>
> This part is not immediately obvious to me.  Why does that have to be true?

Well, I was skipping several steps and making a few assumptions. Roughly, my 
thought process was that you want *some* constructor that corresponds to 
parser/document.createElement behavior. And, since as discussed it definitely 
can't be your own constructor, the browser will need to generate one for you. 
Thus, it'll generate C2, which is different from the C1 you passed in.

Even if you removed the assumption that having a (user-exposed) constructor 
that corresponds to parser behavior is useful, it doesn't fix the issue that 
the C1 constructor is useless.



Re: Custom element design with ES6 classes and Element constructors

2015-01-13 Thread Gabor Krizsanits
> I think this part of the spec was largely written before ES6 class stuff
> stabilized, fwiw.  Which is not hard, since it's still not stabilized.  ;)
Isn't there a chance to consider our use-case in ES6 spec. then?

> Basically, ES6 is moving toward coupling allocation and
> initialization but the upgrade scenario can't really be expressed by
> coupled alloc+init if it preserves object identity, right?

Yes, and it seems to me that we are trying to hack around the fact that
ES6 classes are not compatible with what we are trying to do. Would
it be naive to request a well defined way to change an objects class
post alloc? It seems like a hard problem, but I can imagine
pure JS cases too when it would make sense to do a transformation.

And if there is no official way to do it people will start to try
and hack their way through in 1000 horrible ways...

(+ internally I'm sure most engines does transformations in some cases
already, like for array optimisations...)

 - Gabor


Re: Custom element design with ES6 classes and Element constructors

2015-01-12 Thread Boris Zbarsky

On 1/12/15 7:24 PM, Domenic Denicola wrote:

One crazy idea for solving B is to make every DOM element (or at least, every one 
generated via parsing a hyphenated or is="" element) into a proxy whose target 
can switch from e.g. `new HTMLUnknownElement()` to `new MyEl()` after upgrading. Like 
WindowProxy, basically. I haven't explored this in much detail because proxies are scary.


Hey, we have implementation experience for this in Gecko, since that's 
_exactly_ what we do when you adopt an element into a different global: 
we replace the guts of the old object with a proxy to the new thing.  ;)


Some thoughts:

1)  This does NOT affect C++ references in Gecko, not least because the 
C++ object identity doesn't change in this case.  Updating those would 
be a PITA.  But you want to change the C++ object identity for this 
particular use case, so you could get into weird situations where if 
something that's not JS is holding a ref to your element it can now be 
pointing to the wrong element.  Unless things get changed so all C++ 
holds references to elements via their JS reflections, which is probably 
a nonstarter.


2)  There is a performance penalty for having a proxy, obviously.  For 
adoption this is not too horrible, since in practice that's not that 
common, but presumably upgrades would actually be somewhat common.


3)  You need a way for the brand checks Web IDL methods do to deal with 
these proxies.  Gecko already has a way for that to work, on a slower 
path, since we have these membrane proxies, but other UAs would need to 
grow something like that.


-Boris



Re: Custom element design with ES6 classes and Element constructors

2015-01-12 Thread Boris Zbarsky

On 1/12/15 12:20 PM, Tab Atkins Jr. wrote:

Proto munging isn't even that big of a deal.


That really depends.

For example, dynamically changing __proto__ on something somewhat 
permanently deoptimizes that object in at least some JS engines. 
Whether that's a big deal depends on what you do with your objects.


-Boris




Re: Custom element design with ES6 classes and Element constructors

2015-01-12 Thread Boris Zbarsky

On 1/11/15 3:13 PM, Domenic Denicola wrote:

So, at least as a thought experiment: what if we got rid of all the local name checks 
in implementations and the spec. I think then `` could work, as long as 
it was done *after* `document.registerElement` calls.


Yes.


However, I don't understand how to make it work for upgraded elements at all, i.e., in the case 
where `` is parsed, and then later `document.registerElement("my-q", 
MyQ)` happens. You'd have to somehow graft the internal slots onto all MyQ instances after the 
fact


Yep.  It's no fun.


Is there any way around this you could imagine?


Not without making the set of internal slots mutable.

Note that in some implementations (e.g. a self-hosted implementation 
using WeakMaps for internal slots) this might not be so difficult.  But 
in general it does constrain implementation strategies quite significantly.



I know how seriously you were considering my suggestion to rewrite them all ;)


I've pretty seriously considered it on and off


The story is still pretty unsatisfactory, however. Consider the case where your document consists of 
``, and then later you do `class MyEl extends HTMLElement {}; 
document.registerElement("my-el", MyEl)`. (Note how I don't save the return value of 
document.registerElement.) When the parser encountered ``, it called `new 
HTMLUnknownElement(...)`, allocating a HTMLUnknownElement. The current design says to `__proto__`-munge the 
element after the fact, i.e. `document.body.firstChild.__proto__ = MyEl.prototype`. But it never calls the 
`MyEl` constructor!


Yeah, I'm not a fan of this part either.


- It means that the code `class MyEl extends HTMLElement {}` is largely a lie.


I think this part of the spec was largely written before ES6 class stuff 
stabilized, fwiw.  Which is not hard, since it's still not stabilized.  ;)



- It means that what you get when doing `new MyEl()` is different from what you got when 
parsing-then-upgrading ``.


This seems pretty undesirable.


(The same problems apply with , by the way. It needs to be upgraded 
from HTMLQuoteElement to QQ, but we can only `__proto__`-munge, not call the constructor.)


Hmm.  So given the current direction whereby ES6 constructors may not 
even be [[Call]]-able at all, I'm not sure we have any great options 
here.  :(  Basically, ES6 is moving toward coupling allocation and 
initialization but the upgrade scenario can't really be expressed by 
coupled alloc+init if it preserves object identity, right?



I was hopeful that ES6 would give us a way out of this, but after thinking things 
through, I don't see any improvement at all. In particular it seems you're always going 
to have to have `var C2 = document.registerElement("my-el", C1)` giving `C2 !== 
C1`.


This part is not immediately obvious to me.  Why does that have to be true?

-Boris



Re: Custom element design with ES6 classes and Element constructors

2015-01-12 Thread Ryosuke Niwa

> On Jan 12, 2015, at 5:14 PM, Domenic Denicola  wrote:
> 
> From: Ryosuke Niwa [mailto:rn...@apple.com] 
> 
>> There's no brilliant solution here.  I'm suggesting to introduce something 
>> akin to sync script element.
> 
>> In this particular example, my-el will remain HTMLUnknownElement since it 
>> had already appeared when the script element to load the module is parsed.  
>> A more interesting example would be the one where the script element to load 
>> my-module.js appears before my-el.  In that case, the instantiation of my-el 
>> is delayed until my-module.js is loaded.
> 
> I am still trying to tease out what "the instantiation of my-el is delayed" 
> means.

I mean that the parse would stop processing nodes at that point.  I expect that 
many people will be unhappy with this approach since it would have the same 
ramification as a "sync" script element (without "defer" or "async" attributes).

> What would happen in my example where the my-module.js appears before my-el? 
> I'm guessing from your response above, it would need to be 

RE: Custom element design with ES6 classes and Element constructors

2015-01-12 Thread Domenic Denicola
From: Ryosuke Niwa [mailto:rn...@apple.com] 

> There's no brilliant solution here.  I'm suggesting to introduce something 
> akin to sync script element.

> In this particular example, my-el will remain HTMLUnknownElement since it had 
> already appeared when the script element to load the module is parsed.  A 
> more interesting example would be the one where the script element to load 
> my-module.js appears before my-el.  In that case, the instantiation of my-el 
> is delayed until my-module.js is loaded.

I am still trying to tease out what "the instantiation of my-el is delayed" 
means. What would happen in my example where the my-module.js appears before 
my-el? I'm guessing from your response above, it would need to be 

Re: Custom element design with ES6 classes and Element constructors

2015-01-12 Thread Ryosuke Niwa

> On Jan 12, 2015, at 4:24 PM, Domenic Denicola  wrote:
> 
> From: Ryosuke Niwa [mailto:rn...@apple.com] 
> 
>> In that case, we can either delay the instantiation of those unknown 
>> elements with "-" in their names until pending module loads are finished
> 
> Could you explain this in a bit more detail? I'm hoping there's some 
> brilliant solution hidden here that I haven't been able to find yet.

There's no brilliant solution here.  I'm suggesting to introduce something akin 
to sync script element.

> For example, given
> 
> 
> 
>  window.theFirstChild = document.body.firstChild;
>  console.log(window.theFirstChild);
>  console.log(window.theFirstChild.method);
> 
> 
> 
> 
> with my-module.js containing something like
> 
> 
> document.registerElement("my-el", class MyEl extends HTMLElement {
>  constructor() {
>super();
>console.log("constructed!");
>  }
>  method() { }
> });
> console.log(document.body.firstChild === window.theFirstChild);
> console.log(document.body.method);
> 
> 
> what happens, approximately?

In this particular example, my-el will remain HTMLUnknownElement since it had 
already appeared when the script element to load the module is parsed.  A more 
interesting example would be the one where the script element to load 
my-module.js appears before my-el.  In that case, the instantiation of my-el is 
delayed until my-module.js is loaded.

>> or go with option 2
> 
> There are a few classes of objections to option 2, approximately:
> 
> A. It would spam the DOM with mutations (in particular spamming any mutation 
> observers)
> B. It would invalidate any references to the object (e.g. the 
> `window.theFirstChild !== document.body.firstChild` problem), which is 
> problematic if you were e.g. using those as keys in a map.
> C. What happens to any changes you made to the element? (E.g. attributes, 
> event listeners, expando properties, ...)
> 
> I am not sure why A is a big deal, and C seems soluble (copy over most 
> everything, maybe not expandos---probably just follow the behavior of 
> cloneNode). B is the real problem though.
> 
> One crazy idea for solving B is to make every DOM element (or at least, every 
> one generated via parsing a hyphenated or is="" element) into a proxy whose 
> target can switch from e.g. `new HTMLUnknownElement()` to `new MyEl()` after 
> upgrading. Like WindowProxy, basically. I haven't explored this in much 
> detail because proxies are scary.


B is problematic only if authors are not responding to DOM mutations.  If we 
feel that B is too problematic then we should go with option 1 with the 
aforementioned synchronous delay.

- R. Niwa




RE: Custom element design with ES6 classes and Element constructors

2015-01-12 Thread Domenic Denicola
From: Ryosuke Niwa [mailto:rn...@apple.com] 

> In that case, we can either delay the instantiation of those unknown elements 
> with "-" in their names until pending module loads are finished

Could you explain this in a bit more detail? I'm hoping there's some brilliant 
solution hidden here that I haven't been able to find yet.

For example, given



  window.theFirstChild = document.body.firstChild;
  console.log(window.theFirstChild);
  console.log(window.theFirstChild.method);




with my-module.js containing something like


document.registerElement("my-el", class MyEl extends HTMLElement {
  constructor() {
super();
console.log("constructed!");
  }
  method() { }
});
console.log(document.body.firstChild === window.theFirstChild);
console.log(document.body.method);


what happens, approximately?

> or go with option 2

There are a few classes of objections to option 2, approximately:

A. It would spam the DOM with mutations (in particular spamming any mutation 
observers)
B. It would invalidate any references to the object (e.g. the 
`window.theFirstChild !== document.body.firstChild` problem), which is 
problematic if you were e.g. using those as keys in a map.
C. What happens to any changes you made to the element? (E.g. attributes, event 
listeners, expando properties, ...)

I am not sure why A is a big deal, and C seems soluble (copy over most 
everything, maybe not expandos---probably just follow the behavior of 
cloneNode). B is the real problem though.

One crazy idea for solving B is to make every DOM element (or at least, every 
one generated via parsing a hyphenated or is="" element) into a proxy whose 
target can switch from e.g. `new HTMLUnknownElement()` to `new MyEl()` after 
upgrading. Like WindowProxy, basically. I haven't explored this in much detail 
because proxies are scary.



Re: Custom element design with ES6 classes and Element constructors

2015-01-12 Thread Ryosuke Niwa

> On Jan 12, 2015, at 2:59 PM, Domenic Denicola  wrote:
> 
> From: Ryosuke Niwa [mailto:rn...@apple.com] 
> 
>> As we have repeatedly stated elsewhere in the mailing list, we support 
>> option 1 since authors and frameworks can trivially implement 2 or choose to 
>> set "prototype" without us baking the feature into the platform.
> 
> At first I was sympathetic toward option 1, but then I realized that with ES6 
> modules all script loading becomes async, so it would be literally impossible 
> to use custom elements in a .html file (unless your strategy was to wait for 
> element registration, XHR the .html file into a string, then do 
> `document.documentElement.innerHTML = theBigString`).
> 
> In other words, in an ES6 modules world, all custom elements are upgraded 
> elements.


I see.  Thanks for that clarification.

In that case, we can either delay the instantiation of those unknown elements 
with "-" in their names until pending module loads are finished, or go with 
option 2.  We strongly prefer either one of those options over upgrading 
existing elements.

- R. Niwa




RE: Custom element design with ES6 classes and Element constructors

2015-01-12 Thread Domenic Denicola
From: Domenic Denicola [mailto:d...@domenic.me] 

> In other words, in an ES6 modules world, all custom elements are upgraded 
> elements.

Should be,  "In other words, in an ES6 modules world, all custom elements 
__that appear in the initially-downloaded .html file__ are upgraded elements."


RE: Custom element design with ES6 classes and Element constructors

2015-01-12 Thread Domenic Denicola
From: Ryosuke Niwa [mailto:rn...@apple.com] 

> As we have repeatedly stated elsewhere in the mailing list, we support option 
> 1 since authors and frameworks can trivially implement 2 or choose to set 
> "prototype" without us baking the feature into the platform.

At first I was sympathetic toward option 1, but then I realized that with ES6 
modules all script loading becomes async, so it would be literally impossible 
to use custom elements in a .html file (unless your strategy was to wait for 
element registration, XHR the .html file into a string, then do 
`document.documentElement.innerHTML = theBigString`).

In other words, in an ES6 modules world, all custom elements are upgraded 
elements.


Re: Custom element design with ES6 classes and Element constructors

2015-01-12 Thread Ryosuke Niwa

> On Jan 12, 2015, at 5:16 AM, Anne van Kesteren  wrote:
> 
> On Sun, Jan 11, 2015 at 9:13 PM, Domenic Denicola  wrote:
>> However, I don't understand how to make it work for upgraded elements at all
> 
> Yes, upgrading is the problem. There's two strategies as far as I can
> tell to maintain a sane class design:
> 
> 1) There is no upgrading. We synchronously invoke the correct
> constructor. I've been trying to figure out the drawbacks, but I can't
> find the set of mutation events problems that relates to this. One
> obvious drawback is needing to have all the code in place so you might
> need a way to delay the parser (return of synchronous 

Re: Custom element design with ES6 classes and Element constructors

2015-01-12 Thread Erik Arvidsson
In ES6 the constructor does both allocation and initialization. At
upgrade time it is too late to do allocation so we cannot call the
constructor at that time. We would need a callback for this, call it
upgradeCallback for example.

When the parser sees a custom element (any element with a dash in it)
it queues a reference to this element without creating a JS wrapper.
Before executing a script we need to empty this queue and we can do
that by [[Constructing]] the wrappers for these elements, looking up
the constructors in the registry. This is similar to how
createdCallback is handled today.

This means that the constructor can be used if the local name has been
registered but that the constructor will not be called for upgrades.
User code can structure their code in a way that they can share
initialization code.

class MyElement extends HTMLElement {
  constructor() {
super();
this.init();
  }
  upgradeCallback() {
this.init();
  }
  init() {
...
  }
}
document.register('my-element', MyElement);

There is also some issues related to the parameters passed to the
constructor and it looks like we need to use species for elements
created by the parser but I'll let Domenic cover that area.

On Mon, Jan 12, 2015 at 12:42 PM, Domenic Denicola  wrote:
> From: Tab Atkins Jr. [mailto:jackalm...@gmail.com]
>
>> Proto munging isn't even that big of a deal. It's the back-end stuff that's 
>> kinda-proto but doesn't munge that's the problem.  This is potentially 
>> fixable if we can migrate more elements out into JS space.
>
> It really isn't though, at least, not without a two-stage process like empty 
> constructor() with [[Construct]] semantics that can never be applied to 
> upgraded elements + createdCallback() with [[Call]] semantics that can be 
> applied to upgraded elements after having their __proto__ munged.



-- 
erik



RE: Custom element design with ES6 classes and Element constructors

2015-01-12 Thread Domenic Denicola
From: Tab Atkins Jr. [mailto:jackalm...@gmail.com] 

> Proto munging isn't even that big of a deal. It's the back-end stuff that's 
> kinda-proto but doesn't munge that's the problem.  This is potentially 
> fixable if we can migrate more elements out into JS space.

It really isn't though, at least, not without a two-stage process like empty 
constructor() with [[Construct]] semantics that can never be applied to 
upgraded elements + createdCallback() with [[Call]] semantics that can be 
applied to upgraded elements after having their __proto__ munged.


Re: Custom element design with ES6 classes and Element constructors

2015-01-12 Thread Tab Atkins Jr.
On Mon, Jan 12, 2015 at 5:16 AM, Anne van Kesteren  wrote:
> On Sun, Jan 11, 2015 at 9:13 PM, Domenic Denicola  wrote:
>> However, I don't understand how to make it work for upgraded elements at all
>
> Yes, upgrading is the problem. There's two strategies as far as I can
> tell to maintain a sane class design:
>
> 1) There is no upgrading. We synchronously invoke the correct
> constructor. I've been trying to figure out the drawbacks, but I can't
> find the set of mutation events problems that relates to this. One
> obvious drawback is needing to have all the code in place so you might
> need a way to delay the parser (return of synchronous 

Re: Custom element design with ES6 classes and Element constructors

2015-01-12 Thread Anne van Kesteren
On Sun, Jan 11, 2015 at 9:13 PM, Domenic Denicola  wrote:
> However, I don't understand how to make it work for upgraded elements at all

Yes, upgrading is the problem. There's two strategies as far as I can
tell to maintain a sane class design:

1) There is no upgrading. We synchronously invoke the correct
constructor. I've been trying to figure out the drawbacks, but I can't
find the set of mutation events problems that relates to this. One
obvious drawback is needing to have all the code in place so you might
need a way to delay the parser (return of synchronous 

RE: Custom element design with ES6 classes and Element constructors

2015-01-11 Thread Domenic Denicola
Following some old bug links indicates to me this has all been gone over many 
times before. In particular:

- https://www.w3.org/Bugs/Public/show_bug.cgi?id=20913 discussion of class 
syntax, prototypes,  vs. , and more
- https://www.w3.org/Bugs/Public/show_bug.cgi?id=21063 and related about 
upgrading (at one time upgrades *did* mutate the DOM tree)

I think perhaps the only new information is that, in the old ES6 class design 
with @@create, there was a feasible path forward where document.registerElement 
mutated the @@create to allocate the appropriate backing store, and left the 
constructor's "initialize" behavior (approximately it's [[Call]] behavior) 
alone. This would also work for upgrades, since you could just call the 
constructor on the upgraded element, as constructors in old-ES6 were not 
responsible for allocation. This would perform initialization on it (but leave 
allocation for the browser-generated @@create).

This was all somewhat handwavey, but I believe it was coherent, or close to it.

In contrast, the new ES6 subclassing designs have backed away from the 
allocation/initialization split, which seems like it torpedoes the idea of 
using user-authored constructors. Instead, we essentially have to re-invent the 
@@create/constructor split in user-space as constructor/createdCallback. That's 
a shame.

-Original Message-
From: Domenic Denicola [mailto:d...@domenic.me] 
Sent: Sunday, January 11, 2015 15:13
To: WebApps WG
Subject: Custom element design with ES6 classes and Element constructors

This is a spinoff from the thread "Defining a constructor for Element and 
friends" at 
http://lists.w3.org/Archives/Public/public-webapps/2015JanMar/0038.html, 
focused specifically on the design of custom elements in that world.

>> The logical extension of this, then, is that if after that 
>> `document.registerElement` call I do `document.body.innerHTML = > cite="foo">blah`
>
> Ah, here we go.  This is the part where the trouble starts, indeed.
>
> This is why custom elements currently uses  for creating custom 
> element subclasses of things that are more specific than HTMLElement.  Yes, 
> it's ugly.  But the alternative is at least major rewrite of the HTML spec 
> and at least large parts of Gecko/WebKit/Blink. 
>  :( I can't speak to whether Trident is doing a bunch of localName checks 
> internally.

So, at least as a thought experiment: what if we got rid of all the local name 
checks in implementations and the spec. I think then `` could work, as 
long as it was done *after* `document.registerElement` calls.

However, I don't understand how to make it work for upgraded elements at all, 
i.e., in the case where `` is parsed, and then later 
`document.registerElement("my-q", MyQ)` happens. You'd have to somehow graft 
the internal slots onto all MyQ instances after the fact, which is antithetical 
to the ES6 subclassing design and to how implementations work. __proto__ 
mutation doesn't suffice at all. Is there any way around this you could imagine?

Assuming that there isn't, I agree that any extension of an existing HTML 
element must be used with `is=""` syntax, and there's really no way of fixing 
that. (So, as a side effect, the local name tests can stay. I know how 
seriously you were considering my suggestion to rewrite them all ;).)

This makes me realize that there are really two possible operations going on 
here:

- Register a "custom element," i.e. a new tag name, whose corresponding 
constructor *must* derive *directly* from HTMLElement.
- Register an "existing element extension," i.e. something to be used with 
is="", whose corresponding constructor can derive from some other constructor.

I'd envision this as

```js
document.registerElement("my-el", class MyEl extends HTMLElement {}); 
document.registerElementExtension("qq", class QQ extends HTMLQuoteElement {}); 
```

This would allow , plus  and , but not 
 or  or . (Also note that element extensions 
don't need to be hyphenated, and there's no need for "extends" since you can 
get the appropriate information from looking at the prototype chain of the 
passed constructor.) document.registerElement could even throw for things that 
don't directly extend HTMLElement. And document.registerElementExtension could 
throw for things which don't derive from constructors that are already in the 
registry. (BTW, as noted in the existing spec, for SVG you always want to use 
element extensions, not custom elements.)

---

The story is still pretty unsatisfactory, however. Consider the case where your 
document consists of ``, and then later you do `class MyEl 
extends HTMLElement {}; document.registerElement("my-el", MyEl)`. (Note how I 
don't save the return value of document.registerElement.) When the parser 
encountered ``, it called `new HTMLUnknownElement(...)`, allocating a 
HTMLUnknownElement. The current design says to `__proto__`-munge the element 
after the fact, i.e. `document.body.firstChild.__proto__ = MyEl.pr