Support: custom-elementsv1 Chrome for Android 69+ Chrome 67+ iOS Safari (limited) 10.3+ UC Browser for Android 11.8+ Firefox 63+ IE None Opera Mini None Safari (limited) 10.1+ Samsung Internet 6.2+ Opera (limited) 41+ Android Browser 67+
Source: caniuse.com
This section is non-normative.
Custom elements provide a way for authors to build their own fully-featured DOM elements. Although authors could always use non-standard elements in their documents, with application-specific behavior added after the fact by scripting or similar, such elements have historically been non-conforming and not very functional. By defining a custom element, authors can inform the parser how to properly construct an element and how elements of that class should react to changes.
Custom elements are part of a larger effort to "rationalise the platform", by explaining existing platform features (like the elements of HTML) in terms of lower-level author-exposed extensibility points (like custom element definition). Although today there are many limitations on the capabilities of custom elements—both functionally and semantically—that prevent them from fully explaining the behaviors of HTML's existing elements, we hope to shrink this gap over time.
This section is non-normative.
For the purposes of illustrating how to create an autonomous custom element , let's define a custom element that encapsulates rendering a small icon for a country flag. Our goal is to be able to use it like so:
<flag-icon
country="nl"></flag-icon>
To
do
this,
we
first
declare
a
class
for
the
custom
element,
extending
HTMLElement
:
class FlagIcon extends HTMLElement {
constructor() {
super();
this._countryCode = null;
}
static get observedAttributes() { return ["country"]; }
attributeChangedCallback(name, oldValue, newValue) {
// name will always be "country" due to observedAttributes
this._countryCode = newValue;
this._updateRendering();
}
connectedCallback() {
this._updateRendering();
}
get country() {
return this._countryCode;
}
set country(v) {
this.setAttribute("country", v);
}
_updateRendering() {
// Left as an exercise for the reader. But, you'll probably want to
// check this.ownerDocument.defaultView to see if we've been
// inserted into a document with a browsing context, and avoid
// doing any work if not.
}
}
We then need to use this class to define the element:
customElements.define("flag-icon",
FlagIcon);
At
this
point,
our
above
code
will
work!
The
parser,
whenever
it
sees
the
flag-icon
tag,
will
construct
a
new
instance
of
our
FlagIcon
class,
and
tell
our
code
about
its
new
country
attribute,
which
we
then
use
to
set
the
element's
internal
state
and
update
its
rendering
(when
appropriate).
You
can
also
create
flag-icon
elements
using
the
DOM
API:
const flagIcon = document.createElement("flag-icon")
flagIcon.country = "jp"
document.body.appendChild(flagIcon)
Finally, we can also use the custom element constructor itself. That is, the above code is equivalent to:
const flagIcon = new FlagIcon()
flagIcon.country = "jp"
document.body.appendChild(flagIcon)
This section is non-normative.
Customized built-in elements are a distinct kind of custom element , which are defined slightly differently and used very differently compared to autonomous custom elements . They exist to allow reuse of behaviors from the existing elements of HTML, by extending those elements with new custom functionality. This is important since many of the existing behaviors of HTML elements can unfortunately not be duplicated by using purely autonomous custom elements . Instead, customized built-in elements allow the installation of custom construction behavior, lifecycle hooks, and prototype chain onto existing elements, essentially "mixing in" these capabilities on top of the already-existing element.
Customized built-in elements require a distinct syntax from autonomous custom elements because user agents and other software key off an element's local name in order to identify the element's semantics and behavior. That is, the concept of customized built-in elements building on top of existing behavior depends crucially on the extended elements retaining their original local name.
In
this
example,
we'll
be
creating
a
customized
built-in
element
named
plastic-button
,
which
behaves
like
a
normal
button
but
gets
fancy
animation
effects
added
whenever
you
click
on
it.
We
start
by
defining
a
class,
just
like
before,
although
this
time
we
extend
HTMLButtonElement
instead
of
HTMLElement
:
class PlasticButton extends HTMLButtonElement {
constructor() {
super();
this.addEventListener("click", () => {
// Draw some fancy animation effects!
});
}
}
When
defining
our
custom
element,
we
have
to
also
specify
the
extends
option:
customElements.define("plastic-button",
PlasticButton,
{
extends:
"button"
});
In
general,
the
name
of
the
element
being
extended
cannot
be
determined
simply
by
looking
at
what
element
interface
it
extends,
as
many
elements
share
the
same
interface
(such
as
q
and
blockquote
both
sharing
HTMLQuoteElement
).
To
construct
our
customized
built-in
element
from
parsed
HTML
source
text,
we
use
the
is
attribute
on
a
button
element:
<button
is="plastic-button">Click
Me!</button>
Trying
to
use
a
customized
built-in
element
as
an
autonomous
custom
element
will
not
work;
that
is,
<plastic-button>Click
me?</plastic-button>
will
simply
create
an
HTMLElement
with
no
special
behavior.
If
you
need
to
create
a
customized
built-in
element
programmatically,
you
can
use
the
following
form
of
createElement()
:
const plasticButton = document.createElement("button", { is: "plastic-button" });
plasticButton.textContent
=
"Click
me!";
And as before, the constructor will also work:
const plasticButton2 = new PlasticButton();
console.log(plasticButton2.localName); // will output "button"
console.assert(plasticButton2 instanceof PlasticButton);
console.assert(plasticButton2
instanceof
HTMLButtonElement);
Note
that
when
creating
a
customized
built-in
element
programmatically,
the
is
attribute
will
not
be
present
in
the
DOM,
since
it
was
not
explicitly
set.
However,
it
will
be
added
to
the
output
when
serializing
:
console.assert(!plasticButton.hasAttribute("is"));
console.log(plasticButton.outerHTML);
//
will
output
'<button
is="plastic-button"></button>'
Regardless
of
how
it
is
created,
all
the
of
the
ways
in
which
button
is
special
apply
to
such
"plastic
buttons"
as
well:
their
focus
behavior,
ability
to
participate
in
form
submission
,
the
disabled
attribute,
and
so
on.
Customized
built-in
elements
are
designed
to
allow
extension
of
existing
HTML
elements
that
have
useful
user-agent
supplied
behavior
or
APIs.
As
such,
they
can
only
extend
existing
HTML
elements
defined
in
this
specification,
and
cannot
extend
legacy
elements
such
as
bgsound
,
blink
,
isindex
,
keygen
,
multicol
,
nextid
,
or
spacer
that
have
been
defined
to
use
HTMLUnknownElement
as
their
element
interface
.
One
reason
for
this
requirement
is
future-compatibility:
if
a
customized
built-in
element
was
defined
that
extended
a
currently-unknown
element,
for
example
combobox
,
this
would
prevent
this
specification
from
defining
a
combobox
element
in
the
future,
as
consumers
of
the
derived
customized
built-in
element
would
have
come
to
depend
on
their
base
element
having
no
interesting
user-agent-supplied
behavior.
This section is non-normative.
As
specified
below,
and
alluded
to
above,
simply
defining
and
using
an
element
called
taco-button
does
not
mean
that
such
elements
represent
buttons.
That
is,
tools
such
as
Web
browsers,
search
engines,
or
accessibility
technology
will
not
automatically
treat
the
resulting
element
as
a
button
just
based
on
its
defined
name.
To convey the desired button semantics to a variety of users, while still using an autonomous custom element , a number of techniques would need to be employed:
The
addition
of
the
tabindex
attribute
would
make
the
taco-button
interactive
content
,
thus
making
it
focusable
.
Note
that
if
the
taco-button
were
to
become
logically
disabled,
the
tabindex
attribute
would
need
to
be
removed.
The
addition
of
various
ARIA
attributes
helps
convey
semantics
to
accessibility
technology.
For
example,
setting
the
role
attribute
to
"
button
"
will
convey
the
semantics
that
this
is
a
button,
enabling
users
to
successfully
interact
with
the
control
using
usual
button-like
interactions
in
their
accessibility
technology.
Setting
the
aria-label
attribute
is
necessary
to
give
the
button
an
accessible
name
,
instead
of
having
accessibility
technology
traverse
its
child
text
nodes
and
announce
them.
And
setting
aria-disabled
to
"
true
"
when
the
button
is
logically
disabled
conveys
to
accessibility
technology
the
button's
disabled
state.
The
addition
of
event
handlers
to
handle
commonly-expected
button
behaviors
helps
convey
the
semantics
of
the
button
to
Web
browser
users.
In
this
case,
the
most
relevant
event
handler
would
be
one
that
proxies
appropriate
keydown
events
to
become
click
events,
so
that
you
can
activate
the
button
both
with
keyboard
and
by
clicking.
In
addition
to
any
default
visual
styling
provided
for
taco-button
elements,
the
visual
styling
will
also
need
to
be
updated
to
reflect
changes
in
logical
state,
such
as
becoming
disabled;
that
is,
whatever
stylesheet
style
sheet
has
rules
for
taco-button
will
also
need
to
have
rules
for
taco-button[disabled]
.
With
these
points
in
mind,
a
full-featured
taco-button
that
took
on
the
responsibility
of
conveying
button
semantics
(including
the
ability
to
be
disabled)
might
look
something
like
this:
class TacoButton extends HTMLElement {
static get observedAttributes() { return ["disabled"]; }
constructor() {
super();
this.addEventListener("keydown", e => {
if (e.keyCode === 32 || e.keyCode === 13) {
this.dispatchEvent(new MouseEvent("click", {
bubbles: true,
cancelable: true
}));
}
});
this.addEventListener("click", e => {
if (this.disabled) {
e.preventDefault();
e.stopPropagation();
}
});
this._observer = new MutationObserver(() => {
this.setAttribute("aria-label", this.textContent);
});
}
connectedCallback() {
this.setAttribute("role", "button");
this.setAttribute("tabindex", "0");
this._observer.observe(this, {
childList: true,
characterData: true,
subtree: true
});
}
disconnectedCallback() {
this._observer.disconnect();
}
get disabled() {
return this.hasAttribute("disabled");
}
set disabled(v) {
if (v) {
this.setAttribute("disabled", "");
} else {
this.removeAttribute("disabled");
}
}
attributeChangedCallback() {
// only is called for the disabled attribute due to observedAttributes
if (this.disabled) {
this.removeAttribute("tabindex");
this.setAttribute("aria-disabled", "true");
} else {
this.setAttribute("tabindex", "0");
this.setAttribute("aria-disabled", "false");
}
}
}
Even
with
this
rather-complicated
element
definition,
the
element
is
not
a
pleasure
to
use
for
consumers:
it
will
be
continually
"sprouting"
tabindex
and
aria-*
attributes
of
its
own
volition.
This
is
because
as
of
now
there
is
no
way
to
specify
default
accessibility
semantics
or
focus
behavior
for
custom
elements,
forcing
the
use
of
these
attributes
to
do
so
(even
though
they
are
usually
reserved
for
allowing
the
consumer
to
override
default
behavior).
In
contrast,
a
simple
customized
built-in
element
,
as
shown
in
the
previous
section,
would
automatically
inherit
the
semantics
and
behavior
of
the
button
element,
with
no
need
to
implement
these
behaviors
manually.
In
general,
for
any
elements
with
nontrivial
behavior
and
semantics
that
build
on
top
of
existing
elements
of
HTML,
customized
built-in
elements
will
be
easier
to
develop,
maintain,
and
consume.
This section is non-normative.
Because element definition can occur at any time, a non-custom element could be created , and then later become a custom element after an appropriate definition is registered. We call this process "upgrading" the element, from a normal element into a custom element.
Upgrades
enable
scenarios
where
it
may
be
preferable
for
custom
element
definitions
to
be
registered
after
relevant
elements
have
been
initially
created,
such
as
by
the
parser.
They
allow
progressive
enhancement
of
the
content
in
the
custom
element.
For
example,
in
the
following
HTML
document
the
element
definition
for
img-viewer
is
loaded
asynchronously:
<!DOCTYPE html>
<html lang="en">
<title>Image viewer example</title>
<img-viewer filter="Kelvin">
<img src="images/tree.jpg" alt="A beautiful tree towering over an empty savannah">
</img-viewer>
<script
src="js/elements/img-viewer.js"
async></script>
The
definition
for
the
img-viewer
element
here
is
loaded
using
a
script
element
marked
with
the
async
attribute,
placed
after
the
<img-viewer>
tag
in
the
markup.
While
the
script
is
loading,
the
img-viewer
element
will
be
treated
as
an
undefined
element,
similar
to
a
span
.
Once
the
script
loads,
it
will
define
the
img-viewer
element,
and
the
existing
img-viewer
element
on
the
page
will
be
upgraded,
applying
the
custom
element's
definition
(which
presumably
includes
applying
an
image
filter
identified
by
the
string
"Kelvin",
enhancing
the
image's
visual
appearance).
Note that upgrades only apply to elements in the document tree. (Formally, elements that are connected .) An element that is not inserted into a document will stay un-upgraded. An example illustrates this point:
<!DOCTYPE html>
<html lang="en">
<title>Upgrade edge-cases example</title>
<example-element></example-element>
<script>
"use strict";
const inDocument = document.querySelector("example-element");
const outOfDocument = document.createElement("example-element");
// Before the element definition, both are HTMLElement:
console.assert(inDocument instanceof HTMLElement);
console.assert(outOfDocument instanceof HTMLElement);
class ExampleElement extends HTMLElement {}
customElements.define("example-element", ExampleElement);
// After element definition, the in-document element was upgraded:
console.assert(inDocument instanceof ExampleElement);
console.assert(!(outOfDocument instanceof ExampleElement));
document.body.appendChild(outOfDocument);
// Now that we've moved the element into the document, it too was upgraded:
console.assert(outOfDocument instanceof ExampleElement);
</script>
When authoring custom element constructors , authors are bound by the following conformance requirements:
A
parameter-less
call
to
super()
must
be
the
first
statement
in
the
constructor
body,
to
establish
the
correct
prototype
chain
and
this
value
before
any
further
code
is
run.
A
return
statement
must
not
appear
anywhere
inside
the
constructor
body,
unless
it
is
a
simple
early-return
(
return
or
return
this
).
The
constructor
must
not
use
the
document.write()
or
document.open()
methods.
The element's attributes and children must not be inspected, as in the non- upgrade case none will be present, and relying on upgrades makes the element less usable.
The
element
must
not
gain
any
attributes
or
children,
as
this
violates
the
expectations
of
consumers
who
use
the
createElement
or
createElementNS
methods.
In
general,
work
should
be
deferred
to
connectedCallback
as
much
as
possible—especially
work
involving
fetching
resources
or
rendering.
However,
note
that
connectedCallback
can
be
called
more
than
once,
so
any
initialization
work
that
is
truly
one-time
will
need
a
guard
to
prevent
it
from
running
twice.
In general, the constructor should be used to set up initial state and default values, and to set up event listeners and possibly a shadow root .
Several of these requirements are checked during element creation , either directly or indirectly, and failing to follow them will result in a custom element that cannot be instantiated by the parser or DOM APIs. This is true even if the work is done inside a constructor-initiated microtask , as a microtask checkpoint can occur immediately after construction.
A custom element is an element that is custom . Informally, this means that its constructor and prototype are defined by the author, instead of by the user agent. This author-supplied constructor function is called the custom element constructor .
Two distinct types of custom elements can be defined:
An
autonomous
custom
element
,
which
is
defined
with
no
extends
option.
These
types
of
custom
elements
have
a
local
name
equal
to
their
defined
name
.
A
customized
built-in
element
,
which
is
defined
with
an
extends
option.
These
types
of
custom
elements
have
a
local
name
equal
to
the
value
passed
in
their
extends
option,
and
their
defined
name
is
used
as
the
value
of
the
is
attribute,
which
therefore
must
be
a
valid
custom
element
name
.
After
a
custom
element
is
created
,
changing
the
value
of
the
is
attribute
does
not
change
the
element's
behavior,
as
it
is
saved
on
the
element
as
its
is
value
.
Autonomous custom elements have the following element definition:
is
attribute
HTMLElement
)
An autonomous custom element does not have any special meaning: it represents its children. A customized built-in element inherits the semantics of the element that it extends.
Any
namespace-less
attribute
that
is
relevant
to
the
element's
functioning,
as
determined
by
the
element's
author,
may
be
specified
on
an
autonomous
custom
element
,
so
long
as
the
attribute
name
is
XML-compatible
and
contains
no
ASCII
upper
alphas
.
The
exception
is
the
is
attribute,
which
must
not
be
specified
on
an
autonomous
custom
element
(and
which
will
have
no
effect
if
it
is).
Customized
built-in
elements
follow
the
normal
requirements
for
attributes,
based
on
the
elements
they
extend.
To
add
custom
attribute-based
behavior,
use
data-*
attributes.
A valid custom element name is a sequence of characters name that meets all of the following requirements:
name
must
match
the
PotentialCustomElementName
production:
PotentialCustomElementName
::=
[a-z]
(
PCENChar
)*
'-'
(
PCENChar
)*
PCENChar
::=
"-"
|
"."
|
[0-9]
|
"_"
|
[a-z]
|
#xB7
|
[#xC0-#xD6]
|
[#xD8-#xF6]
|
[#xF8-#x37D]
|
[#x37F-#x1FFF]
|
[#x200C-#x200D]
|
[#x203F-#x2040]
|
[#x2070-#x218F]
|
[#x2C00-#x2FEF]
|
[#x3001-#xD7FF]
|
[#xF900-#xFDCF]
|
[#xFDF0-#xFFFD]
|
[#x10000-#xEFFFF]
This uses the EBNF notation from the XML specification. [XML]
name must not be any of the following:
annotation-xml
color-profile
font-face
font-face-src
font-face-uri
font-face-format
font-face-name
missing-glyph
The list of names above is the summary of all hyphen-containing element names from the applicable specifications , namely SVG and MathML . [SVG] [MATHML]
These requirements ensure a number of goals for valid custom element names :
They start with an ASCII lower alpha , ensuring that the HTML parser will treat them as tags instead of as text.
They do not contain any ASCII upper alphas , ensuring that the user agent can always treat HTML elements ASCII-case-insensitively.
They contain a hyphen, used for namespacing and to ensure forward compatibility (since no elements will be added to HTML, SVG, or MathML with hyphen-containing local names in the future).
They
can
always
be
created
with
createElement()
and
createElementNS()
,
which
have
restrictions
that
go
beyond
the
parser's.
Apart
from
these
restrictions,
a
large
variety
of
names
is
allowed,
to
give
maximum
flexibility
for
use
cases
like
<math-α>
or
<emotion-😍>
.
A custom element definition describes a custom element and consists of:
Function
callback
function
type
value
wrapping
the
custom
element
constructor
sequence<DOMString>
connectedCallback
",
"
disconnectedCallback
",
"
adoptedCallback
",
and
"
attributeChangedCallback
".
The
corresponding
values
are
either
a
Web
IDL
Function
callback
function
type
value,
or
null.
By
default
the
value
of
each
entry
is
null.
To look up a custom element definition , given a document , namespace , localName , and is , perform the following steps. They will return either a custom element definition or null:
If namespace is not the HTML namespace , return null.
If document does not have a browsing context , return null.
Let
registry
be
document
's
browsing
context
's
Window
relevant
global
object
's
CustomElementRegistry
object.
If there is custom element definition in registry with name and local name both equal to localName , return that custom element definition .
If there is a custom element definition in registry with name equal to is and local name equal to localName , return that custom element definition .
Return null.
CustomElementRegistry
interface
Each
Window
object
is
associated
with
a
unique
instance
of
a
CustomElementRegistry
object,
allocated
when
the
Window
object
is
created.
Custom
element
registries
are
associated
with
Window
objects,
instead
of
Document
objects,
since
each
custom
element
constructor
inherits
from
the
HTMLElement
interface,
and
there
is
exactly
one
HTMLElement
interface
per
Window
object.
The
customElements
attribute
of
the
Window
interface
must
return
the
CustomElementRegistry
object
for
that
Window
object.
[Exposed=Window]
interface CustomElementRegistry {
[(DOMString name, Function constructor, optional ElementDefinitionOptions options);
[CEReactions] void define(DOMString name, CustomElementConstructor constructor, optional ElementDefinitionOptions options);
any get(DOMString name);
Promise<void> whenDefined(DOMString name);
[CEReactions] void upgrade(Node root);
};
callback CustomElementConstructor = any ();
dictionary ElementDefinitionOptions {
DOMString extends;
};
Every
CustomElementRegistry
has
a
set
of
custom
element
definitions
,
initially
empty.
In
general,
algorithms
in
this
specification
look
up
elements
in
the
registry
by
any
of
name
,
local
name
,
or
constructor
.
Every
CustomElementRegistry
also
has
an
element
definition
is
running
flag
which
is
used
to
prevent
reentrant
invocations
of
element
definition
.
It
is
initially
unset.
Every
CustomElementRegistry
also
has
a
when-defined
promise
map
,
mapping
valid
custom
element
names
to
promises.
It
is
used
to
implement
the
whenDefined()
method.
customElements
.
define
(
name
,
constructor
)
customElements
.
define
(
name
,
constructor
,
{
extends:
baseLocalName
})
NotSupportedError
"
DOMException
will
be
thrown
upon
trying
to
extend
a
custom
element
or
an
unknown
element.
customElements
.
get
(
name
)
customElements
.
whenDefined
(
name
)
SyntaxError
"
DOMException
if
not
given
a
valid
custom
element
name
.
customElements
.
upgrade
(
root
)
Element
definition
is
a
process
of
adding
a
custom
element
definition
to
the
CustomElementRegistry
.
This
is
accomplished
by
the
define()
method.
When
invoked,
the
define(
name
,
constructor
,
options
)
method
must
run
these
steps:
If
IsConstructor
(
constructor
)
is
false,
then
throw
a
TypeError
.
If
name
is
not
a
valid
custom
element
name
,
then
throw
a
"
SyntaxError
"
DOMException
.
If
this
CustomElementRegistry
contains
an
entry
with
name
name
,
then
throw
a
"
NotSupportedError
"
DOMException
.
If
this
CustomElementRegistry
contains
an
entry
with
constructor
constructor
,
then
throw
a
"
NotSupportedError
"
DOMException
.
Let localName be name .
Let
extends
be
the
value
of
the
extends
member
of
options
,
or
null
if
no
such
member
exists.
If extends is not null, then:
If
extends
is
a
valid
custom
element
name
,
then
throw
a
"
NotSupportedError
"
DOMException
.
If
the
element
interface
for
extends
and
the
HTML
namespace
is
HTMLUnknownElement
(e.g.,
if
extends
does
not
indicate
an
element
definition
in
this
specification),
then
throw
a
"
NotSupportedError
"
DOMException
.
Set localName to extends .
If
this
CustomElementRegistry
's
element
definition
is
running
flag
is
set,
then
throw
a
"
NotSupportedError
"
DOMException
.
Set
this
CustomElementRegistry
's
element
definition
is
running
flag.
Run the following substeps while catching any exceptions:
Let prototype be Get ( constructor , "prototype"). Rethrow any exceptions.
If
Type
(
prototype
)
is
not
Object,
then
throw
a
TypeError
exception.
Let
lifecycleCallbacks
be
a
map
with
the
four
keys
"
connectedCallback
",
"
disconnectedCallback
",
"
adoptedCallback
",
and
"
attributeChangedCallback
",
each
of
which
belongs
to
an
entry
whose
value
is
null.
For each of the four keys callbackName in lifecycleCallbacks , in the order listed in the previous step:
Let callbackValue be Get ( prototype , callbackName ). Rethrow any exceptions.
If
callbackValue
is
not
undefined,
then
set
the
value
of
the
entry
in
lifecycleCallbacks
with
key
callbackName
to
the
result
of
converting
callbackValue
to
the
Web
IDL
Function
callback
type.
Rethrow
any
exceptions
from
the
conversion.
Let
observedAttributes
be
an
empty
sequence<DOMString>
.
If
the
value
of
the
entry
in
lifecycleCallbacks
with
key
"
attributeChangedCallback
"
is
not
null,
then:
Let observedAttributesIterable be Get ( constructor , "observedAttributes"). Rethrow any exceptions.
If
observedAttributesIterable
is
not
undefined,
then
set
observedAttributes
to
the
result
of
converting
observedAttributesIterable
to
a
sequence<DOMString>
.
Rethrow
any
exceptions
from
the
conversion.
Then, perform the following substep, regardless of whether the above steps threw an exception or not:
Unset
this
CustomElementRegistry
's
element
definition
is
running
flag.
Finally, if the first set of substeps threw an exception, then rethrow that exception (thus terminating this algorithm). Otherwise, continue onward.
Let definition be a new custom element definition with name name , local name localName , constructor constructor , observed attributes observedAttributes , and lifecycle callbacks lifecycleCallbacks .
Add
definition
to
this
CustomElementRegistry
.
Let
document
be
this
CustomElementRegistry
's
relevant
global
object
's
associated
Document
.
Let
upgrade
candidates
be
all
elements
that
are
shadow-including
descendants
of
document
,
whose
namespace
is
the
HTML
namespace
and
whose
local
name
is
localName
,
in
shadow-including
tree
order
.
Additionally,
if
extends
is
non-null,
only
include
elements
whose
is
value
is
equal
to
name
.
For each element element in upgrade candidates , enqueue a custom element upgrade reaction given element and definition .
If
this
CustomElementRegistry
's
when-defined
promise
map
contains
an
entry
with
key
name
:
Let promise be the value of that entry.
Resolve promise with undefined.
Delete
the
entry
with
key
name
from
this
CustomElementRegistry
's
when-defined
promise
map
.
When
invoked,
the
get(
name
)
method
must
run
these
steps:
If
this
CustomElementRegistry
contains
an
entry
with
name
name
,
then
return
that
entry's
constructor
.
Otherwise, return undefined.
When
invoked,
the
whenDefined(
name
)
method
must
run
these
steps:
If
name
is
not
a
valid
custom
element
name
,
then
return
a
new
promise
rejected
with
a
"
SyntaxError
"
DOMException
.
If
this
CustomElementRegistry
contains
an
entry
with
name
name
,
then
return
a
new
promise
resolved
with
undefined.
Let
map
be
this
CustomElementRegistry
's
when-defined
promise
map
.
If map does not contain an entry with key name , create an entry in map with key name and whose value is a new promise.
Let promise be the value of the entry in map with key name .
Return promise .
The
whenDefined()
method
can
be
used
to
avoid
performing
an
action
until
all
appropriate
custom
elements
are
defined
.
In
this
example,
we
combine
it
with
the
:defined
pseudo-class
to
hide
a
dynamically-loaded
article's
contents
until
we're
sure
that
all
of
the
autonomous
custom
elements
it
uses
are
defined.
articleContainer.hidden = true;
fetch(articleURL)
.then(response => response.text())
.then(text => {
articleContainer.innerHTML = text;
return Promise.all(
[...articleContainer.querySelectorAll(":not(:defined)")]
.map(el => customElements.whenDefined(el.localName))
);
})
.then(() => {
articleContainer.hidden = false;
});
When
invoked,
the
upgrade(
root
)
method
must
run
these
steps:
Let candidates be a list of all of root 's shadow-including inclusive descendant elements, in shadow-including tree order .
For each candidate of candidates , try to upgrade candidate .
The
upgrade()
method
allows
upgrading
of
elements
at
will.
Normally
elements
are
automatically
upgraded
when
they
become
connected
,
but
this
method
can
be
used
if
you
need
to
upgrade
before
you're
ready
to
connect
the
element.
const el = document.createElement("spider-man");
class SpiderMan extends HTMLElement {}
customElements.define("spider-man", SpiderMan);
console.assert(!(el instanceof SpiderMan)); // not yet upgraded
customElements.upgrade(el);
console.assert(el
instanceof
SpiderMan);
//
upgraded!
To upgrade an element , given as input a custom element definition definition and an element element , run the following steps:
If element is custom , then return.
This can occur due to reentrant invocation of this algorithm, as in the following example:
<!DOCTYPE html>
<x-foo id="a"></x-foo>
<x-foo id="b"></x-foo>
<script>
// Defining enqueues upgrade reactions for both "a" and "b"
customElements.define("x-foo", class extends HTMLElement {
constructor() {
super();
const b = document.querySelector("#b");
b.remove();
// While this constructor is running for "a", "b" is still
// undefined, and so inserting it into the document will enqueue a
// second upgrade reaction for "b" in addition to the one enqueued
// by defining x-foo.
document.body.appendChild(b);
}
})
</script>
This
step
will
thus
bail
out
the
algorithm
early
when
upgrade
an
element
is
invoked
with
"
b
"
a
second
time.
If
element
's
custom
element
state
is
"
failed
",
then
return.
Set element 's custom element definition to definition .
For
each
attribute
in
element
's
attribute
list
,
in
order,
enqueue
a
custom
element
callback
reaction
with
element
,
callback
name
"
attributeChangedCallback
",
and
an
argument
list
containing
attribute
's
local
name,
null,
attribute
's
value,
and
attribute
's
namespace.
If
element
is
connected
,
then
enqueue
a
custom
element
callback
reaction
with
element
,
callback
name
"
connectedCallback
",
and
an
empty
argument
list.
Add element to the end of definition 's construction stack .
Let C be definition 's constructor .
Run the following substeps while catching any exceptions:
Let constructResult be the result of constructing C , with no arguments.
If
C
non-conformantly
uses
an
API
decorated
with
the
[CEReactions]
extended
attribute,
then
the
reactions
enqueued
at
the
beginning
of
this
algorithm
will
execute
during
this
step,
before
C
finishes
and
control
returns
to
this
algorithm.
Otherwise,
they
will
execute
after
C
and
the
rest
of
the
upgrade
process
finishes.
If
SameValue
(
constructResult
,
element
)
is
false,
then
throw
an
"
InvalidStateError
"
DOMException
.
This
can
occur
if
C
constructs
another
instance
of
the
same
custom
element
before
calling
super()
,
or
if
C
uses
JavaScript's
return
-override
feature
to
return
an
arbitrary
object
from
the
constructor.
Then, perform the following substep, regardless of whether the above steps threw an exception or not:
Remove the last entry from the end of definition 's construction stack .
Assuming
C
calls
super()
(as
it
will
if
it
is
conformant
),
and
that
the
call
succeeds,
this
will
be
the
already
constructed
marker
that
replaced
the
element
we
pushed
at
the
beginning
of
this
algorithm.
(The
HTML
element
constructor
carries
out
this
replacement.)
If
C
does
not
call
super()
(i.e.
it
is
not
conformant
),
or
if
any
step
in
the
HTML
element
constructor
throws,
then
this
entry
will
still
be
element
.
Finally, if the above steps threw an exception, then:
Set
element
's
custom
element
state
to
"
failed
".
Set element 's custom element definition to null.
Empty element 's custom element reaction queue .
Rethrow the exception (thus terminating this algorithm).
Set
element
's
custom
element
state
to
"
custom
".
To try to upgrade an element , given as input an element element , run the following steps:
Let
definition
be
the
result
of
looking
up
a
custom
element
definition
given
element
's
node
document
,
element
's
namespace,
element
's
local
name,
and
element
's
is
value
.
If definition is not null, then enqueue a custom element upgrade reaction given element and definition .
A custom element possesses the ability to respond to certain occurrences by running author code:
When upgraded , its constructor is run, with no arguments.
When
it
becomes
connected
,
its
connectedCallback
is
called,
with
no
arguments.
When
it
becomes
disconnected
,
its
disconnectedCallback
is
called,
with
no
arguments.
When
it
is
adopted
into
a
new
document,
its
adoptedCallback
is
called,
given
the
old
document
and
new
document
as
arguments.
When
any
of
its
attributes
are
changed
,
appended
,
removed
,
or
replaced
,
its
attributeChangedCallback
is
called,
given
the
attribute's
local
name,
old
value,
new
value,
and
namespace
as
arguments.
(An
attribute's
old
or
new
value
is
considered
to
be
null
when
the
attribute
is
added
or
removed,
respectively.)
We call these reactions collectively custom element reactions .
The way in which custom element reactions are invoked is done with special care, to avoid running author code during the middle of delicate operations. Effectively, they are delayed until "just before returning to user script". This means that for most purposes they appear to execute synchronously, but in the case of complicated composite operations (like cloning , or range manipulation), they will instead be delayed until after all the relevant user agent processing steps have completed, and then run together as a batch.
Additionally, the precise ordering of these reactions is managed via a somewhat-complicated stack-of-queues system, described below. The intention behind this system is to guarantee that custom element reactions always are invoked in the same order as their triggering actions, at least within the local context of a single custom element . (Because custom element reaction code can perform its own mutations, it is not possible to give a global ordering guarantee across multiple elements.)
Each unit of related similar-origin browsing contexts has a custom element reactions stack , which is initially empty. The current element queue is the element queue at the top of the custom element reactions stack . Each item in the stack is an element queue , which is initially empty as well. Each item in an element queue is an element. (The elements are not necessarily custom yet, since this queue is used for upgrades as well.)
Each
custom
element
reactions
stack
has
an
associated
backup
element
queue
,
which
an
initially-empty
element
queue
.
Elements
are
pushed
onto
the
backup
element
queue
during
operations
that
affect
the
DOM
without
going
through
an
API
decorated
with
[CEReactions]
,
or
through
the
parser's
create
an
element
for
the
token
algorithm.
An
example
of
this
is
a
user-initiated
editing
operation
which
modifies
the
descendants
or
attributes
of
an
editable
element.
To
prevent
reentrancy
when
processing
the
backup
element
queue
,
each
custom
element
reactions
stack
also
has
a
processing
the
backup
element
queue
flag,
initially
unset.
All elements have an associated custom element reaction queue , initially empty. Each item in the custom element reaction queue is of one of two types:
An upgrade reaction , which will upgrade the custom element and contains a custom element definition ; or
A callback reaction , which will call a lifecycle callback, and contains a callback function as well as a list of arguments.
This is all summarized in the following schematic diagram:
To enqueue an element on the appropriate element queue , given an element element , run the following steps:
If the custom element reactions stack is empty, then:
Add element to the backup element queue .
If the processing the backup element queue flag is set, then return.
Set the processing the backup element queue flag.
Queue a microtask to perform the following steps:
Invoke custom element reactions in the backup element queue .
Unset the processing the backup element queue flag.
Otherwise, add element to the current element queue .
To enqueue a custom element callback reaction , given a custom element element , a callback name callbackName , and a list of arguments args , run the following steps:
Let definition be element 's custom element definition .
Let callback be the value of the entry in definition 's lifecycle callbacks with key callbackName .
If callback is null, then return
If
callbackName
is
"
attributeChangedCallback
",
then:
Let attributeName be the first element of args .
If definition 's observed attributes does not contain attributeName , then return.
Add a new callback reaction to element 's custom element reaction queue , with callback function callback and arguments args .
Enqueue an element on the appropriate element queue given element .
To enqueue a custom element upgrade reaction , given an element element and custom element definition definition , run the following steps:
Add a new upgrade reaction to element 's custom element reaction queue , with custom element definition definition .
Enqueue an element on the appropriate element queue given element .
To invoke custom element reactions in an element queue queue , run the following steps:
For each custom element element in queue :
Let reactions be element 's custom element reaction queue .
Repeat until reactions is empty:
Remove the first element of reactions , and let reaction be that element. Switch on reaction 's type:
If this throws an exception, catch it, and report the exception .
To
ensure
custom
element
reactions
are
triggered
appropriately,
we
introduce
the
[CEReactions]
IDL
extended
attribute
.
It
indicates
that
the
relevant
algorithm
is
to
be
supplemented
with
additional
steps
in
order
to
appropriately
track
and
invoke
custom
element
reactions
.
The
[CEReactions]
extended
attribute
must
take
no
arguments,
and
must
not
appear
on
anything
other
than
an
operation,
attribute,
setter,
or
deleter.
Additionally,
it
must
not
appear
on
readonly
attributes.
Operations,
attributes,
setters,
or
deleters
annotated
with
the
[CEReactions]
extended
attribute
must
run
the
following
steps
in
place
of
the
ones
specified
in
their
description:
Push a new element queue onto the custom element reactions stack .
Run the originally-specified steps for this construct, catching any exceptions. If the steps return a value, let value be the returned value. If they throw an exception, let exception be the thrown exception.
Let queue be the result of popping from the custom element reactions stack .
Invoke custom element reactions in queue .
If an exception exception was thrown by the original steps, rethrow exception .
If a value value was returned from the original steps, return value .
The intent behind this extended attribute is somewhat subtle. One way of accomplishing its goals would be to say that every operation, attribute, setter, and deleter on the platform must have these steps inserted, and to allow implementers to optimize away unnecessary cases (where no DOM mutation is possible that could cause custom element reactions to occur).
However, in practice this imprecision could lead to non-interoperable implementations of custom element reactions , as some implementations might forget to invoke these steps in some cases. Instead, we settled on the approach of explicitly annotating all relevant IDL constructs, as a way of ensuring interoperable behavior and helping implementations easily pinpoint all cases where these steps are necessary.
Any
nonstandard
APIs
introduced
by
the
user
agent
that
could
modify
the
DOM
in
such
a
way
as
to
cause
enqueuing
a
custom
element
callback
reaction
or
enqueuing
a
custom
element
upgrade
reaction
,
for
example
by
modifying
any
attributes
or
child
elements,
must
also
be
decorated
with
the
[CEReactions]
attribute.
As of the time of this writing, the following nonstandard or not-yet-standardized APIs are known to fall into this category:
HTMLElement
's
outerText
IDL
attribute
HTMLInputElement
's
webkitdirectory
and
incremental
IDL
attributes
HTMLLinkElement
's
disabled
and
scope
IDL
attributes
ShadowRoot
's
innerHTML
IDL
attribute