Re: Custom element design with ES6 classes and Element constructors

2015-01-27 Thread Elliott Sprehn
On Thursday, January 15, 2015, Domenic Denicola d...@domenic.me 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 Yehuda Katz
Agree with this completely.

Yehuda Katz
(ph) 718.877.1325

On Tue, Jan 27, 2015 at 9:32 PM, Domenic Denicola d...@domenic.me 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 polymer-database'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 d...@domenic.me 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 polymer-database'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
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 polymer-database'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 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 (init +
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 wyc...@gmail.com wrote:

 On Tue, Jan 13, 2015 at 9:50 AM, Domenic Denicola d...@domenic.me wrote:

 From: Boris Zbarsky [mailto:bzbar...@mit.edu]

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

 Agreed. That needs to be done with img is=my-img, 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 dglaz...@google.com 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 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 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 Dimitri Glazkov
On Thu, Jan 15, 2015 at 2:37 AM, Anne van Kesteren ann...@annevk.nl wrote:

 On Thu, Jan 15, 2015 at 5:11 AM, Yehuda Katz wyc...@gmail.com 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 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 dglaz...@google.com 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 Erik Arvidsson
On Thu, Jan 15, 2015 at 1:09 AM, Dmitry Lomov dslo...@chromium.org wrote:


 On Thu, Jan 15, 2015 at 5:11 AM, Yehuda Katz wyc...@gmail.com wrote:

 On Wed, Jan 14, 2015 at 3:47 PM, Domenic Denicola d...@domenic.me 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 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 Anne van Kesteren
On Thu, Jan 15, 2015 at 5:11 AM, Yehuda Katz wyc...@gmail.com 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 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 Dmitry Lomov
On Thu, Jan 15, 2015 at 5:11 AM, Yehuda Katz wyc...@gmail.com wrote:

 On Wed, Jan 14, 2015 at 3:47 PM, Domenic Denicola d...@domenic.me 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-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 

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 d...@domenic.me 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 future where Ember tries to move its Component
API to be just a DOM subclass. So when you 

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 bzbar...@mit.edu 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 Dimitri Glazkov
On Mon, Jan 12, 2015 at 9:11 PM, Boris Zbarsky bzbar...@mit.edu 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 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 Anne van Kesteren
On Tue, Jan 13, 2015 at 6:50 PM, Domenic Denicola d...@domenic.me wrote:
 Agreed. That needs to be done with img is=my-img, 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-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-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 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: 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 Domenic Denicola
From: Boris Zbarsky [mailto:bzbar...@mit.edu] 

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

Agreed. That needs to be done with img is=my-img, 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 img and span: 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 img and span, 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:50 PM, Domenic Denicola wrote:

Agreed. That needs to be done with img is=my-img, 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 img and span: 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 img 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 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-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 my-img to 
be a subclass of img, 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 Yehuda Katz
On Tue, Jan 13, 2015 at 9:50 AM, Domenic Denicola d...@domenic.me wrote:

 From: Boris Zbarsky [mailto:bzbar...@mit.edu]

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

 Agreed. That needs to be done with img is=my-img, 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-12 Thread Anne van Kesteren
On Sun, Jan 11, 2015 at 9:13 PM, Domenic Denicola d...@domenic.me 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 script
loading).

2) As you indicate, upgrading becomes replacing. This used to be the
old model and got eventually killed through
https://www.w3.org/Bugs/Public/show_bug.cgi?id=21063 though there's no
clear summary as to why that happened. Issues seem to be: mutation
observer spam, dangling references, attributes, event listeners.

Forever prototype munging seems really broken too so we should really
revisit these two approaches to custom elements I think.


-- 
https://annevankesteren.nl/



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 d...@domenic.me 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 Ryosuke Niwa

 On Jan 12, 2015, at 5:16 AM, Anne van Kesteren ann...@annevk.nl wrote:
 
 On Sun, Jan 11, 2015 at 9:13 PM, Domenic Denicola d...@domenic.me 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 script
 loading).
 
 2) As you indicate, upgrading becomes replacing. This used to be the
 old model and got eventually killed through
 https://www.w3.org/Bugs/Public/show_bug.cgi?id=21063 though there's no
 clear summary as to why that happened. Issues seem to be: mutation
 observer spam, dangling references, attributes, event listeners.

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.

- 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] 

 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 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 Ryosuke Niwa

 On Jan 12, 2015, at 2:59 PM, Domenic Denicola d...@domenic.me 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 Ryosuke Niwa

 On Jan 12, 2015, at 4:24 PM, Domenic Denicola d...@domenic.me 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
 
 my-el/my-el
 script
  window.theFirstChild = document.body.firstChild;
  console.log(window.theFirstChild);
  console.log(window.theFirstChild.method);
 /script
 script type=module src=my-module.js/script
 
 
 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

my-el/my-el
script
  window.theFirstChild = document.body.firstChild;
  console.log(window.theFirstChild);
  console.log(window.theFirstChild.method);
/script
script type=module src=my-module.js/script


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 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 `my-q` 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 `my-q` 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 
`my-el/my-el`, 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 `my-el`, 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 `my-el/my-el`.


This seems pretty undesirable.


(The same problems apply with q is=qq, 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 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 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 ann...@annevk.nl wrote:
 On Sun, Jan 11, 2015 at 9:13 PM, Domenic Denicola d...@domenic.me 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 script
 loading).

That's the issue - you have to have all custom element definitions
loaded before any of your app is allowed to load, or else you'll have
confusing errors where your elements just don't work, or work in racy
conditions because you're racing an async script download against the
download+parse of the rest of the document.

 2) As you indicate, upgrading becomes replacing. This used to be the
 old model and got eventually killed through
 https://www.w3.org/Bugs/Public/show_bug.cgi?id=21063 though there's no
 clear summary as to why that happened. Issues seem to be: mutation
 observer spam, dangling references, attributes, event listeners.

Yeah, as you say, this is also likely to be racy and bug-prone -
sometimes your events stick around (because the script that set them
ran after the script that initialized the element) and sometimes they
don't (because the race went the other way).  Even in the lack of
races, more non-obvious ordering constraints are confusing to authors.

 Forever prototype munging seems really broken too so we should really
 revisit these two approaches to custom elements I think.

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.

~TJ



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, my-el vs. q is=my-qq, 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 = my-q 
 cite=fooblah/my-q`

 Ah, here we go.  This is the part where the trouble starts, indeed.

 This is why custom elements currently uses q is=my-q 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 `my-q` 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 `my-q` 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 my-el, plus q is=qq and blockquote is=qq, but not 
qq or q is=my-el or span is=qq. (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 `my-el/my-el`, 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 `my-el`, it called `new HTMLUnknownElement(...)`, allocating a 
HTMLUnknownElement. The current design says to `__proto__`-munge the