Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved. W3C liability, trademark and document use rules apply.
This is a work in progress! This specification is for review and not for implementation! For the latest updates, including important bug fixes, please look at the draft on GitHub instead.
This specification describes the method for enabling the author to define and use new types of DOM elements in a document.
This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at http://www.w3.org/TR/.
This document was published by the Web Applications Working Group as an Editor's Draft. If you wish to make comments regarding this document, please send them to public-webapps@w3.org (subscribe, archives). All feedback is welcome.
Publication as a Working Draft does not imply endorsement by the W3C Membership. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.
This document was produced by a group operating under the 5 February 2004 W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.
This document is governed by the 14 October 2005 W3C Process Document.
All diagrams, examples, notes, are non-normative, as well as sections explicitly marked as non-normative. Everything else in this specification is normative.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in the normative parts of this document are to be interpreted as described in RFC2119. For readability, these words do not appear in all uppercase letters in this specification.
Any point, at which a conforming UA must make decisions about the state or reaction to the state of the conceptual model, is captured as algorithm. The algorithms are defined in terms of processing equivalence. The processing equivalence is a constraint imposed on the algorithm implementors, requiring the output of the both UA-implemented and the specified algorithm to be exactly the same for all inputs.
This document relies on the following specifications:
There are two motivations that fueled the development of this specification:
Most of the effort went into finding the right balance between the two motivations, driven by the hope that these motivations do not run counter to each other, but are rather complementary parts of the same larger story. For example, though the scope of the spec is currently limited to only creating custom elements by authors, it is designed to shorten the distance to a much more ambitious goal of rationalizing all HTML, SVG, and MathML elements into one coherent system.
Custom element is platform object whose interface is defined by the author. The interface prototype object of a custom element's interface is called the custom element prototype.
The custom element type identifies a custom element interface and is a sequence of characters that must match the NCName production, must contain a U+002D HYPHEN-MINUS character, and must not contain any uppercase ASCII letters. The custom element type must not be one of the following values:
annotation-xml
color-profile
font-face
font-face-src
font-face-uri
font-face-format
font-face-name
missing-glyph
The element definition describes a custom element and consists of:
At the time of creation, a document could be associated with a registry. A registry is a set of element definitions.
Element registration is a process of adding an element definition to a registry. One element definition can only be registered with one registry.
If a document has a registry associated with it, then for this document and a given element definition in the registry, the custom element's interface must be the element interface for local name and namespace values of custom element type and the namespace of the element definition, respectively.
If a document does not have a registry associated with it, all attempts at element registration will fail.
The exact nature of creating registries, their association with documents, and element registration are defined further in this specification.
A custom element can go through these changes during its lifetime:
Various callbacks can be invoked when a custom element goes through some of these changes. These callbacks are stored internally as a collection of key-value pairs and called lifecycle callbacks.
To transfer a callback named name from an object property named property to lifecycle callbacks, the user agent must run the following steps:
To facilitate invoking callbacks, each unit of related similar-origin browsing contexts has a processing stack, which is initially empty. Each item in the stack is an element queue, which is initially empty as well. Each item in the element queue is a custom element.
Each custom element has an associated callback queue and an element is being created flag. The flag is initially set to false and the callback queue is initially empty. Each item in the queue consists of the callback itself and zero or more string values that are used as callback arguments.
To invoke callbacks in an element queue, the user agent must run these steps or their equivalent:
Any time a script calls a method, reads or sets a property that is implemented by the user agent, the following actions must occur:
In addition to an element queue, there is also a sorted element queue. The custom elements are kept in the order of increasing custom element order.
The custom element order is a sum of document custom element order and import tree order, in which the import tree order is scaled so that its lowest value is always larger than the highest possible value of document custom element order.
The document custom element order is a numerical value, associated with every custom element. This value is as a result of custom element's document keeping a numerical value that is incremented and assigned to custom element as its custom element order whenever the following occurs:
The import tree order of a given custom element of an import link tree is determined by tree order in an import link tree that was flattened by replacing every import link
with the content of its imported document.
The highest stable order is the value that is immediately preceeding the custom element order of an element in the first encountered import, in tree order, that has not yet completely loaded. If there is no such element, the highest stable order is the highest custom element order in the flattened import link tree.
Because imports load asynchronously, we need to divide a sorted element queue into the part where things have settled down (all imports have loaded), and the part where the loading is still happening, and thus the actual sorting order is not yet determined. For example, suppose you have the following document structure:
index.html:
<link rel="import" href="import.html">
...
<me-second></me-second>
...
import.html:
<me-first></me-first>
The order of custom elements in the flattened import link tree is me-first
(1), me-second
(2). However, it's very likely that the parser will find out about me-second
sooner than me-first
, since the latter requires loading the import.html
. While the network stack is doing its job, the highest stable order stays at the beginning position. When import.html
is ready, the order jumps all the way to me-second
(2).
Each unit of related similar-origin browsing contexts has an initially-empty sorted element queue, called base element queue.
Whenever a base element queue becomes non-empty, the user agent must queue a microtask to process base element queue for the unit of related similar-origin browsing contexts to which the scripts' browsing context belongs.
To prevent reentrance while processing base element queue, each unit of related similar-origin contexts has a processing base element queue flag, which must initially be false.
To process base element queue, a conforming user agent must run the following steps or their equivalent:
In the unit of related similar-origin browsing contexts to which the scripts' browsing context belongs, the current element queue is the element queue at the top of the processing stack or the base element queue if the processing stack is empty.
To enqueue a lifecycle callback, the user must run the following steps or their equivalent:
The following callbacks are recognized:
To set custom element prototype on a custom element, a conforming user agent must run the following steps or their equivalent:
[[Prototype]]
internal property of ELEMENT to PROTOTYPE.When an HTML Document is loaded in a browsing context, a new registry must be created and associated with this document.
A new document instance must be associated with an existing registry in these cases:
DOMImplementation
's createDocument
method is invoked with namespace set to HTML Namespace or when the createHTMLDocument
method is invoked, use the registry of the associated document to the new instance.In all other cases, new documents must not have a registry.
Because element registration can occur at any time, a custom element could be created before its definition is registered. Such custom element instances are called unresolved elements. When an unresolved element is created, and if its element interface was not defined by HTML or other applicable specifications, the unresolved element's element interface must be:
HTMLElement
, if the namespace is HTML Namespace;SVGElement
, if namespace is SVG Namespace; orEach registry has an associated map of all instances of unresolved elements for a given pair of custom element type and namespace. This data structure is called the upgrade candidates map and is initially empty. Each value item in this map is a sorted element queue.
Whenever an unresolved element is created, it must be added to the respective sorted element queue in upgrade candidates map.
Registering an element definition is the responsibility of the element registration algorithm, which must be equivalent to running these steps:
None
, InvalidType
, InvalidName
, NoRegistry
, or DuplicateDefinition
None
, stop.NoRegistry
and stop.The definition construction algorithm creates an element definition and must be equivalent to running these steps:
None
, InvalidType
, InvalidName
, or DuplicateDefinition
None
InvalidType
and stop.SVGElement
inheritance detection algorithm with PROTOTYPE and the global environment of DOCUMENT as argumentsDuplicateDefinition
and stop.InvalidName
and stop.InvalidName
and stop.The SVGElement
inheritance detection algorithm must run these steps:
SVGElement
or false otherwiseSVGElement
's interface prototype object for ENVIRONMENTundefined
:
The element upgrade algorithm upgrades unresolved elements whose definition is now registered and must be equivalent to running these steps:
Document
InterfaceThe registerElement
method of the Document interface provides a way to register a custom element and returns its custom element constructor.
partial interface Document {
Function registerElement(DOMString type, optional ElementRegistrationOptions options);
};
dictionary ElementRegistrationOptions {
object? prototype = null;
DOMString? extends = null;
};
When called, the registerElement
method must run these steps:
Object.create
with HTMLElement
's interface prototype object as only argumentInvalidType
, throw a SyntaxError
and stop.None
, throw a NotSupportedError
and stop.ElementRegistrationOptions
is an abstraction that enables using function objects and ES6 classes as the second argument of document.register
method.
In order to register a custom element with a prototype, other than HTMLElement
or SVGElement
, the caller of document.registerElement
has to first build a proper prototype object that inherits from HTMLElement
. Here's a simple example of how one could do this:
document.registerElement('x-foo', {
prototype: Object.create(HTMLParagraphElement.prototype, {
firstMember: {
get: function() { return "foo"; },
enumerable: true,
configurable: true
},
// specify more members for your prototype.
// ...
}),
extends: 'p'
});
Note the use of extends
option to specify that the element is being registered as a type extension -- that is, this element does not introduce a new tag (like the custom tag elements do), but rather extends an existing element of type HTMLParagraphElement. Here's how one could instantiate this element:
<p is="x-foo">Paragraph of amazement</p>
Or imperatively, in JavaScript:
var foo = document.createElement('p', 'x-foo');
Elements with SVGElement
prototype deserve a special mention: using custom tag approach results in ignored elements in SVG. Thus, your SVG-based custom elements would almost always be type extensions.
The :unresolved
pseudoclass must match all custom elements whose created callback has not yet been invoked.
The :unresolved
pseudoclass could be used to mitigate the Flash of Unstyled Content (FOUC) issues with custom elements.
The custom element type is given to a custom element at the time of its instantation in one of the two ways:
is
attribute of the custom element. custom element types given this way are called type extensions.After a custom element is instantiated, changing the value of the is
attribute must not affect this element's custom element type.
If both types of custom element types are provided at the time of element's instantiation, the custom tag must win over the type extension.
All custom elements must be constructable with a function object, called custom element constructor. This constructor must be created with the custom element constructor generation algorithm, which must be equivalent to running these steps:
constructor
, throw a NotSupportedError
and stop.is
attribute to TYPEDocument
InterfaceTo allow creating both custom tag and type extension-style custom elements, the createElement
or createElementNS
methods have overloads with a typeExtension
argument:
partial interface Document {
Element createElement(DOMString localName, DOMString typeExtension);
Element createElementNS(DOMString? namespace, DOMString qualifiedName, DOMString typeExtension);
};
Instead of step 3 in createElement
and step 9 in createElementNS
(the steps that determine element interface, both methods must run the following
steps:
createElement
)Additionally, both createElement
or createElementNS
methods must run the following steps just before returning the result:
To enable instantiating custom elements during tree construction, a conforming UA must run enqueue created callback whenever creating a custom element.
This modification to tree construction has the consequence of custom elements being created when parsing HTML documents or fragments.
Once the ECMAScript Standard Edition 6 is released, this section will be integrated into the respective areas of this specification. Until then, here is an overview of how ECMAScript 6 and Custom Elements integrate.
If the user agent implements the @@create
method, this specification would stop treating the ElementRegistrationOptions options
argument in registerElement
as a dictionary, and instead view it as a the custom element constructor.
Instead of generating a constructor, the user agent will now mutate this argument to have a new @@create
method that creates a new element object.
Since the registerElement
's second argument is now a constructor function, the element definition should change to hold that constructor function, rather than the custom element prototype.
To accommodate this change, the element registration algorithm to the following steps:
None
, InvalidType
, InvalidName
, NoRegistry
, or DuplicateDefinition
None
, stop.NoRegistry
and stop.The steps run when calling registerElement
will change to:
FunctionAllocate
with HTMLElement
as the functionPrototype and true as strictHTMLElement
's interface prototype object as only argumentDefinePropertyOrThrow(
PROTOTYPE, "constructor", PropertyDescriptor{[[Value]]:
FUNCTION, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false})
DefinePropertyOrThrow(
FUNCTION, "prototype", PropertyDescriptor{[[Value]]:
PROTOTYPE, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false})
Get(
FUNCTION, "prototype")
InvalidType
, throw a SyntaxError
and stop.None
, throw a NotSupportedError
and stop.Similarly, the custom element constructor generation algorithm will change as follows:
NotSupportedError
and stop.is
attribute to TYPEDefinePropertyOrThrow(
FUNCTION, @@create, PropertyDescriptor{[[Value]]:
CREATE, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false})
The default semantics of a custom element is dependent upon the form in which it is instiantated:
By default a custom tag has no special meaning at all. It represents its children.
For example a custom tag, could be named taco-button but it does not express the semantics of a HTML button
element simply due to its name. As instaniated a custom tag conveys a similar amount of semantics as a HTML div
or span
element. The addition of visual styling and scripted events to the taco-button could provide hints as to the semantics and expected interaction behaviours of the custom element for some users, but for the semantics to be formally expressed, developers must convey the role, states and properties of the element to browser APIs.
<!-- taco-button represents a span with a fancy name -->
<taco-button></taco-button>
The addition of a tabindex
attribute to the taco-button element provides interaction (the element is included in the focus order) and property/state semantics (it exposes information that it is focusable and if it currently has focus).
<!-- taco-button represents a focusable span with a fancy name -->
<taco-button tabindex="0"></taco-button>
The addition of a text label to the taco-button element provides an Accessible Name for the element.
<!-- taco-button represents a focusable span with a fancy name and a text label -->
<taco-button tabindex="0">Eat Me</taco-button>
or
<taco-button tabindex="0" aria-label="Eat Me"></taco-button>
The addition of keyboard event handlers to the taco-button element provides the means for keyboard users to operate the control, but does not convey the presence of the functionality.
The addition of in line event handlers are for demonstration purposes only. The event handlers could be added by the lifecycle callbacks imperatively, or maybe even not used at all.
<!-- taco-button represents focusable span with a fancy name, a text label and button like event handling -->
<taco-button tabindex="0" onclick="alert('tasty eh?');"
onkeypress="if(event.keyCode==32||event.keyCode==13){alert('tasty eh?');};">Eat Me</taco-button>
The addition of an ARIA role="button"
conveys the taco-button element's role semantics, which enables users to successfully interact with the control using the expected button
interaction behaviours (pressing the space or enter
keys to activate).
<!-- taco-button represents a focusable button with a text label and button like event handling -->
<taco-button role="button" tabindex="0" onclick="alert('tasty eh?');"
onkeypress="if(event.keyCode==32||event.keyCode==13){alert('tasty eh?');};">Eat Me</taco-button>
By default a type extension inherits the semantics of the element type it extends.
For example a type extension, could extend the HTML button
element. As instaniated it would inherit the button
element's name, role, states and properties, built in focus and keyboard interaction behaviours.
<!-- taco-button represents a button with an accesssible name of "Eat Me!" -->
<button is="taco-button">Eat Me!</button>
To provide the desired taco-button feature, all that is required is the addition of an event handler. The rest of the semantics and interaction behaviour are provided by the browser as part of its implementation of the button
element.
<!-- taco-button represents a button -->
<button is="taco-button" onclick="alert('tasty eh?');">Eat Me!</button>
TO DO
To help navigate through various parts of the spec and their interactions, here is a diagram that attempts to put all algorithms actions that trigger them in one space. Click on each box to go to the corresponding algorithm or action.
David Hyatt developed XBL 1.0, and Ian Hickson co-wrote XBL 2.0. These documents provided tremendous insight into the problem of behavior attachment and greatly influenced this specification.
Alex Russell and his considerable forethought triggered a new wave of enthusiasm around the subject of behavior attachment and how it can be applied practically on the Web.
Dominic Cooney, Hajime Morrita, and Roland Steiner worked tirelessly to scope the problem within the confines of the Web platform and provided a solid foundation for this document.
The editor would also like to thank Alex Komoroske, Anne van Kesteren, Boris Zbarsky, Daniel Buchner, Edward O'Connor, Erik Arvidsson, Elliott Sprehn, Hayato Ito, Jonas Sicking, Olli Pettay, Rafael Weinstein, Scott Miles, Simon Pieters, Steve Orvell, Tab Atkins, and William Chen for their comments and contributions to this specification.
This list is too short. There's a lot of work left to do. Please contribute by reviewing and filing bugs—and don't forget to ask the editor to add your name into this section.