Bug 20913 - [Custom]: What does inheriting from existing HTML element really mean?
[Custom]: What does inheriting from existing HTML element really mean?
Status: RESOLVED FIXED
Product: WebAppsWG
Classification: Unclassified
Component: Component Model
unspecified
PC All
: P2 normal
: ---
Assigned To: Dimitri Glazkov
public-webapps-bugzilla
:
Depends on:
Blocks: 20684
  Show dependency treegraph
 
Reported: 2013-02-08 17:33 UTC by Blake Kaplan
Modified: 2013-02-22 19:18 UTC (History)
5 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Blake Kaplan 2013-02-08 17:33:39 UTC
The spec for document.register allows a user to pass in a prototype that specifies the API of the new element and restricts it only in that it must inherit from HTMLElement. Allowing the prototype to inherit from e.g. HTMLButtonElement is useful in the <button is="x-foo"> case, but it isn't clear what should happen if the user simply calls document.createElement("x-foo"). Should that throw? What should happen if one of the methods defined on HTMLButtonElement is called?
Comment 1 Dimitri Glazkov 2013-02-08 18:26:52 UTC
(In reply to comment #0)
> The spec for document.register allows a user to pass in a prototype that
> specifies the API of the new element and restricts it only in that it must
> inherit from HTMLElement. Allowing the prototype to inherit from e.g.
> HTMLButtonElement is useful in the <button is="x-foo"> case, but it isn't
> clear what should happen if the user simply calls
> document.createElement("x-foo"). Should that throw?

We settled on <button is="x-foo"> and <x-foo> being interchangeable, so for:

var foo = document.createElement("x-foo")

and:

div.innerHTML = '<button is="x-foo"></button>';
var foo = div.firstChild;

foo should be of the same type. There will be difference between these instances: localNames, will be different ("x-foo" and "button", respectively), <x-foo> won't match any of "button" styles.

> What should happen if one of the methods defined on HTMLButtonElement is called?

That is a really interesting question and you're right that spec is totally skimpy on this.

Intuitively, the author would expect that an instance that inherits from HTMLButtonElement behaves like an HTMLButtonElement.

From the perspective of platform objects, this means that the instance will be backed by the proper HTMLButtonElement implementation on the C++ side. I am trying to figure how to spec that.
Comment 2 Boris Zbarsky 2013-02-08 23:04:24 UTC
Being backed "by the proper HTMLButtonElement" on the C++ side while not having a localName of "button" is, imo, a non-starter.

There's lots of code in both C++ and JS that examines the localName when working with the object and assumes that buttons will be "button".  So creating something that's an HTMLButtonElement with a different localName means that it will work in some ways but not in others, and generally be broken.

It's not just style matching that depends on the localName; it's context menus, creation of special CSS boxes, serialization behavior, editing behavior.  The list is quite long.
Comment 3 Dimitri Glazkov 2013-02-08 23:07:18 UTC
(In reply to comment #2)
> Being backed "by the proper HTMLButtonElement" on the C++ side while not
> having a localName of "button" is, imo, a non-starter.
> 
> There's lots of code in both C++ and JS that examines the localName when
> working with the object and assumes that buttons will be "button".  So
> creating something that's an HTMLButtonElement with a different localName
> means that it will work in some ways but not in others, and generally be
> broken.
> 
> It's not just style matching that depends on the localName; it's context
> menus, creation of special CSS boxes, serialization behavior, editing
> behavior.  The list is quite long.

I agree, I know for a fact we do some of this in WebKit, too.

Should we perhaps have two classes of custom elements:

1) "old-style folk" that always get instantiated as <localName is=custom-name>
2) "new-style folk" that always get instantiated as <custom-name>?
Comment 4 Boris Zbarsky 2013-02-08 23:08:52 UTC
With new-style folk always inheriting from HTMLElement or something if they're in the HTML namespace?

I could live with that, I think.
Comment 5 Dimitri Glazkov 2013-02-08 23:13:27 UTC
(In reply to comment #4)
> With new-style folk always inheriting from HTMLElement or something if
> they're in the HTML namespace?
> 
> I could live with that, I think.

Scott, Daniel, you guys chime in. This is sort of important.
Comment 6 Scott Miles 2013-02-08 23:19:42 UTC
Given 

"Being backed "by the proper HTMLButtonElement" on the C++ side while not having a localName of "button" is, imo, a non-starter"

Then it seems like only derivatives of HTMLUnknownElement (HTMLElement?) could ever be "new-style folk".

I believe I can live with that too.
Comment 7 Boris Zbarsky 2013-02-08 23:25:02 UTC
Note that deriving things with custom interfaces from HTMLElement is probably more semantically correct than from HTMLUnknownElement, because they are not in fact "unknown".  At least that was the argument Blake made earlier today to me.  ;)
Comment 8 Scott Miles 2013-02-08 23:26:47 UTC
Touche. HTMLElement it is. =P
Comment 9 Dimitri Glazkov 2013-02-08 23:40:20 UTC
Here's the summary:

Depending on your heritage, you will be instantiated in one of two ways when using document.createElement/NS or your constructor.

1) If you inherit from an existing HTML or SVG element, you and all your descendants will always be instantiated with the "is" syntax.

2) If you inherit from HTMLElement or SVGElement, you and your descendants will always be instantiated with the custom tag syntax.
Comment 10 Erik Arvidsson 2013-02-09 01:29:31 UTC
(In reply to comment #9)
> Here's the summary:
> 
> Depending on your heritage, you will be instantiated in one of two ways when
> using document.createElement/NS or your constructor.
> 
> 1) If you inherit from an existing HTML or SVG element, you and all your
> descendants will always be instantiated with the "is" syntax.
> 
> 2) If you inherit from HTMLElement or SVGElement, you and your descendants
> will always be instantiated with the custom tag syntax.

That seems like a reasonable trade off.

Then all the following asserts should pass

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

  var b1 = new MyButton()
  assert(b1.tagName === 'button');

  var b2 = document.createElement('my-button');
  assert(b2.tagName === 'button');

  var div = document.createElement('div');
  div.innerHTML = '<my-button>...</my-button>';
  var b3 = div.firstChild;
  assert(b3.tagName === 'button');

It is unfortunate and unexpected but I don't see any other possibility given the current constraints.
Comment 11 Boris Zbarsky 2013-02-10 02:56:36 UTC
Hmm.  Why do we have to add that sort of magic to createElement and parsing?  I guess for createElement it's because we can't do the is="" thing there (which is a bit sad, but not too bad), but parsing _can_ use is="" and seems like it should, because messing with the parser like that is ... weird.
Comment 12 Anne 2013-02-10 10:11:46 UTC
Is there no way we can make 

A extends BElement

x = new A()

work? That way createElement() is no longer required. I suppose you still need to register the fixed local name so that's why you'd use document.register()?
Comment 13 Boris Zbarsky 2013-02-10 12:11:12 UTC
We can totally make that work; the idea of document.register is to register such an A.

But people want to be able to mix scripted and declarative creation of elements too...
Comment 14 Dimitri Glazkov 2013-02-11 21:46:32 UTC
(In reply to comment #3)
> (In reply to comment #2)
> > It's not just style matching that depends on the localName; it's context
> > menus, creation of special CSS boxes, serialization behavior, editing
> > behavior.  The list is quite long.
> 
> I agree, I know for a fact we do some of this in WebKit, too.

Here's a simple search to demonstrate:
https://code.google.com/p/chromium/codesearch#search/&q=%5C%3EhasTagName%20file:Source/WebCore&sq=package:chrome&type=cs
Comment 15 Daniel Buchner 2013-02-11 23:29:01 UTC
In the following feedback, I'll highlight what I see as the most developer-friendly, ergonomic combination of the various concerns before us. In response, I would like to know what the major blockers or concerns are in relation to feasibility. (Some of the points below are already know or have been detailed in previous comments - they are included for cohesion)

1. Developers should be able to subclass elements with ES6 methods, but sub-classing should not implicitly register them with the parser.

 - document.register and class extension should not be - and need not be - synonymous. These interfaces represent two different conceptual functions: 1) creating new objects that inherit from a previously declared HTMLElement-objects, and 2) registering an element prototype for parser and DOM method functionality.

 - ES6 extension of elements need not affect document.register if the register method understands that the second property could be an options object, or an HTMLElement-inheriting object.

2. Developers should have access to all properties that existed on the base prototype whether declaring elements via tag or attribute. This means that setting 'src' on an <x-loader>, that was derived from { prototype: Object.create() } using HTMLScriptElement as a proto base should work - if it doesn't, that is non-optimal and should be corrected (feasible? Not sure. I'll plead ignorance on this point and defer to you all)

Examples to illustrate the above points:

This should work:

class SuperScript extends HTMLScriptElement;
document.register('super-script', SuperScript);

So should this:

document.register('super-script', {
    prototype: Object.create(HTMLScriptElement.prototype, { ... })
});

*** Recommendation ***

Do not return constructors from document.register, ever.

We should leave user-facing constructor generation to ES6 and make document.register understand them when used as the second parameter.

In talking to just about every developer friend I could get a hold of over the weekend I found that...no one cares about constructors. Maybe that changes in 4 years when most browsers have ES6 implemented. They told me they can't use the constructors of 99.5% of elements currently, so they could care less as long as document.createElement('super-script') works.
Comment 16 Dimitri Glazkov 2013-02-12 00:28:14 UTC
(In reply to comment #15)
> In the following feedback, I'll highlight what I see as the most
> developer-friendly, ergonomic combination of the various concerns before us.
> In response, I would like to know what the major blockers or concerns are in
> relation to feasibility. (Some of the points below are already know or have
> been detailed in previous comments - they are included for cohesion)
> 
> 1. Developers should be able to subclass elements with ES6 methods, but
> sub-classing should not implicitly register them with the parser.
> 
>  - document.register and class extension should not be - and need not be -
> synonymous. These interfaces represent two different conceptual functions:
> 1) creating new objects that inherit from a previously declared
> HTMLElement-objects, and 2) registering an element prototype for parser and
> DOM method functionality.

Yup. That's unchanged in any of the proposed solutions.

> 
>  - ES6 extension of elements need not affect document.register if the
> register method understands that the second property could be an options
> object, or an HTMLElement-inheriting object.

The solution that Arv proposed in http://lists.w3.org/Archives/Public/public-webapps/2013JanMar/0250.html doesn't have either/or split like this. Instead, there's the same syntax for both ES6 and ES5/3. If there _is_ a way to do that, I would think it's a Good Thing.

Boris is finding out how feasible it is, though.

> 
> 2. Developers should have access to all properties that existed on the base
> prototype whether declaring elements via tag or attribute. This means that
> setting 'src' on an <x-loader>, that was derived from { prototype:
> Object.create() } using HTMLScriptElement as a proto base should work - if
> it doesn't, that is non-optimal and should be corrected (feasible? Not sure.
> I'll plead ignorance on this point and defer to you all)

The problem that Boris raised is summarized in comment 9. It's a sucky situation, but basically, if you inherit from HTMLScriptElement, you can only be born as <script is="x-loader">, and never as <x-loader> :-\. I know you don't like that, but I haven't been able to think a way out.

> 
> Examples to illustrate the above points:
> 
> This should work:
> 
> class SuperScript extends HTMLScriptElement;
> document.register('super-script', SuperScript);
> 
> So should this:
> 
> document.register('super-script', {
>     prototype: Object.create(HTMLScriptElement.prototype, { ... })
> });
> 
> *** Recommendation ***
> 
> Do not return constructors from document.register, ever.

I don't understand what you mean here. In the current form, we do return a constructor. How would you instantiate an object? Being stuck with document.createElement forever seems like ghetto.

> 
> We should leave user-facing constructor generation to ES6 and make
> document.register understand them when used as the second parameter.
> 
> In talking to just about every developer friend I could get a hold of over
> the weekend I found that...no one cares about constructors. Maybe that
> changes in 4 years when most browsers have ES6 implemented. They told me
> they can't use the constructors of 99.5% of elements currently, so they
> could care less as long as document.createElement('super-script') works.

Well, I am your developer friend, and I like allowing constructors a lot :)

It lets us to get rid of the creation callbacks, simplifies the spec, and basically lets the builder of a component do exactly what they need at creation time, instead of dealing with "created" and "shadowRootCreated".


Check this out:

function SuperScript() {
   HTMLScriptElement.call(this);
   var root = this.createShadowRoot();
   root.innerHTML = "BEHOLD TEH SUPPERRRR SCRIPT!!!"
}
SuperScript.prototype = Object.create(HTMLScriptElement.prototype);
document.register("super-script", SuperScript);

Compare to:

document.register("super-script", {
  prototype: Object.create(HTMLScriptElement.prototype),
  lifecycle: {
     shadowRootCreated(root) {
        root.innerHTML = "BEHOLD TEH SUPPERRRR SCRIPT!!!";
     }
  }
});

The first one looks and feels like idiomatic JS. The author is in control of creating the shadow tree. The second has mysterious parameters, needs additional learning, and the author has no control of when the shadow tree is created.
Comment 17 Boris Zbarsky 2013-02-12 00:35:13 UTC
> Boris is finding out how feasible it is, though.

If by "it" you mean changing the [Construct] of an existing function dynamically in SpiderMonkey, it's not feasible without significant changes to the internal representation of functions in Spidermonkey.  I now have this on very good authority.  It's also not likely to happen on any sort of short timeframe.

I can't speak for JSC and Chakra and Carakan.  Might want to check with them too.
Comment 18 Daniel Buchner 2013-02-12 01:20:13 UTC
(In reply to comment #16)
> (In reply to comment #15)

> >  - ES6 extension of elements need not affect document.register if the
> > register method understands that the second property could be an options
> > object, or an HTMLElement-inheriting object.
> 
> The solution that Arv proposed in
> http://lists.w3.org/Archives/Public/public-webapps/2013JanMar/0250.html
> doesn't have either/or split like this. Instead, there's the same syntax for
> both ES6 and ES5/3. If there _is_ a way to do that, I would think it's a
> Good Thing.
> 
> Boris is finding out how feasible it is, though.

"Instead, there's the same syntax for both ES6 and ES5/3" - I don't see how this is at all possible to polyfill, because HTMLScriptElement.call(this); throws.

> > 2. Developers should have access to all properties that existed on the base
> > prototype whether declaring elements via tag or attribute. This means that
> > setting 'src' on an <x-loader>, that was derived from { prototype:
> > Object.create() } using HTMLScriptElement as a proto base should work - if
> > it doesn't, that is non-optimal and should be corrected (feasible? Not sure.
> > I'll plead ignorance on this point and defer to you all)
> 
> The problem that Boris raised is summarized in comment 9. It's a sucky
> situation, but basically, if you inherit from HTMLScriptElement, you can
> only be born as <script is="x-loader">, and never as <x-loader> :-\. I know
> you don't like that, but I haven't been able to think a way out.

^ Can we not fix this? What are all the options? I really don't think developers are going to like this, it sounds like the platform coded itself into a corner a bit - but I could be wrong ***ducks***

> > *** Recommendation ***
> > 
> > Do not return constructors from document.register, ever.
> > 
> > We should leave user-facing constructor generation to ES6 and make
> > document.register understand them when used as the second parameter.
>
> Well, I am your developer friend, and I like allowing constructors a lot :)

Developers rarely, if ever, use native element constructors in the wild because only a handful are supported: Image, Option, etc. Now let's be clear, developers use non-DOM/element related user-code constructors for their own objects, but given there are only a few working element constructors as it is, I assure you they won't miss what they never had. Additionally, if they can't extend what is output by document.register in the ES6 way, because ES6 methods aren't supported, what good is it to front-load the requirement?

> It lets us to get rid of the creation callbacks, simplifies the spec, and
> basically lets the builder of a component do exactly what they need at
> creation time, instead of dealing with "created" and "shadowRootCreated".
> 
> 
> Check this out:
> 
> function SuperScript() {
>    HTMLScriptElement.call(this);
>    var root = this.createShadowRoot();
>    root.innerHTML = "BEHOLD TEH SUPPERRRR SCRIPT!!!"
> }
> SuperScript.prototype = Object.create(HTMLScriptElement.prototype);
> document.register("super-script", SuperScript);

The problem is, the above doesn't work today and can't be polyfilled in a way that gracefully falls forward. 

> Compare to:
> 
> document.register("super-script", {
>   prototype: Object.create(HTMLScriptElement.prototype),
>   lifecycle: {
>      shadowRootCreated(root) {
>         root.innerHTML = "BEHOLD TEH SUPPERRRR SCRIPT!!!";
>      }
>   }
> });

Let's not ignore the other extremely useful lifecycle callbacks by pointing to constructors as a replacement for 'created'. In actual use, 'inserted', 'removed', and 'attributeChanged' are used (in some combination) in the majority of components we've observed. Forcing developers to deal with magical, implicitly-hooked mutation callback functions or long-in-tooth document-wide Mutation Observer generation in every constructor would be ugly and obtuse - comparatively, lifecycle callbacks are *waaay* better in terms of API ergonomics.

I think you'd be surprised to what extent developers prefer option objects in widgets and object instantiations, it is actually the most common pattern in all major libraries today.
Comment 19 Daniel Buchner 2013-02-12 01:40:24 UTC
(In reply to comment #18)

> I think you'd be surprised to what extent developers prefer option objects
> in widgets and object instantiations, it is actually the most common pattern
> in all major libraries today.

To clarify:

I think you'd be surprised to what extent developers prefer passing ***vanilla objects*** as options for methods vs being forced into formal constructor creation. The latter is huge in Java, not so much with web developers at-large.
Comment 20 Boris Zbarsky 2013-02-12 01:49:00 UTC
> Can we not fix this?

Depends on how you define "this".

Can we fix all the checks for localName that people do?  Sure.  We just need to fix them in every single browser codebase, in every single browser extension, and in various web libraries.  Is it practical to do that?  My gut feeling is no.

But if the "this" is something else, I'd like to know what it is before I can tell you whether we can fix it.
Comment 21 Scott Miles 2013-02-12 02:10:41 UTC
> "Instead, there's the same syntax for both ES6 and ES5/3" - I don't see how
> this is at all possible to polyfill, because HTMLScriptElement.call(this);
> throws.

This problem exists for polyfilling regardless of how you frame the syntax, and we have already discussed ways around it, no?

> Developers rarely, if ever, use native element constructors in the wild
> because only a handful are supported: Image, Option, etc. Now let's be
> clear, developers use non-DOM/element related user-code constructors for
> their own objects, but given there are only a few working element
> constructors as it is, I assure you they won't miss what they never had.

I think it's a mistake for any one of us to claim the inside scoop on developers. Personally I agree with Dimitri et al, that moving towards proper constructors is a positive. As I suggest above, I don't think it causes the problem you think it does.

> I think you'd be surprised to what extent developers prefer option objects
> in widgets and object instantiations, it is actually the most common pattern
> in all major libraries today.

If we can define precisely the information we are talking about, we can probably reach agreement about the best place to specify/store these things. I don't believe there is a philosophical rightness here.
Comment 22 Daniel Buchner 2013-02-12 16:29:58 UTC
(In reply to comment #21)
> > "Instead, there's the same syntax for both ES6 and ES5/3" - I don't see how
> > this is at all possible to polyfill, because HTMLScriptElement.call(this);
> > throws.
> 
> This problem exists for polyfilling regardless of how you frame the syntax,
> and we have already discussed ways around it, no?
> 
> > Developers rarely, if ever, use native element constructors in the wild
> > because only a handful are supported: Image, Option, etc. Now let's be
> > clear, developers use non-DOM/element related user-code constructors for
> > their own objects, but given there are only a few working element
> > constructors as it is, I assure you they won't miss what they never had.
> 
> I think it's a mistake for any one of us to claim the inside scoop on
> developers. Personally I agree with Dimitri et al, that moving towards
> proper constructors is a positive. As I suggest above, I don't think it
> causes the problem you think it does.
> 
> > I think you'd be surprised to what extent developers prefer option objects
> > in widgets and object instantiations, it is actually the most common pattern
> > in all major libraries today.
> 
> If we can define precisely the information we are talking about, we can
> probably reach agreement about the best place to specify/store these things.
> I don't believe there is a philosophical rightness here.

Let me frame this with some examples:

Someone posted this:

function SuperScript() {
   HTMLScriptElement.call(this);
   var root = this.createShadowRoot();
   root.innerHTML = "BEHOLD TEH SUPPERRRR SCRIPT!!!"
}
SuperScript.prototype = Object.create(HTMLScriptElement.prototype);
document.register("super-script", SuperScript);

and compared it to this:

document.register("super-script", {
  prototype: Object.create(HTMLScriptElement.prototype),
  lifecycle: {
     shadowRootCreated(root) {
        root.innerHTML = "BEHOLD TEH SUPPERRRR SCRIPT!!!";
     }
  }
});

Let's look at a more real-world example (threw this together pretty quickly, forgive any errors please) that uses the common observer functionality we've observed used widely in component creation:

document.register("draggable-product", {
  prototype: Object.create(HTMLDivElement.prototype),
  lifecycle: {
     inserted: function(){
         if (this.parentNode.nodeName == 'shopping-cart') {
             this.doPriceCheck(function(data){
                 alert('There is ' + data.isOnSale ? 'an' : 'no' + ' in-cart discount.');
                 if (this.attachedCart) this.attachedCart.addProductToTotal(this);
             });
             this.attachedCart = this.parentNode;
         }
     },
     removed: function(){
         if (this.attachedCart) this.attachedCart.removeProductFromTotal(this);
         this.attachedCart = null;
     },
  }
});

Can you show me what this would look like in a polyfilled version that uses a function constructor and declares the 'inserted' and 'removed' actions within it?
Comment 23 Dimitri Glazkov 2013-02-12 16:40:33 UTC
There are two different things going on in these comments, and I would really like for this tangling to stop. Otherwise, I fear we're all going to go crazy.

1) We seem to have migrated here the discussion about unified ES5+ES6 syntax proposal from this thread http://lists.w3.org/Archives/Public/public-webapps/2013JanMar/thread.html#msg250.

2) This bug is about instantiating custom elements as custom tags or type extensions (aka "is" attribute), which is completely unrelated to the first two items. I'll rename this bug to reflect this more accurately.

Can we possibly move the discussion about unified document.register syntax either back to the public-webapps thread or into a new bug? Bug 20831 seems nice for this purpose.
Comment 24 Anne 2013-02-12 16:51:17 UTC
It seems super weird to me to do createElement("x-x") and get an element back whose localName is not x-x and which in addition has an attribute defined!

Again as I said in http://lists.w3.org/Archives/Public/public-webapps/2013JanMar/0371.html already if you want to explain what the DOM is doing by exposing the guts, this kind of behavior does not help one bit.
Comment 25 Daniel Buchner 2013-02-12 17:46:48 UTC
(In reply to comment #16)

> It lets us to get rid of the creation callbacks, simplifies the spec, and
> basically lets the builder of a component do exactly what they need at
> creation time, instead of dealing with "created" and "shadowRootCreated".

I'm sorry if I've somehow clouded the issue here, I don't oppose ES6 or extending DOM element objects via class/extends. I simply want the impact of changing doc.register to only take generated constructors to be clearly spelled out with a real example and realized. The above statement makes the current document.register property object sound like *it* is the complex, obtuse choice - what I'm saying is that it's the *opposite*. We're forcing constructors that have more cumbersome ergonomics and polyfill surface on folks that prefer not to use constructors for generating elements, as this survey shows: https://docs.google.com/forms/d/16cNqHRe-7CFRHRVcFo94U6tIYnohEpj7NZhY02ejiXQ/viewanalytics

My contention is as follows: 

Developers rarely use element constructors - there are several reasons, including: they're currently uncallable and throw (basically unusable, except for Image, Option, etc), devs have document.createElement and don't need them, they aren't programming Java.

Why it matters:

1) We are arguing to change the interface to take only a parameter type that is largely irrelevant on the web today.

2) We lose easily defined callbacks to common actions - if we don't, *please correct me* and show an example. For instance, inside the constructor will I not be able to do: this.lifecycle.inserted = function(){...}? If so, yay, I am less concerned!

3) I am worried about boilerplate and monotony. Without answering the ergonomics questions about providing the functionality in the spec's current property object in a way easily accessible to developers within their user-generated constructor definitions, this concern persists.

Can someone please correct my fears that people will be forced to do this to achieve parity with what the spec's property object provides currently? -->

(function(){

  var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
  var list = this;
 
  var observer = new MutationObserver(function(mutations) {  
    mutations.forEach(function(record) {
      record.addedNodes.forEach(function(el){
        if (node.nodeName == 'super-list') {
            // do something when super-list elements are added
        }
      }
    });
  });
 
  observer.observe(document.body, {
    attributes: true, 
    childList: true, 
    characterData: true 
  });

  function SuperList() {
    this.setAttribute('super', true);
  }

  document.register('super-list', SuperList);

})();


As opposed to:

document.register('super-list', {
  prototype: Object.create(HTMLUListElement.prototype),
  lifecycle: {
    created: function(){
      this.setAttribute('super', true);
    },
    inserted: function(){
      // do something when super-list elements are added
    }
  }
});

Me thinks --> "Holy burgeoning boilerplate Batman!"
Comment 26 Dimitri Glazkov 2013-02-12 17:57:40 UTC
Moving callbacks discussion to bug 20831... see you guys there! :)
Comment 27 Dimitri Glazkov 2013-02-14 21:50:41 UTC
(In reply to comment #10)

>   var div = document.createElement('div');
>   div.innerHTML = '<my-button>...</my-button>';
>   var b3 = div.firstChild;
>   assert(b3.tagName === 'button');

I only have a problem with this one. Just can't stomach it -- eech :)
Comment 28 Dimitri Glazkov 2013-02-14 21:58:55 UTC
(In reply to comment #24)
> It seems super weird to me to do createElement("x-x") and get an element
> back whose localName is not x-x and which in addition has an attribute
> defined!
> 
> Again as I said in
> http://lists.w3.org/Archives/Public/public-webapps/2013JanMar/0371.html
> already if you want to explain what the DOM is doing by exposing the guts,
> this kind of behavior does not help one bit.

I agree it's super-weird, and would love to have your ideas on this.

I can't think of a way we can get around this problem. The issue at hand is that just checking localName has always been enough. With introduction of custom elements, it isn't any longer. We've changed an invariant. This is the bill for collateral damages.

Now, had we begun with an element.isa("button") function, which traverses element's inheritance chain to determine the answer, we'd be all set today :) #hindsight2020
Comment 29 Anne 2013-02-15 14:29:29 UTC
I think we should not break the createElement invariant of argument passed equals local name. I would be okay with adding a parameter to createElement for the is="" attribute I think, though if the long term game plan is constructors, lets just use those and not try to shoehorn new world into old world.
Comment 30 Dimitri Glazkov 2013-02-16 18:36:09 UTC
(In reply to comment #29)
> I think we should not break the createElement invariant of argument passed
> equals local name. I would be okay with adding a parameter to createElement
> for the is="" attribute I think, 

Do you see this as a dictionary argument for supplying element attribute values?
 
> though if the long term game plan is
> constructors, lets just use those and not try to shoehorn new world into old
> world.

Long term game plan is constructors. The mid-term dichotomy, however is somewhat maddening. I shudder imagining Web developer Bob trying to correctly determine how an element Foo should be constructed.

a) If Foo is an existing HTML element, use createElement only
b) If Foo is a custom element that doesn't inherit from any existing HTML element, use either createElement or constructor
c) If Foo is a custom element that does inherit from an existing HTML element, use constructor only.
c.1) .. or use a new createElement API with "is" attribute argument.

Woe to Bob. Especially if colleague Jill decided that Foo should benefit from inheriting from an existing HTML element. But maybe I am worrying too much :)
Comment 31 Anne 2013-02-18 10:05:10 UTC
(In reply to comment #30)
> Do you see this as a dictionary argument for supplying element attribute
> values?

Nah, I thought just a simple string. createElement(localName [, customName]). A better API for creating elements should not be created on top of createElement I think, as it's pretty clear nobody really likes that API.


> Long term game plan is constructors. The mid-term dichotomy, however is
> somewhat maddening. I shudder imagining Web developer Bob trying to
> correctly determine how an element Foo should be constructed.

It can be much simpler than what you suggest. If it's a platform-defined element, use createElement/createElementNS. If it's a custom element, use a constructor.

Short term there will be libraries wrapping what we come up with no matter what we do, as there will be implementation differences to paper over. Long term still seems somewhat cloudy as to what the best way is to create a new element.
Comment 32 Daniel Buchner 2013-02-18 16:20:05 UTC
(In reply to comment #30)
> a) If Foo is an existing HTML element, use createElement only
> b) If Foo is a custom element that doesn't inherit from any existing HTML
> element, use either createElement or constructor
> c) If Foo is a custom element that does inherit from an existing HTML
> element, use constructor only.
> c.1) .. or use a new createElement API with "is" attribute argument.
> 
> Woe to Bob. Especially if colleague Jill decided that Foo should benefit
> from inheriting from an existing HTML element. But maybe I am worrying too
> much :)

Woe to Bob indeed. In regard to the document.createElement('x-foo') case, you say this: "Intuitively, the author would expect that an instance that inherits from HTMLButtonElement behaves like an HTMLButtonElement." <-- I completely agree

There has to be a way to get to a place where native-inheriting custom elements can be declared via the existing createElement style of element generation. If we can figure out a way to do it, we should.

However, if we _must_ enforce a special case when generating native-inheriting custom elements, I agree that extending createElement to take an attributes object is not a bad idea (in fact, having createElement take an attributes object is a pretty nifty addition itself). This way developers could do: document.createElement('button', { is: 'x-foo' }). While, it's not the perfect solution, it can be polyfilled and using is="" when extending native elements is livable.
Comment 33 Dimitri Glazkov 2013-02-19 21:01:24 UTC
One upside of the solution, outlined in https://www.w3.org/Bugs/Public/show_bug.cgi?id=20913#c9 is that a custom element is guaranteed to have the right platform (C++) object, even before its definition was registered.

Given that, the effect of the element upgrade process (when an element's definition is registered) can be viewed as only mutating the prototype chain.
Comment 34 Dimitri Glazkov 2013-02-20 19:08:42 UTC
Documented the discussion and outcome here: http://lists.w3.org/Archives/Public/public-webapps/2013JanMar/0495.html
Comment 35 Dimitri Glazkov 2013-02-21 23:41:30 UTC
https://dvcs.w3.org/hg/webcomponents/rev/ea84d8cd5062

I will walk over this one more time tomorrow to double-check.
Comment 36 Dimitri Glazkov 2013-02-22 18:39:07 UTC
Forgot to add setting "is" for createElement/NS: https://dvcs.w3.org/hg/webcomponents/rev/4f22ef5d1f8b
Comment 37 Dimitri Glazkov 2013-02-22 18:47:23 UTC
Added custom element name (type) to duplicate registrations check: https://dvcs.w3.org/hg/webcomponents/rev/a4e5ff90ee0d
Comment 38 Dimitri Glazkov 2013-02-22 18:47:59 UTC
I think I've got it. Please reopen if I messed up.
Comment 39 Scott Miles 2013-02-22 19:18:46 UTC
I'm concerned about one thing and I want to get it in the record.

People can automate createDocument, and currently they only need to use a single string to do so. 

E.g., imagine:

MyLibrary.eltFactory = function(inTag, inAttributes, inContent) {
  var elt = document.createElement(inTag);
  // install attributes and content
  return elt;
};

Any code like that will have to be modified to work with the new extra parameter.

This is why I was hoping we could come up with some way of cramming both pieces of information into the first parameter (e.g. createElement("input/fancy-input")).

Maybe it's nothing to worry about, I'm not claiming this is a stopper.