Re: Custom element design with ES6 classes and Element constructors
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
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
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
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
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
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
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
> > > > 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
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
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
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
> > > 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
> 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
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
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
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
> 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
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
> 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
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
> 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
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
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
> 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
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
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
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
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
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