Support in all current engines.
This section is non-normative.
Custom elements 为作者提供了一种构建他们自己的完整特性的 DOM 元素的方式。即使作者们总是可以在他们的文档中使用非标准元素, 在生效之后使用类似脚本的方式给它们增加应用相关的行为, 但是这些元素由于历史原因表现并不一致,也不是很好用。 通过 定义 custom element, 作者可以告知解析器如何适当地构造一个元素,以及这类元素如何响应变化。
Custom elements 是 "解释 Web 平台" 努力中的一部分, 在这一工作中,我们通过将低层的可扩展点(比如定义 Custom Element)暴露给作者, 来解释既有的平台特性(比如 HTML 的元素)。 即使现在 Custom Element 在功能和语义上都有很多能力的局限, 尚不能完整地解释现存 HTML 元素的行为,我们希望随着时间的推移逐渐缩小这个差异。
This section is non-normative.
为了说明如何创建 autonomous custom element, 我们定义 Custom Element 来封装一个显示国旗的小图标。 我们的目标是可以像这样使用它:
<flag-icon country="nl"></flag-icon>
为了实现这一效果,我们首先为该 Custom Element 声明一个类,扩展
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. } }
我们现在需要使用这个类来定义这个元素:
customElements.define("flag-icon", FlagIcon);
此时,上述代码生效了!解析器一旦看到 flag-icon
标签, 就会为我们的 FlagIcon
类构造一个新的实例,并把新的 country
属性告诉我们的代码,
接着我们用它来设置内部状态并更新渲染(在适当的时候)。
你也可以使用 DOM API 创建 flag-icon
元素:
const flagIcon = document.createElement("flag-icon") flagIcon.country = "jp" document.body.appendChild(flagIcon)
最后,我们还可以直接使用 custom element 构造器。 这就是说上述代码等效于:
const flagIcon = new FlagIcon() flagIcon.country = "jp" document.body.appendChild(flagIcon)
This section is non-normative.
Adding a static formAssociated
property, with a true value, makes an
autonomous custom element a form-associated custom element. The
ElementInternals
interface helps you to implement functions and properties common
to form control elements.
class MyCheckbox extends HTMLElement {
static get formAssociated() { return true; }
static get observedAttributes() { return ['checked']; }
constructor() {
super();
this._internals = this.attachInternals();
this.addEventListener('click', this._onClick.bind(this));
}
get form() { return this._internals.form; }
get name() { return this.getAttribute('name'); }
get type() { return this.localName; }
get checked() { return this.getAttribute('checked'); }
set checked(flag) { this.toggleAttribute('checked', Boolean(flag)); }
attributeChangedCallback(name, oldValue, newValue) {
// name will always be "checked" due to observedAttributes
this._internals.setFormValue(this.checked ? 'on' : null);
}
_onClick(event) {
this.checked = !this.checked;
}
}
customElements.define('my-checkbox', MyCheckbox);
You can use the custom element my-checkbox
like a built-in
form-associated element. For example, putting it in form
or label
associates the my-checkbox
element with them, and submitting the
form
will send data provided by my-checkbox
implementation.
<form action="..." method="...">
<label><my-checkbox name="agreed"></my-checkbox> I read the agreement.</label>
<input type="submit">
</form>
This section is non-normative.
By using the appropriate properties of ElementInternals
, your custom element can
have default accessibility semantics. The following code expands our form-associated checkbox from
the previous section to properly set its default role and checkedness, as viewed by accessibility
technology:
class MyCheckbox extends HTMLElement {
static get formAssociated() { return true; }
static get observedAttributes() { return ['checked']; }
constructor() {
super();
this._internals = this.attachInternals();
this.addEventListener('click', this._onClick.bind(this));
this._internals.role = 'checkbox';
this._internals.ariaChecked = false;
}
get form() { return this._internals.form; }
get name() { return this.getAttribute('name'); }
get type() { return this.localName; }
get checked() { return this.getAttribute('checked'); }
set checked(flag) { this.toggleAttribute('checked', Boolean(flag)); }
attributeChangedCallback(name, oldValue, newValue) {
// name will always be "checked" due to observedAttributes
this._internals.setFormValue(this.checked ? 'on' : null);
this._internals.ariaChecked = this.checked;
}
_onClick(event) {
this.checked = !this.checked;
}
}
customElements.define('my-checkbox', MyCheckbox);
Note that, like for built-in elements, these are only defaults, and can be overridden by the
page author using the role
and aria-*
attributes:
<!-- This markup is non-conforming -->
<input type="checkbox" checked role="button" aria-checked="false">
<!-- This markup is probably not what the custom element author intended -->
<my-checkbox role="button" checked aria-checked="false">
Custom element authors are encouraged to state what aspects of their accessibility semantics
are strong native semantics, i.e., should not be overriden by users of the custom element. In our
example, the author of the my-checkbox
element would state that its
role and aria-checked
values are strong
native semantics, thus discouraging code such as the above.
This section is non-normative.
定制的内建元素 是一种独特的 custom element,它的定义稍有不同,但使用方式与 自主的 custom elements 很不一样。 它们的存在是为了允许通过扩展新的定制功能来复用现存 HTML 元素的行为。 因为如果只使用 自主的 custom elements 很多 HTML 元素的现存行为可能需要重复实现, 但使用 定制的内建元素 允许给既有的元素 安装自定义的构造行为、生命周期钩子、以及原型链。 本质上就是在已经有的元素上 "混入" 这些定制的功能。
定制的内建元素 要求一种与 自主的 custom elements 不同的语法,因为用户代理和其他软件会提取元素的 局部名 来识别元素的语义和行为。 就是说建立在既有行为基础上的 定制的内建元素 的概念 严重依赖于被扩展的元素保持他们原有的 局部名。
在这个例子中,我们将会创建一个名为 plastic-button
的
定制的内建元素。它像一个普通按钮一样,但是当点击时有漂亮的动画效果。
就像以前一样,我们从定义一个类开始,虽然这次我们扩展的是 HTMLButtonElement
而不是 HTMLElement
:
class PlasticButton extends HTMLButtonElement { constructor() { super(); this.addEventListener("click", () => { // Draw some fancy animation effects! }); } }
在定义我们的 custom element 时,我们必须同时声明 extends
选项:
customElements.define("plastic-button", PlasticButton, { extends: "button" });
通常被扩展元素的名字不能简单地通过看它扩展了什么元素接口来确定,
因为很多元素共用同样的接口(比如 q
和 blockquote
共用 HTMLQuoteElement
)。
为了使用我们的 定制内建元素,
我们使用 button
元素上的 is
属性:
<button is="plastic-button">Click Me!</button>
尝试将 定制的内建元素 当做
自主 custom element 使用 不会 奏效;
也就是说 <plastic-button>Click me?</plastic-button>
只会创建一个没有特殊行为的 HTMLElement
。
如果你需要编程方式创建一个类型扩展的元素,可以使用下面的形式
createElement()
:
const plasticButton = document.createElement("button", { is: "plastic-button" }); plasticButton.textContent = "Click me!";
像以前一样,构造器也好使:
const plasticButton2 = new PlasticButton(); console.log(plasticButton2.localName); // 将会输出 "button" console.log(plasticButton2.getAttribute("is")); // 将会输出 "plastic-button"
值得一提的是,所有 button
的特殊行为也适用于该 "plastic buttons":
获得焦点行为、参与 表单提交 的能力、
disabled
属性,等等。
定制的内建元素 设计为允许扩展既有 HTML 元素,尤其是那些用户代理提供了
有用行为或者 API 的元素。它们只能扩展在本标准中定义的现存 HTML 元素,不可继承遗留元素,
比如 bgsound
, blink
, isindex
, keygen
,
multicol
, nextid
, 或者
使用 HTMLUnknownElement
作为它们的 元素接口
来定义的 spacer
。
这一要求的一个原因是向后兼容性:如果定义了一个
定制内建元素,扩展了
一个目前未知的元素,比如 combobox
,这将会阻止本标准在将来定义一个
combobox
元素,因为派生过
定制内建元素 的客户可能依赖于它们的基元素
没有那些有趣的用户代理提供的行为。
此外, applet
元素不可被扩展,因为它们正在被 Web 平台移除的过程中。
This section is non-normative.
正如下文规定,以及上文提到的,简单地定义和使用一个称为
taco-button
不意味着这个元素代表 represent 按钮。也就是说像 Web 浏览器、搜索引擎、
或可访问性技术这些工具将不会自动仅仅基于它定义的名字,就把它当做一个按钮。
为了向不同的用户表达期望的按钮语义,在使用 自主 custom element 的时候, 就有必要应用下面这些技术了:
添加 tabindex
属性将会让
taco-button
变成 交互内容,
因此可以成为 可聚焦区域。
注意如果 taco-button
如果逻辑上被 disabled,
tabindex
属性就需要被移除。
添加各种 ARIA 属性帮助我们向可访问性技术表达语义。比如,
设置 role
属性为
"button
" 表示这是一个按钮,
使得用户可以在他们的可访问性技术中使用像按钮一样的工具,成功地与这一控件进行交互。
设置 aria-label
属性很有必要,
这会给按钮一个 可访问的名字,
而不是让可访问性技术遍历它的子文本节点并声明。
当按钮逻辑上被 disabled 时,
设置 aria-disabled
属性为 "true
"
向可访问性技术表示该按钮被禁用的状态。
添加按钮常见的事件处理器来帮助向 Web 浏览器用户表达按钮的语义。
在这一情形下,最相关的事件处理器当数代理合适的
keydown
事件并把它变成
click
事件,这样你既可以用键盘也可以用鼠标来激活该按钮。
除了 taco-button
元素的默认视觉样式之外,
其视觉样式也将需要更新来反映其逻辑状态的变化,比如被禁用;
这就是说任何包含 taco-button
规则的样式表都将需要
包含 taco-button[disabled]
规则。
考虑到这些点,一个全特性的 taco-button
想要负责表达按钮的语义
(包括被禁用的能力),看起来可能是这样的:
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"); } } }
即使有这样复杂的元素定义,该元素对用户来讲并不好用:它不断地根据自己的意愿添加
tabindex
和 aria-*
属性。
这是因为现在还没有办法设置 Custom Element 的默认的可访问性语义或者获得焦点行为。
只能强制使用这些属性来完成这一工作(即使它们通常是保留的,只供用户来重写默认行为)。
相比而言,一个简单的 定制内建元素,
如上一节所述,将会自动继承 button
的语义和行为,不需手动实现这些行为。
通常对于任何有着建立在既有 HTML 元素之上的明显语义和行为的元素,
定制内建元素 更容易开发、维护,以及使用。
This section is non-normative.
因为 元素定义 任何时候都可以发生, 可能会先 创建 一个非 custom element, 在适当的 定义 注册之后, 才变成一个 custom element。 我们称这一过程为 "升级" 元素:从一个普通元素编程一个 Custom Element。
升级 使得
custom element 定义 可以在
相关元素初(比如被解析器)始创建之后再行注册。这允许渐进增强 Custom Element 的内容。
例如在下列 HTML 文档中,img-viewer
的元素定义是异步载入的:
<!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>
img-viewer
元素的定义是被一个标记了
async
属性的 script
元素加载的。
在标记代码中该脚本放置在 <img-viewer>
标签之后。
当脚本正在载入时,img-viewer
元素将会被当做未定义元素,
类似 span
。一旦脚本载入,它将会定义 img-viewer
元素,
并且页面中既有的 img-viewer
元素将会被升级,
应用 Custom Element 的定义(大概包括根据 "Kelvin" 字符串应用图像光栅,增强图像的视觉效果)。
注意 升级 只会应用于文档树中的元素。 (形式化地说是 已连接的 元素)。没有插入到文档中的元素将会保持未升级的状态。下面的例子说明了这一点:
<!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.
When authoring custom element reactions, authors should avoid manipulating the node tree as this can lead to unexpected results.
An element's connectedCallback
can be queued before the element is
disconnected, but as the callback queue is still processed, it results in a connectedCallback
for an element that is no longer connected:
class CParent extends HTMLElement {
connectedCallback() {
this.firstChild.remove();
}
}
customElements.define("c-parent", CParent);
class CChild extends HTMLElement {
connectedCallback() {
console.log("CChild connectedCallback: isConnected =", this.isConnected);
}
}
customElements.define("c-child", CChild);
const parent = new CParent(),
child = new CChild();
parent.append(child);
document.body.append(parent);
// Logs:
// CChild connectedCallback: isConnected = false
custom element 是 自定义的 元素。 非形式化地,这意味着它的构造器和原型链由作者而不是用户代理定义。 作者提供的构造函数称为 custom element 构造器。
可以定义两种不同的 custom elements:
自主的 custom element 定义时不包含 extends
选项。这类 custom elements 的 局部名 等于它们
定义的名称。
定制内建元素 定义时包含 extends
选项。这类 custom elements 的 局部名 等于它们
在 extends
选项中传入的值,它们的 定义的名称 被用于
is
属性的值,因此它必须是一个
合法的 custom element 名。
在 custom element 被 创建 后,
更改 is
属性的值不会更改该元素的行为,因为它只是
保存为该元素的 is
的值。
自主的 custom elements 有下列的元素定义:
is
之外的 全局属性form
— Associates the element with a form
elementdisabled
— Whether the form control is disabledreadonly
— Affects willValidate
, plus any behavior added by the custom element authorname
— Name of the element to use for form submission and in the form.elements
API HTMLElement
)自主的 custom element 没有任何特殊含义:它 表示 它的子节点。 定制内建元素 继承了它扩展的元素的语义。
任何没有命名空间的并与元素功能相关的属性,都可以按照元素作者的决定添加到
自主的 custom element 上,
只要该属性名是 XML 兼容的 其不含 ASCII 大写字母。is
属性较为特殊,
它不能声明在 自主的 custom element 上(如果设置的话不会有任何效果)。
定制内建元素 遵循普通的属性名要求
(基于它们扩展的元素)。新增自定义的基于属性的行为,可使用
data-*
属性。
一个 自主的 custom element,如果与一个 form-associated 字段为 true 的 Custom Element 定义 相关联, 就称为 表单相关的 custom element。
name
属性表示 表单相关的 custom element 的名字。
disabled
属性用来让 表单相关的 custom element
不可交互并阻止它的 提交数据 被提交。
form
属性用来明确地把
表单相关的 custom element 和它的 表单 owner 相关联。
readonly
属性指定该元素
禁止约束验证。用户代理不会给这个属性提供任何其他行为,
但 custom element 作者应该利用它是否存在来以某种方式让控件变得不可编辑,类似内置控件的
readonly 属性那样。
强校验:如果 表单相关的 custom element 的指定了
readonly
属性,该元素就 禁止了约束验证。
表单相关的 custom element 的 重置算法
是用这个元素 入队一个 Custom Element 反应,回调名为 "formResetCallback
",参数列表为空。
合法的 custom element 名 是满足以下要求的一串字符 name:
name 必须匹配 PotentialCustomElementName
生成式:
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]
name 不可是下列之一:
annotation-xml
color-profile
font-face
font-face-src
font-face-uri
font-face-format
font-face-name
missing-glyph
上述要求保证了 合法 custom element 名 的这些目标:
以 ASCII 小写字母 起始,确保 HTML 解析器将它们当做标签而不是文本。
不包含任何 ASCII 大写字母, 确保用户代理总是可以将 HTML 元素当做大小写不敏感的 ASCII 来处理。
包含一个横线,用于做命名空间来确保向后兼容(因为 HTML、SVG、MathML 中新增的元素 局部名 不会包含横线)。
总是可以使用 createElement()
和 createElementNS()
来创建它们,
这些 API 有着比解析器更高的限制。
除这些限制之外,允许了大量的名字来给像
<math-α>
或 <emotion-😍>
这样的用例最大的自由。
Custom Element 定义 描述了一个 custom element 的组成:
Function
回调函数类型的值,包装了
custom element 构造器sequence<DOMString>
connectedCallback
",
"disconnectedCallback
",
"adoptedCallback
",
"attributeChangedCallback
",
"formAssociatedCallback
",
"formDisabledCallback
",
"formResetCallback
" 和 "formStateRestoreCallback
"。
相应的值是 Web IDL Function
回调函数类型的值,或者 null。默认每一项的值都是null。attachInternals()
。
attachShadow()
。
给定一个 document,namespace,localName,以及 is, 查找一个 custom element 的定义 应执行下列步骤( 返回一个 custom element 定义 或 null):
如果 namespace 不是 HTML 命名空间,返回 null。
如果 document 的 浏览环境 是 null,返回 null。
令 registry 为 document 的
相关全局对象 的
CustomElementRegistry
对象。
如果在 registry 中有一个 custom element 定义 的 name 和 局部名 都等于 localName,返回该 custom element 定义。
如果在 registry 中有一个 custom element 定义 的 name 等于 is 且 局部名 等于 localName,返回该 custom element 定义。
返回 null。
CustomElementRegistry
接口Support in all current engines.
每个 Window
对象都关联是一个唯一的
CustomElementRegistry
对象实例,当 Window
对象创建时分配。
Custom element registries 与 Window
对象而不是 Document
对象关联,因为每个 custom element 构造函数 继承自 HTMLElement
接口,而每个 Window
对象只有一个 HTMLElement
接口。
Support in all current engines.
Window
接口的 customElements
属性必须返回该 Window
对象的 CustomElementRegistry
对象。
interface CustomElementRegistry { [CEReactions] undefined define(DOMString name, CustomElementConstructor constructor, optional ElementDefinitionOptions options = {}); (CustomElementConstructor or undefined) get(DOMString name); Promise<CustomElementConstructor> whenDefined(DOMString name); [CEReactions] undefined upgrade(Node root); }; callback CustomElementConstructor = HTMLElement (); dictionary ElementDefinitionOptions { DOMString extends; };
每个 CustomElementRegistry
有一个 custom element 定义 的集合,初始为空。通常本标准中的算法在 registry 中通过
name,
局部名,
或 constructor
中的任意一个进行查找。
每个 CustomElementRegistry
还有一个
element 定义正在运行 标记,
用来防止 元素定义 被再次调用。
初始未设置。
每个 CustomElementRegistry
还有一个 when-defined Promise 映射,
将 合法的 custom element 名 映射为 Promise。
用来实现 whenDefined()
方法。
customElements
. define
(name,
constructor)Support in all current engines.
customElements
. define
(name, constructor,
{ extends: baseLocalName })NotSupportedError
" DOMException
。customElements
. get
(name)Support in all current engines.
customElements
. whenDefined
(name)CustomElementRegistry/whenDefined
Support in all current engines.
SyntaxError
" DOMException
拒绝的 Promise。元素定义 是向 CustomElementRegistry
新增一个 custom element 定义 的过程。
由 define()
方法完成。
define(name, constructor,
options)
方法被调用时必须运行下列步骤:
如果 IsConstructor(constructor) 为 false,抛出一个
TypeError
并中止这些步骤。
如果 name 不是一个 合法的 custom element 名,则抛出一个
"SyntaxError
" DOMException
并中止这些步骤。
如果该 CustomElementRegistry
包含 name 值为 name 的项目,
则抛出一个 "NotSupportedError
" DOMException
并中止这些步骤。
如果该 CustomElementRegistry
包含 constructor 值为 constructor 的项目,
则抛出一个 "NotSupportedError
" DOMException
并中止这些步骤。
令 localName 为 name。
令 extends 为 options 的 extends
成员(没有该成员则为 null)。
如果 extends 不为 null,则:
如果 extends 是一个 合法的 custom element 名,则抛出
"NotSupportedError
" DOMException
。
如果 extends 的 元素接口 的 HTML 命名空间 是 HTMLUnknownElement
(例如,如果 extends 表示的元素定义不在本标准中),则抛出
"NotSupportedError
" DOMException
。
设置 localName 为 extends。
如果该 CustomElementRegistry
的 元素定义正在运行
标记被设置,则抛出一个 "NotSupportedError
" DOMException
并中止这些步骤。
设置该 CustomElementRegistry
的 元素定义正在运行
标记。
令 formAssociated 为 false。
令 disableInternals 为 false。
令 disableShadow 为 false。
令 observedAttributes 为空 sequence<DOMString>
。
运行下列子步骤并捕获所有异常:
令 prototype 为 Get(constructor, "prototype")。重新抛出任何异常。
令 lifecycleCallbacks 为生命周期回调映射,并拥有键 "connectedCallback
", "disconnectedCallback
", "adoptedCallback
", 和 "attributeChangedCallback
",
每一项对应的值均为 null。
对 lifecycleCallbacks 键中的每一项 callbackName,按照上一步列出的顺序:
如果 lifecycleCallbacks 中键为 "attributeChangedCallback
" 的项目的值不是 null,则:
令 disabledFeatures 为空 sequence<DOMString>
。
令 disabledFeaturesIterable 为 Get(constructor, "disabledFeatures")。 重新抛出任何异常。
如果 disabledFeaturesIterable 不是 undefined,设置 disabledFeatures 为
disabledFeaturesIterable 转换为
sequence<DOMString>
的结果。
重新抛出转换中的任何异常。
如果 disabledFeatures 包含 "internals
",
设置 disableInternals 为 true。
如果 disabledFeatures 包含 "shadow
",
设置 disableShadow 为 true。
令 formAssociatedValue 为 Get( constructor, "formAssociated")。重新抛出任何异常。
设置 formAssociated 为
formAssociatedValue 转换为 boolean
的结果。
重新抛出转换中的任何异常。
如果 formAssociated 为 true,对每个
"formAssociatedCallback
", "formResetCallback
",
"formDisabledCallback
" 和
"formStateRestoreCallback
" callbackName:
然后执行下列的子步骤,不论上述步骤是否抛出异常:
重置 CustomElementRegistry
的 元素定义正在运行 标记。
最终,如果第一批子步骤抛出了异常,则重新抛出该异常并终止算法。否则继续。
令 definition 为一个新的 custom element 定义, 其 name 为 name, 其 局部名 为 localName, 其 constructor 为 constructor, 其 observed attributes 为 observedAttributes, 其 lifecycle callbacks 为 lifecycleCallbacks, 其 form-associated 为 formAssociated, 其 disable internals 为 disableInternals, 其 disable shadow 为 disableShadow。
添加 definition 到该该 CustomElementRegistry
。
令 document 为该 CustomElementRegistry
的 相关全局对象 的 关联的 Document
。
令 upgrade candidates 为
document 的所有 shadow-including 后代 中,
命名空间为 HTML 命名空间 且 局部名 为 localName 的元素,
以 shadow-including 树序。
此外如果 extends 不是 null,则只包括 is
的值 等于 name 的元素。
对 upgrade candidates 中的每一个元素 element, 将一个 Custom Element 升级响应加入队列,并指定 element 和 definition。
如果该 CustomElementRegistry
的 when-defined Promise 映射
包含键为 name 的项目:
令 promise 为该项目的值。
以 constructor 完成 promise。
从该 CustomElementRegistry
的 when-defined promise 映射
中删除键为 name 的项目。
get(name)
方法被调用时必须执行这些步骤:
如果该 CustomElementRegistry
包含 name 为 name 条目,则返回该条目的
构造函数。
否则返回 undefined。
whenDefined(name)
方法被调用时必须执行这些步骤:
如果 name 不是一个 合法的 custom element 名称,则返回一个新的
promise,以 "SyntaxError
" DOMException
拒绝它并中止这些步骤。
如果该 CustomElementRegistry
包含一个 name 为 name 的条目,则返回一个 Promise,其值为
那个条目的 构造函数。
令 map 为该 CustomElementRegistry
的 when-defined promise 映射。
如果 map 不包含键为 name 的条目,在 map 中创建一个条目, 其键为 name,值为一个新的 promise。
令 promise 为 map 中键为 name 的条目的值。
返回 promise。
whenDefined()
方法可以用来避免
在合适的 custom elements 被定义 之前执行操作。
这个例子中我们将它与 :defined
伪类结合,
来隐藏一个动态载入的文章内容,直到我们确定它使用的所有
自主 Custom Element 已经定义。
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;
});
调用 upgrade(root)
方法时必须执行以下步骤:
令 candidates 为 root 中 包括 shadow 的后代 所有元素的 列表, 按照 包括 shadow 的树排序。
upgrade()
方法允许按需升级元素。
正常情况下,元素会在 连接到 DOM 时自动升级,
但也可以在已经准备好连接到 DOM 时调用这个方法。
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!
给定输入 custom element 定义 definition 和一个元素 element,升级元素 应运行以下步骤:
如果 element 是 自定义的,中止这些步骤。
这一情况在再次进入该算法时就会发生,比如下面的例子:
<!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>
这一步会使得第二次以 "b
" 为参数调用
升级元素 算法时直接早退出。
如果 element 的 custom element 状态 是 failed
",则中止这些步骤。
对 element 的 属性列表 中每一个 attribute,
按顺序 入队一个 Custom Element 反应
(元素为 element,回调名为 "attributeChangedCallback
"),
参数列表为 attribute 的 局部名,null,attribute的值,以及 attribute 的命名空间。
如果 element 已连接,
入队一个 Custom Element 反应
(元素为 element,回调名为 "attributeChangedCallback
"),参数列表为空。
将 element 添加到 definition 的 构造栈 尾部。
令 C 为 definition 的 构造器。
运行以下子步骤的同时捕获任何异常:
令 constructResult 为无参数 构造 C 的结果。
如果 C 不合规范地
使用了以 [CEReactions]
扩展属性修饰的 API,
那么就在这一步中,在 C 结束前执行本算法开始时入队的回调,此后将控制返回给本算法。
否则,在 C 和其他升级过程结束后再执行它们。
如果 SameValue(constructResult, element) 为 false,
那么抛出一个 "InvalidStateError
" DOMException
。
如果 C 在调用 super()
之前构造了同一个 Custom Element 的另一个实例,
或者 C 使用了 JavaScript 的 return
重写特性并在构造函数中返回一个任意的对象时,
就会发生这种情况。
然后执行下列子步骤,无论上述步骤是否抛出了异常:
从 definition 的 构造栈 尾部移除最后一个入口。
假设 C 调用了 super()
(如果它 符合规范 就会这样做),并且该调用是成功的,此时 this 就是
已构造 标记,替换了本算法最开始推入的 element。
(就是 HTML 元素构造器 执行了这个替换。)
如果 C 没有调用 super()
(即它不 符合规范),或者在 HTML 元素构造 中的任何一步抛出了异常,那么这个入口仍然是 element。
最后,如果上述步骤抛出了异常:
设置 element 的 custom element 状态 为 "failed
"。
清空 element 的 custom element 反应队列。
重新抛出该异常,终止本算法。
设置 element 的 custom element 状态 为 "custom
"。
设置 element 的 custom element 定义 为 definition。
给定输入元素 element,尝试升级元素, 应执行下列步骤:
令 definition 为给定 element 的 节点文档,
element的命名空间, element的 局部名,以及 element 的 is
值,
查找 custom element 定义 的结果。
如果 definition 不是 null,那么 入队一个 Custom Element 升级反应, 其元素为 element,定义为 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.)
When the user agent resets the form owner of a
form-associated custom element and doing so changes the form owner, its formAssociatedCallback
is called, given the new form owner (or null if no owner)
as an argument.
When the form owner of a form-associated custom element is reset, its formResetCallback
is
called.
When the disabled state of a
form-associated custom element is changed, its formDisabledCallback
is called, given the new state as an
argument.
When user agent updates a form-associated custom element's value on behalf of
a user, its formStateRestoreCallback
is called, given the new value and a
string indicating a reason, "restore
" or "autocomplete
", as arguments.
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 similar-origin window agent has a custom element reactions stack, which is initially empty. A similar-origin window agent's current element queue is the element queue at the top of its 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:
Let reactionsStack be element's relevant agent's custom element reactions stack.
If reactionsStack is empty, then:
Add element to reactionsStack's backup element queue.
If reactionsStack's processing the backup element queue flag is set, then return.
Set reactionsStack's processing the backup element queue flag.
Queue a microtask to perform the following steps:
Invoke custom element reactions in reactionsStack's backup element queue.
Unset reactionsStack's processing the backup element queue flag.
Otherwise, add element to element's relevant agent's 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 this object's relevant agent's 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 this object's relevant agent's 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 scope
IDL attribute
ShadowRoot
's innerHTML
IDL attribute
Certain capabilities are meant to be available to a custom element author, but not to a custom
element consumer. These are provided by the element.attachInternals()
method, which returns an instance of
ElementInternals
. The properties and methods of ElementInternals
allow
control over internal features which the user agent provides to all elements.
attachInternals()
Returns an ElementInternals
object targeting the custom element
element. Throws an exception if element is not a custom
element, if the "internals
" feature was disabled as part of the
element definition, or if it is called twice on the same element.
Each HTMLElement
has an attached internals boolean, initially
false.
The attachInternals()
method steps
are:
If this's is
value is not null, then throw a "NotSupportedError
"
DOMException
.
Let definition be the result of looking up a custom element definition given this's node
document, its namespace, its local name, and null as the is
value.
If definition is null, then throw an
"NotSupportedError
" DOMException
.
If definition's disable internals is true,
then throw a "NotSupportedError
" DOMException
.
If this's attached internals is true, then throw an
"NotSupportedError
" DOMException
.
If this's custom element state is not "precustomized
" or "custom
", then throw a
"NotSupportedError
" DOMException
.
Set this's attached internals to true.
Return a new ElementInternals
instance whose target element is this.
ElementInternals
interfaceThe IDL for the ElementInternals
interface is as follows, with the various operations and attributes
defined in the following sections:
[Exposed=Window]
interface ElementInternals {
// Shadow root access
readonly attribute ShadowRoot
? shadowRoot;
// Form-associated custom elements
undefined setFormValue((File or USVString or FormData)? value,
optional (File or USVString or FormData)? state);
readonly attribute HTMLFormElement? form;
undefined setValidity(optional ValidityStateFlags flags = {},
optional DOMString message,
optional HTMLElement anchor);
readonly attribute boolean willValidate;
readonly attribute ValidityState validity;
readonly attribute DOMString validationMessage;
boolean checkValidity();
boolean reportValidity();
readonly attribute NodeList labels;
};
// Accessibility semantics
ElementInternals includes ARIAMixin;
dictionary ValidityStateFlags {
boolean valueMissing = false;
boolean typeMismatch = false;
boolean patternMismatch = false;
boolean tooLong = false;
boolean tooShort = false;
boolean rangeUnderflow = false;
boolean rangeOverflow = false;
boolean stepMismatch = false;
boolean badInput = false;
boolean customError = false;
};
Each ElementInternals
has a target element,
which is a custom element.
shadowRoot
Returns the ShadowRoot
for internals's
target element, if the
target element is a shadow host, or null
otherwise.
The shadowRoot
getter steps
are:
Let target be this's target element.
If target is not a shadow host, then return null.
Let shadow be target's shadow root.
If shadow's available to element internals is false, then return null.
Return shadow.
setFormValue
(value)Sets both the state and submission value of internals's target element to value.
If value is null, the element won't participate in form submission.
setFormValue
(value,
state)Sets the submission value of internals's target element to value, and its state to state.
If value is null, the element won't participate in form submission.
form
Returns the form owner of internals's target element.
setValidity
(flags,
message [, anchor ])Marks internals's target element as
suffering from the constraints indicated by the flags argument, and sets the element's
validation message to message. If anchor is specified, the user agent might
use it to indicate problems with the constraints of internals's target element when the form owner is validated
interactively or reportValidity()
is
called.
setValidity
({})Marks internals's target element as satisfying its constraints.
willValidate
Returns true if internals's target element will be validated when the form is submitted; false otherwise.
validity
Returns the ValidityState
object for internals's
target element.
validationMessage
Returns the error message that would be shown to the user if internals's target element was to be checked for validity.
checkValidity()
Returns true if internals's target
element has no validity problems; false otherwise. Fires an invalid
event at the element in the latter case.
reportValidity()
Returns true if internals's target
element has no validity problems; otherwise, returns false, fires an invalid
event at the element, and (if the event isn't canceled)
reports the problem to the user.
labels
Returns a NodeList
of all the label
elements that
internals's target element is associated
with.
Each form-associated custom element has submission value. It is used to provide one or more
entries on form submission.
The initial value of submission value is null, and
submission value can be null, a string, a
File
, or a list of entries.
Each form-associated custom element has state.
It is information with which the user agent can restore a user's input for the element.
The initial value of state is null, and state can be null, a string, a File
, or a
list of entries.
The setFormValue()
method is used by
the custom element author to set the element's submission
value and state, thus communicating these to the user
agent.
When the user agent believes it is a good idea to restore a form-associated custom
element's state, for example after navigation or
restarting the user agent, they may enqueue a custom element callback reaction with
that element, callback name "formStateRestoreCallback
", and an argument
list containing the state to be restored, and "restore"
.
If the user agent has a form-filling assist feature, then when the feature is invoked,
it may enqueue a custom element callback reaction with
a form-associated custom element, callback name
"formStateRestoreCallback
", and an argument list containing the state value
determined by history of state value and some heuristics, and
"autocomplete"
.
In general, the state is information specified by a user, and the submission value is a value after canonicalization or sanitization, suitable for submission to the server. The following examples makes this concrete:
Suppose that we have a form-associated custom element which asks a
user to specify a date. The user specifies "3/15/2019", but the control wishes to
submit "2019-03-15"
to the server. "3/15/2019" would be a state of the element, and "2019-03-15"
would be
a submission value.
Suppose you develop a custom element emulating a the behavior of the existing
checkbox input
type. Its submission value would be the value of its value
content attribute, or the string "on"
. Its state would be one of "checked"
, "unchecked"
, "checked/indeterminate"
, or "unchecked/indeterminate"
.
The setFormValue(value,
state)
method steps are:
Let element be this's target element.
If element is not a form-associated custom element, then throw a
"NotSupportedError
" DOMException
.
Set target element's submission value to value if value is
not a FormData
object, or to a clone of the
entry list associated with value otherwise.
If the state argument of the function is omitted, set element's state to its submission value.
Otherwise, if state is a FormData
object, set element's
state to clone of the
entry list associated with state.
Otherwise, set element's state to state.
Each form-associated custom element has validity flags named
valueMissing
, typeMismatch
,
patternMismatch
, tooLong
,
tooShort
, rangeUnderflow
,
rangeOverflow
, stepMismatch
, and
customError
. They are false initially.
Each form-associated custom element has a validation message string. It is the empty string initially.
Each form-associated custom element has a validation anchor element. It is null initially.
The setValidity(flags,
message, anchor)
method steps are:
Let element be this's target element.
If element is not a form-associated custom element, then throw a
"NotSupportedError
" DOMException
.
If flags contains one or more true values and message is not given
or is the empty string, then throw a TypeError
.
For each entry flag → value of flags, set element's validity flag with the name flag to value.
Set element's validation message to the empty string if message is not given or all of element's validity flags are false, or to message otherwise.
If element's customError
validity flag is true, then set
element's custom validity error message to element's
validation message. Otherwise, set
element's custom validity error message to the empty string.
Set element's validation anchor to
null if anchor is not given. Otherwise, if anchor is not a
shadow-including descendant of element, then throw a
"NotFoundError
" DOMException
. Otherwise, set
element's validation anchor to
anchor.
ElementInternals/validationMessage
The validationMessage
getter steps are to return the validation message of
this's target element.
The entry construction algorithm for a form-associated custom element, given an element element and a list entry list, consists of the following steps:
If element's submission value is a list of entries, then append each item of element's submission value to entry list, and return.
In this case, user agent does not refer to the
name
content attribute value. An implementation of
form-associated custom element is responsible to decide names of
entries. They can be the
name
content attribute value, they can be strings based on
the name
content attribute value, or they can be unrelated
to the name
content attribute.
If the element does not have a name
attribute
specified, or its name
attribute's value is the empty string,
then return.
If the element's submission value is not
null, append an entry to entry list with the
name
attribute value and the
submission value.
role
[ = value ]Sets or retrieves the default ARIA role for internals's target element, which will be used unless the page author
overrides it using the role
attribute.
aria*
[ = value ]Sets or retrieves various default ARIA states or property values for
internals's target element, which will be used
unless the page author overrides them using the aria-*
attributes.
Each custom element has a native accessibility semantics map, which is a map, initially empty. See the Requirements related to ARIA and to platform accessibility APIs section for information on how this impacts platform accessibility APIs.
ElementInternals
includes the ARIAMixin
mixin. The accessors provided
by this mixin are used to manipulate the target element's
native accessibility semantics map, as follows:
The ARIAMixin
getter
steps for ElementInternals
, given internals,
idlAttribute, and contentAttribute, are:
Let map be internals's target element's native accessibility semantics map.
If map[contentAttribute] exists, then return it.
Return null.
The ARIAMixin
setter steps for ElementInternals
, given
internals, idlAttribute, contentAttribute, and
value, are:
Let map be internals's target element's native accessibility semantics map.
If value is null, then remove map[contentAttribute].
Otherwise, set map[contentAttribute] to value.