Bug 150225

Summary: Prototype custom element API
Product: WebKit Reporter: Ryosuke Niwa <rniwa>
Component: DOMAssignee: Ryosuke Niwa <rniwa>
Status: RESOLVED FIXED    
Severity: Normal CC: andrea.giammarchi, barraclough, cgarcia, eoconnor, ggaren, koivisto, mjs, patrick.arlt, sam, webkit-bug-importer, wilsonpage
Priority: P2 Keywords: InRadar
Version: WebKit Nightly Build   
Hardware: Unspecified   
OS: Unspecified   
Bug Depends on: 150586, 153005, 153092, 153114, 153173, 153327, 153342    
Bug Blocks:    
Attachments:
Description Flags
Basic support for document.defineCustomElement
none
Added basic support for Element.attributeChanged
none
Made tag name to super() optional
none
Updated for ToT
none
Made Element.attributeChanged robust none

Description Ryosuke Niwa 2015-10-16 00:59:48 PDT
We should prototype custom element API in WebKit.
Comment 1 Ryosuke Niwa 2015-10-16 01:00:49 PDT
<rdar://problem/22732164>
Comment 2 Ryosuke Niwa 2015-10-16 23:01:47 PDT
Created attachment 263360 [details]
Basic support for document.defineCustomElement

This WIP patch does the following:
- Added document.defineCustomElement(DOMString, CustomElementInterface)
- document.createElement support a custom element name
- HTMLElement constructible via custom element

With this, the following code does something useful:

class MyElement extends HTMLElement {
    constructor() {
        super('my-element');
        return this;
    }
}

document.defineCustomElement('my-element', MyElement);

new MyElement;
document.createElement('my-element');
Comment 3 Andrea Giammarchi 2015-10-21 02:31:02 PDT
Thanks for moving forward, but as maintainer of the document.registerElement polyfill, the one used behind recent ampproject too, I need to ask a couple of questions:

  1) what about the current standard API? https://w3c.github.io/webcomponents/spec/custom/#dfn-document-registerElement
  2) is document.defineCustomElement compatible with ES3 like classes or it's locked in with ES6 classes only?
  3) is the current proposal compatible with HTMLInputElement and others "special" elements that don't inherits from HTMLElement?

Thanks
Comment 4 Ryosuke Niwa 2015-10-27 08:22:40 PDT
(In reply to comment #3)
> Thanks for moving forward, but as maintainer of the document.registerElement
> polyfill, the one used behind recent ampproject too, I need to ask a couple
> of questions:
> 
>   1) what about the current standard API?
> https://w3c.github.io/webcomponents/spec/custom/#dfn-document-registerElement

We don't have any plan to support the current proposal as it's fundamentally incompatible with ES6 class syntax.

>   2) is document.defineCustomElement compatible with ES3 like classes or
> it's locked in with ES6 classes only?

It's compatible with ES5 classes because ES6 class is simply a syntax sugar on top of ES5 classes.

>   3) is the current proposal compatible with HTMLInputElement and others
> "special" elements that don't inherits from HTMLElement?

We don't have a plan to support inheritance from a subclass of HTMLElement such as HTMLInputElement.
Comment 5 Andrea Giammarchi 2015-10-27 08:35:42 PDT
AFAIK the following syntax is not compatible with ES5, hence my question.

```
class MyElement extends HTMLElement {
    constructor() {
        super('my-element');
        return this;
    }
}
```

what would be the equivalent in ES5 for `super('my-element')` ?
Is that just a `document.createElement('my-element')` ?

Thanks
Comment 6 Ryosuke Niwa 2015-10-27 08:41:45 PDT
(In reply to comment #5)
> AFAIK the following syntax is not compatible with ES5, hence my question.
> 
> ```
> class MyElement extends HTMLElement {
>     constructor() {
>         super('my-element');
>         return this;
>     }
> }
> ```
> 
> what would be the equivalent in ES5 for `super('my-element')` ?
> Is that just a `document.createElement('my-element')` ?

Ah, I think we can work around that problem by not requiring the argument to super() when MyElement has exactly one tag name associated name since we can infer the tag name based on new.target if it's associated with exactly one tag name.
Comment 7 Andrea Giammarchi 2015-10-27 11:37:52 PDT
Thanks again, please bear with me for last questions:

  1. is there any specification/document I can read about this implementation?
  2. is this statement correct: you can `extends HTMLElement` and HTMLElement only?
  3. can you define two elements with a different name, using the same class?
  4. are createdCallback, attachedCallback, detachedCallback, and attributeChangedCallback part of this implementation? If not, what else?


Thank you, I'm planning to work on a `document.defineCustomElement` polyfill, based on document.registerElement one, to see how better it works for developers but I need to know more details.

Best Regards
Comment 8 Patrick Arlt 2015-10-27 12:22:34 PDT
I also have some questions about this too. I recently have begun suggesting using the custom element spec as a way for my company to share common UI code among our apps.

1. Are other browser vendors considering an implementation of document.defineCustomElement
2. Is there a formal specification for this?
3. How would/could existing elements written for document.registerElement be used with this.

For example I've been writing a set of custom elements like this:


```
class MyElement extends HTMLElement {
  createdCallback () {}
  attachedCallback () {}
  detachedCallback () {}
  attributeChangedCallback () {}
}

document.registerElement('my-element', MyElement);
```

and compiling with Babel. When used with Andreas polyfill this works great. Based on your answers to previous questions it seems like I could so this:

```
class MyElement extends HTMLElement {
  constructor() {
    this.createdCallback();
    return this;
  }
  createdCallback () {}
  attachedCallback () {}
  detachedCallback () {}
  attributeChangedCallback () {}
}

// polyfill 1 of these 2 things...
if (document.defineCustomElement) {
  document.defineCustomElement('my-element', MyElement);
} else {
  document.registerElement('my-element', MyElement);
}

```

and compile this with Babel and have it work.

I like the syntax you have. I always thought `createdCallback` was weird and that you couldn't just use `new MyElement()` but I don't want a nicer syntax to come along with sacrificing the native browser support in Chrome or having a bunch of special cases for Safari or have to add an abstraction layer to deal with large differences in the specs.
Comment 9 Ryosuke Niwa 2015-10-27 22:52:43 PDT
(In reply to comment #7)
> Thanks again, please bear with me for last questions:
> 
>   1. is there any specification/document I can read about this
> implementation?

No. The point of this prototype/experiment is to come up with a proposal for custom element API.

>   2. is this statement correct: you can `extends HTMLElement` and
> HTMLElement only?

Yes.

>   3. can you define two elements with a different name, using the same class?

Yes, we're planning to allow that.

>   4. are createdCallback, attachedCallback, detachedCallback, and
> attributeChangedCallback part of this implementation? If not, what else?

No. Since we're using the constructor for creating, createdCallback won't be needed. We're likely renaming attachedCallback and detachedCallback as well since our callbacks' semantics will be slightly different.  Finally, we're probably going to have callbacks for attribute changes and children changes.


(In reply to comment #8)
> 1. Are other browser vendors considering an implementation of
> document.defineCustomElement

No, since we haven't even made a proposal yet.

> 2. Is there a formal specification for this?

Not yet.

> 3. How would/could existing elements written for document.registerElement be
> used with this.

It's too early in the prototyping stage to answer this question.
Comment 10 Andrea Giammarchi 2015-10-28 02:12:09 PDT
if I might, there is already an abstraction of mine that simplifies in a similar way the class definition and extends by default HTMLElement.

It's called dom-class
https://github.com/WebReflection/dom-class#custom-elements-callbacks

Maybe there's some possible hint in there too.

Thanks for coming back, please keep us updated with progresses.

Best Regards
Comment 11 Ryosuke Niwa 2015-10-28 21:34:30 PDT
Created attachment 264295 [details]
Added basic support for Element.attributeChanged

Added Element.attibuteChanged symbol and called in setAttribute as well as
a subset of DOM APIs that modify element attributes.
Comment 12 Ryosuke Niwa 2015-10-31 08:03:29 PDT
Created attachment 264477 [details]
Made tag name to super() optional

Made the tag name to super() call optional when a class defines exactly one element.
This allows constructor to be omitted in simple case.

e.g.
class MyElement extends HTMLElement {
    [Element.attributeChanged](name, oldValue, newValue) {
      ...
    }
};
document.defineCustomElement('my-element', MyElement);

will work.
Comment 13 Ryosuke Niwa 2015-11-07 16:19:43 PST
(In reply to comment #6)
> (In reply to comment #5)
> > AFAIK the following syntax is not compatible with ES5, hence my question.
> > 
> > ```
> > class MyElement extends HTMLElement {
> >     constructor() {
> >         super('my-element');
> >         return this;
> >     }
> > }
> > ```
> > 
> > what would be the equivalent in ES5 for `super('my-element')` ?
> > Is that just a `document.createElement('my-element')` ?
> 
> Ah, I think we can work around that problem by not requiring the argument to
> super() when MyElement has exactly one tag name associated name since we can
> infer the tag name based on new.target if it's associated with exactly one
> tag name.

On more careful consideration, that still doesn't work because ES5 will use MyElement's [[Construct]] which doesn't create the underlying backing store.
Comment 14 Ryosuke Niwa 2015-11-11 07:03:57 PST
Created attachment 265285 [details]
Updated for ToT
Comment 15 Ryosuke Niwa 2015-11-17 23:49:39 PST
Created attachment 265736 [details]
Made Element.attributeChanged robust

Added an assertion to make sure every IDL method / interface creates the processing stack if needed.
Comment 16 Ryosuke Niwa 2016-03-01 21:43:07 PST
We're done prototyping.
Comment 17 Ryosuke Niwa 2016-03-01 21:45:56 PST
The actual implementation of the API will take place in the bug 154907 instead.