1. 4.13 自定义元素
      1. 4.13.1 简介
        1. 4.13.1.1 创建一个自主的自定义元素
        2. 4.13.1.2 创建一个与表单关联的自定义元素
        3. 4.13.1.3 创建一个具有默认可访问角色、状态和属性的自定义元素
        4. 4.13.1.4 创建一个自定义的内置元素
        5. 4.13.1.5 自主自定义元素的缺点
        6. 4.13.1.6 元素创建后的升级
        7. 4.13.1.7 公开自定义元素状态
      2. 4.13.2 自定义元素构造函数和反应的要求
      3. 4.13.3 核心概念
      4. 4.13.4 CustomElementRegistry 接口
      5. 4.13.5 升级
      6. 4.13.6 自定义元素反应
      7. 4.13.7 元素内部
        1. 4.13.7.1 ElementInternals 接口
        2. 4.13.7.2 阴影根访问
        3. 4.13.7.3 与表单关联的自定义元素
        4. 4.13.7.4 可访问性语义
        5. 4.13.7.5 自定义状态伪类

4.13 自定义元素

使用_自定义元素

所有当前引擎都支持。

Firefox63+Safari10.1+Chrome54+
Opera?Edge79+
Edge (Legacy)?Internet Explorer不支持
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

4.13.1 简介

本节是非规范性的。

自定义元素 为作者提供了一种构建自己的完整功能 DOM 元素的方法。虽然作者始终可以在他们的文档中使用非标准元素,并在之后通过脚本或类似方法添加应用程序特定的行为,但此类元素在历史上一直不符合标准,而且功能不强。通过 定义 一个自定义元素,作者可以告知解析器如何正确构造一个元素以及该类的元素应该如何对更改做出反应。

自定义元素是更大努力的一部分,旨在通过解释现有的平台功能(如 HTML 元素)来“理顺平台”,这些功能是以更低级别的作者公开扩展点(如自定义元素定义)来解释的。虽然今天自定义元素的功能和语义上都存在很多限制,阻止它们完全解释 HTML 现有元素的行为,但我们希望随着时间的推移缩小这种差距。

4.13.1.1 创建一个自主的自定义元素

本节是非规范性的。

为了说明如何创建一个 自主的自定义元素,让我们定义一个自定义元素,它封装了为国家国旗渲染一个小图标。我们的目标是能够像这样使用它

<flag-icon country="nl"></flag-icon>

为此,我们首先为自定义元素声明一个类,扩展 HTMLElement

class FlagIcon extends HTMLElement {
  constructor() {
    super();
    this._countryCode = null;
  }

  static observedAttributes = ["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)

最后,我们还可以使用 自定义元素构造函数 本身。也就是说,上面的代码等效于

const flagIcon = new FlagIcon()
flagIcon.country = "jp"
document.body.appendChild(flagIcon)
4.13.1.2 创建一个与表单关联的自定义元素

本节是非规范性的。

添加一个带有真值的静态 formAssociated 属性,会使 自主的自定义元素 成为 与表单关联的自定义元素ElementInternals 接口可以帮助您实现表单控件元素的通用函数和属性。

class MyCheckbox extends HTMLElement {
  static formAssociated = true;
  static observedAttributes = ['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.hasAttribute('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);

您可以像使用内置的表单关联元素一样使用自定义元素 my-checkbox。例如,将其放在 formlabel 中,会将 my-checkbox 元素与其关联,并且提交 form 将发送由 my-checkbox 实现提供的数据。

<form action="..." method="...">
  <label><my-checkbox name="agreed"></my-checkbox> I read the agreement.</label>
  <input type="submit">
</form>
4.13.1.3 创建一个具有默认可访问角色、状态和属性的自定义元素

本节是非规范性的。

通过使用 ElementInternals 的适当属性,您的自定义元素可以具有默认的可访问性语义。以下代码扩展了我们上一节中的与表单关联的复选框,以正确设置其默认角色和选中状态,如辅助技术所见

class MyCheckbox extends HTMLElement {
  static formAssociated = true;
  static observedAttributes = ['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.hasAttribute('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);

请注意,与内置元素一样,这些只是默认值,可以通过页面作者使用 rolearia-* 属性来覆盖。

<!-- 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">

鼓励自定义元素作者说明其可访问性语义的哪些方面是强大的本机语义,即,不应该被自定义元素的用户覆盖。在我们的示例中,my-checkbox 元素的作者会声明其 rolearia-checked 值是强大的本机语义,因此不鼓励使用诸如上面的代码。

4.13.1.4 创建一个自定义的内置元素

本节是非规范性的。

自定义的内置元素 是一种独特的 自定义元素,其定义方式略有不同,使用方式也与 自主的自定义元素 不同。它们的存在是为了允许重用 HTML 现有元素的行为,方法是使用新的自定义功能扩展这些元素。这很重要,因为 HTML 元素的许多现有行为不幸的是无法通过仅仅使用 自主的自定义元素 来复制。相反,自定义的内置元素 允许将自定义构造行为、生命周期挂钩和原型链安装到现有元素上,本质上是在现有元素的基础上“混合”这些功能。

自定义的内置元素 需要与 自主的自定义元素 不同的语法,因为用户代理和其他软件根据元素的本地名称来识别元素的语义和行为。也就是说,自定义的内置元素 在现有行为基础上构建的概念,主要依赖于扩展元素保留其原始本地名称。

在这个例子中,我们将创建一个名为 plastic-button自定义的内置元素,它像一个普通的按钮,但只要点击它就会添加花哨的动画效果。我们首先定义一个类,就像之前一样,不过这次我们扩展的是 HTMLButtonElement 而不是 HTMLElement

class PlasticButton extends HTMLButtonElement {
  constructor() {
    super();

    this.addEventListener("click", () => {
      // Draw some fancy animation effects!
    });
  }
}

在定义自定义元素时,我们还必须指定 extends 选项

customElements.define("plastic-button", PlasticButton, { extends: "button" });

通常,扩展的元素名称不能仅仅通过查看它扩展了哪个元素接口来确定,因为许多元素共享同一个接口(例如,qblockquote 都共享 HTMLQuoteElement)。

为了从解析的 HTML 源文本中构造我们的 自定义的内置元素,我们在 button 元素上使用 is 属性

<button is="plastic-button">Click Me!</button>

尝试将 自定义的内置元素 作为 自主的自定义元素 使用将不会起作用;也就是说,<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);  // will output "button"
console.assert(plasticButton2 instanceof PlasticButton);
console.assert(plasticButton2 instanceof HTMLButtonElement);

请注意,在以编程方式创建一个自定义的内置元素时,is 属性将不会出现在 DOM 中,因为它没有被显式设置。但是,它将在序列化时添加到输出

console.assert(!plasticButton.hasAttribute("is"));
console.log(plasticButton.outerHTML); // will output '<button is="plastic-button"></button>'

无论使用哪种方式创建,button 的所有特殊之处也适用于此类“塑料按钮”:它们的焦点行为、参与 表单提交 的能力、disabled 属性等等。

自定义内置元素 旨在允许扩展具有有用用户代理提供的行为或 API 的现有 HTML 元素。 因此,它们只能扩展本规范中定义的现有 HTML 元素,不能扩展旧版元素,例如 bgsoundblinkisindexkeygenmulticolnextidspacer,这些元素已被定义为使用 HTMLUnknownElement 作为其 元素接口

对这一要求的原因之一是未来兼容性:如果定义了一个扩展当前未知元素(例如 combobox)的 自定义内置元素,这将阻止本规范将来定义 combobox 元素,因为派生 自定义内置元素 的使用者将依赖其基本元素没有有趣的用户代理提供的行为。

4.13.1.5 自主自定义元素的缺点

本节是非规范性的。

正如下面所述,以及上面所提到的,简单地定义和使用一个名为 taco-button 的元素并不意味着这些元素 代表 按钮。 也就是说,像 Web 浏览器、搜索引擎或辅助技术之类的工具不会仅仅根据其定义的名称自动将生成的元素视为按钮。

为了向各种用户传达所需的按钮语义,同时仍然使用 自主自定义元素,需要使用多种技术。

考虑到这些要点,一个全功能的 taco-button,它承担了传达按钮语义的责任(包括能够被禁用)可能看起来像这样

class TacoButton extends HTMLElement {
  static observedAttributes = ["disabled"];

  constructor() {
    super();
    this._internals = this.attachInternals();
    this._internals.role = "button";

    this.addEventListener("keydown", e => {
      if (e.code === "Enter" || e.code === "Space") {
        this.dispatchEvent(new PointerEvent("click", {
          bubbles: true,
          cancelable: true
        }));
      }
    });

    this.addEventListener("click", e => {
      if (this.disabled) {
        e.preventDefault();
        e.stopImmediatePropagation();
      }
    });

    this._observer = new MutationObserver(() => {
      this._internals.ariaLabel = this.textContent;
    });
  }

  connectedCallback() {
    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(flag) {
    this.toggleAttribute("disabled", Boolean(flag));
  }

  attributeChangedCallback(name, oldValue, newValue) {
    // name will always be "disabled" due to observedAttributes
    if (this.disabled) {
      this.removeAttribute("tabindex");
      this._internals.ariaDisabled = "true";
    } else {
      this.setAttribute("tabindex", "0");
      this._internals.ariaDisabled = "false";
    }
  }
}

即使有了这个相当复杂的元素定义,该元素对于使用者来说也不是很好用:它将不断“萌发”它自己意愿的 tabindex 属性,并且它选择 tabindex="0" 可聚焦行为可能与当前平台上的 button 行为不匹配。 这是因为到目前为止,还没有办法为自定义元素指定默认的焦点行为,迫使使用 tabindex 属性来做到这一点(即使它通常用于允许使用者覆盖默认行为)。

相比之下,如上一节所示的简单 自定义内置元素 将自动继承 button 元素的语义和行为,无需手动实现这些行为。 一般来说,对于任何基于 HTML 的现有元素构建的具有非平凡行为和语义的元素,自定义内置元素 将更容易开发、维护和使用。

4.13.1.6 创建元素后升级元素

本节是非规范性的。

由于 元素定义 可以随时发生,非自定义元素可以 创建,然后在注册了适当的 定义 之后,它将成为 自定义元素。 我们将此过程称为从普通元素到自定义元素的“升级”元素。

升级 允许在创建相关元素(例如解析器创建的元素)后,注册 自定义元素定义 的场景。 它们允许对自定义元素中的内容进行渐进增强。 例如,在以下 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 元素将被升级,应用自定义元素的定义(该定义可能包括应用由字符串“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>
4.13.1.7 公开自定义元素状态

用户代理提供的内置元素具有一定的状态,这些状态会随着时间的推移而发生变化,具体取决于用户交互和其他因素,并通过 伪类 公开给 Web 作者。 例如,某些表单控件具有“无效”状态,该状态通过 :invalid 伪类 公开。

与内置元素一样,自定义元素 也可能处于各种状态,并且 自定义元素 作者希望以类似于内置元素的方式公开这些状态。

这是通过 :state() 伪类来完成的。 自定义元素作者可以使用 states 属性(ElementInternals)添加和删除此类自定义状态,这些状态然后作为 :state() 伪类的参数公开。

以下展示了如何使用 :state() 来设置自定义复选框元素的样式。 假设 LabeledCheckbox 不通过内容属性公开其“已选中”状态。

<script>
class LabeledCheckbox extends HTMLElement {
  constructor() {
    super();
    this._internals = this.attachInternals();
    this.addEventListener('click', this._onClick.bind(this));

    const shadowRoot = this.attachShadow({mode: 'closed'});
    shadowRoot.innerHTML =
      `<style>
       :host::before {
         content: '[ ]';
         white-space: pre;
         font-family: monospace;
       }
       :host(:state(checked))::before { content: '[x]' }
       </style>
       <slot>Label</slot>`;
  }

  get checked() { return this._internals.states.has('checked'); }

  set checked(flag) {
    if (flag)
      this._internals.states.add('checked');
    else
      this._internals.states.delete('checked');
  }

  _onClick(event) {
    this.checked = !this.checked;
  }
}

customElements.define('labeled-checkbox', LabeledCheckbox);
</script>

<style>
labeled-checkbox { border: dashed red; }
labeled-checkbox:state(checked) { border: solid; }
</style>

<labeled-checkbox>You need to check this</labeled-checkbox>

自定义伪类甚至可以定位阴影部分。 上面的示例扩展展示了这一点

<script>
class QuestionBox extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({mode: 'closed'});
    shadowRoot.innerHTML =
      `<div><slot>Question</slot></div>
       <labeled-checkbox part='checkbox'>Yes</labeled-checkbox>`;
  }
}
customElements.define('question-box', QuestionBox);
</script>

<style>
question-box::part(checkbox) { color: red; }
question-box::part(checkbox):state(checked) { color: green; }
</style>

<question-box>Continue?</question-box>

4.13.2 自定义元素构造函数和反应的要求

在编写 自定义元素构造函数 时,作者受以下一致性要求约束

这些要求中的几个在元素创建期间进行检查,无论是直接还是间接,如果未能遵守这些要求,将导致自定义元素无法通过解析器或DOM API实例化。即使工作是在构造函数启动的微任务中完成,这仍然适用,因为微任务检查点可以在构造后立即发生。

在编写自定义元素反应时,作者应避免操作节点树,因为这会导致意外结果。

元素的connectedCallback可以在元素断开连接之前排队,但由于回调队列仍在处理,它会导致connectedCallback针对不再连接的元素。

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

4.13.3 核心概念

自定义元素自定义的元素。非正式地说,这意味着它的构造函数和原型由作者定义,而不是由用户代理定义。此作者提供的构造函数称为自定义元素构造函数

可以定义两种不同类型的自定义元素

Global_attributes/is

Firefox63+SafariNoChrome67+
Opera?Edge79+
Edge (Legacy)?Internet Explorer不支持
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
  1. 自治自定义元素,它是在没有extends选项的情况下定义的。这些类型的自定义元素的本地名称与其定义名称相同。

  2. 自定义内置元素,它是在带有extends选项的情况下定义的。这些类型的自定义元素的本地名称等于其extends选项中传递的值,并且它们的定义名称用作is属性的值,因此必须是有效的自定义元素名称

自定义元素创建之后,更改is属性的值不会改变元素的行为,因为它作为元素的is保存。

自治自定义元素具有以下元素定义

类别:
流内容.
短语内容.
可感知内容.
对于与表单关联的自定义元素列出可标记可提交可重置与表单关联的元素
可以使用此元素的上下文:
在预期短语内容的地方。
内容模型:
透明的.
内容属性:
全局属性,除了is属性
form,用于与表单关联的自定义元素 - 将元素与form元素关联
disabled,用于与表单关联的自定义元素 - 表单控件是否被禁用
readonly,用于与表单关联的自定义元素 - 影响willValidate,以及自定义元素作者添加的任何行为
name,用于与表单关联的自定义元素 - 用于表单提交form.elements API的元素名称
任何其他没有命名空间的属性(参见文本)。
可访问性注意事项:
对于与表单关联的自定义元素针对作者针对实现者
否则:针对作者针对实现者
DOM 接口:
由元素的作者提供(继承自HTMLElement

自治自定义元素没有任何特殊含义:它表示其子级。一个自定义内置元素继承了它扩展的元素的语义。

任何对元素功能相关的无命名空间属性,如元素作者所确定,都可以在自治自定义元素上指定,只要属性名称是XML 兼容并且不包含ASCII 大写字母。例外是is属性,它不能在自治自定义元素上指定(如果指定,它将不起作用)。

自定义内置元素遵循基于其扩展的元素的属性的正常要求。要添加基于属性的自定义行为,请使用data-*属性。


如果元素与自定义元素定义关联,其中与表单关联字段设置为 true,则自治自定义元素称为与表单关联的自定义元素

name属性表示与表单关联的自定义元素的名称。disabled属性用于使与表单关联的自定义元素不可交互,并防止其提交值被提交。form属性用于明确地将与表单关联的自定义元素与其表单所有者关联。

与表单关联的自定义元素readonly属性指定元素是被禁止进行约束验证的。用户代理不会为该属性提供任何其他行为,但自定义元素作者应尽可能使用其存在使他们的控件以某种适当的方式不可编辑,类似于对内置表单控件的readonly属性的行为。

约束验证:如果在与表单关联的自定义元素上指定了readonly属性,则该元素是被禁止进行约束验证的。

对于与表单关联的自定义元素重置算法将一个自定义元素回调反应排队,其中包含元素、回调名称“formResetCallback”以及一个空参数列表。


有效的自定义元素名称是满足以下所有要求的字符序列name

这些要求确保了有效的自定义元素名称的几个目标

除了这些限制外,还允许使用各种名称,以最大程度地灵活地用于诸如 <math-α><emotion-😍> 之类的用例。

一个 自定义元素定义 描述了一个 自定义元素,它包括

一个 名称
一个 有效的自定义元素名称
一个 本地名称
一个本地名称
一个 构造函数
一个 Web IDL CustomElementConstructor 回调函数类型值,包装了 自定义元素构造函数
一个 观察到的属性 列表
一个 sequence<DOMString>
一个 生命周期回调 集合
一个映射,其键是字符串 "connectedCallback"、"disconnectedCallback"、"adoptedCallback"、"attributeChangedCallback"、"formAssociatedCallback"、"formDisabledCallback"、"formResetCallback" 和 "formStateRestoreCallback"。相应的取值要么是 Web IDL Function 回调函数类型值,要么是 null。默认情况下,每个条目的值都为 null。
一个 构造堆栈
一个最初为空的列表,由 升级元素 算法和 HTML 元素构造函数 操作。列表中的每个条目将是元素或 已构造 标记
一个 与表单关联 布尔值
如果为 true,则用户代理会将与该 自定义元素定义 关联的元素视为 与表单关联的自定义元素
一个 禁用内部 布尔值
控制 attachInternals()
一个 禁用影子 布尔值
控制 attachShadow()

为了 查找自定义元素定义,给定 documentnamespacelocalNameis,执行以下步骤。它们将返回 自定义元素定义 或 null

  1. 如果 namespace 不是 HTML 命名空间,则返回 null。

  2. 如果 document浏览上下文 为 null,则返回 null。

  3. registrydocument相关全局对象CustomElementRegistry 对象。

  4. 如果 registry 中存在 自定义元素定义,其 名称本地名称 都等于 localName,则返回该 自定义元素定义

  5. 如果 registry 中存在 自定义元素定义,其 名称 等于 is本地名称 等于 localName,则返回该 自定义元素定义

  6. 返回 null。

4.13.4 CustomElementRegistry 接口

CustomElementRegistry

所有当前引擎都支持。

Firefox63+Safari10.1+Chrome54+
Opera?Edge79+
Edge (Legacy)?Internet Explorer不支持
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

每个 Window 对象都与 CustomElementRegistry 对象的唯一实例关联,该实例在创建 Window 对象时分配。

自定义元素注册表与 Window 对象关联,而不是与 Document 对象关联,因为每个 自定义元素构造函数 都继承自 HTMLElement 接口,并且每个 Window 对象只有一个 HTMLElement 接口。

Window/customElements

所有当前引擎都支持。

Firefox63+Safari10.1+Chrome54+
Opera?Edge79+
Edge (Legacy)?Internet Explorer不支持
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

Window 接口的 customElements 属性必须返回该 Window 对象的 CustomElementRegistry 对象。

[Exposed=Window]
interface CustomElementRegistry {
  [CEReactions] undefined define(DOMString name, CustomElementConstructor constructor, optional ElementDefinitionOptions options = {});
  (CustomElementConstructor or undefined) get(DOMString name);
  DOMString? getName(CustomElementConstructor constructor);
  Promise<CustomElementConstructor> whenDefined(DOMString name);
  [CEReactions] undefined upgrade(Node root);
};

callback CustomElementConstructor = HTMLElement ();

dictionary ElementDefinitionOptions {
  DOMString extends;
};

每个 CustomElementRegistry 都有一个 自定义元素定义 集,最初为空。通常,本规范中的算法通过 名称本地名称构造函数 中的任何一个在注册表中查找元素。

每个 CustomElementRegistry 也都有一个 元素定义正在运行 标志,用于防止 元素定义 的重复调用。它最初未设置。

每个 CustomElementRegistry 也都有一个 已定义承诺映射,它将 有效的自定义元素名称 映射到承诺。它用于实现 whenDefined() 方法。

window.customElements.define(name, constructor)

CustomElementRegistry/define

所有当前引擎都支持。

Firefox63+Safari10.1+Chrome54+
Opera?Edge79+
Edge (Legacy)?Internet Explorer不支持
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
定义一个新的 自定义元素,将给定名称映射到给定构造函数,作为一个 自主自定义元素
window.customElements.define(name, constructor, { extends: baseLocalName })
定义一个新的 自定义元素,将给定名称映射到给定构造函数,作为一个 定制的内置元素,用于由提供的 baseLocalName 标识的 元素类型。当尝试扩展 自定义元素 或未知元素时,将抛出 "NotSupportedError" DOMException
window.customElements.get(name)

CustomElementRegistry/get

所有当前引擎都支持。

Firefox63+Safari10.1+Chrome54+
Opera?Edge79+
Edge (Legacy)?Internet Explorer不支持
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
检索为给定 名称 定义的 自定义元素构造函数。如果不存在具有给定 名称自定义元素定义,则返回 undefined。
window.customElements.getName(constructor)
检索为给定 构造函数 定义的 自定义元素 的给定名称。如果不存在具有给定 构造函数自定义元素定义,则返回 null。
window.customElements.whenDefined(name)

CustomElementRegistry/whenDefined

所有当前引擎都支持。

Firefox63+Safari10.1+Chrome54+
Opera?Edge79+
Edge (Legacy)?Internet Explorer不支持
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
返回一个承诺,该承诺将在使用给定名称定义 自定义元素 时用该 自定义元素 的构造函数完成。(如果已经定义了这样的 自定义元素,则返回的承诺将立即完成。)如果未提供 有效的自定义元素名称,则返回一个承诺,该承诺将用 "SyntaxError" DOMException 拒绝。
window.customElements.upgrade(root)

CustomElementRegistry/upgrade

所有当前引擎都支持。

Firefox63+Safari12.1+Chrome68+
Opera?Edge79+
Edge (Legacy)?Internet Explorer不支持
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
尝试升级 root 的所有 包含影子的包含后代 元素,即使它们未 连接

元素定义 是将一个 自定义元素定义 添加到 CustomElementRegistry 的过程。这是通过 define() 方法实现的。当调用时,define(name, constructor, options) 方法必须执行以下步骤

  1. 如果 IsConstructor(constructor) 为 false,则抛出 TypeError

  2. 如果 name 不是 有效的自定义元素名称,则抛出 "SyntaxError" DOMException

  3. 如果此 CustomElementRegistry 包含具有 名称 name 的条目,则抛出 "NotSupportedError" DOMException

  4. 如果此 CustomElementRegistry 包含具有 构造函数 constructor 的条目,则抛出 "NotSupportedError" DOMException

  5. localNamename

  6. extendsoptionsextends 成员的值,如果不存在此成员则为 null。

  7. 如果 extends 不为 null,则

    1. 如果 extends有效的自定义元素名称,则抛出 "NotSupportedError" DOMException

    2. 如果 extendsHTML 命名空间元素接口HTMLUnknownElement(例如,如果 extends 在此规范中未指示元素定义),则抛出 "NotSupportedError" DOMException

    3. localName 设置为 extends

  8. 如果此 CustomElementRegistry元素定义正在运行 标志已设置,则抛出 "NotSupportedError" DOMException

  9. 设置此 CustomElementRegistry元素定义正在运行 标志。

  10. formAssociated 为 false。

  11. disableInternals 为 false。

  12. disableShadow 为 false。

  13. observedAttributes 为空的 sequence<DOMString>

  14. 运行以下子步骤,同时捕获任何异常

    1. prototype 为 ? Get(constructor, "prototype").

    2. 如果 Type(prototype) 不是 Object,则抛出 TypeError 异常。

    3. lifecycleCallbacks 为一个映射,其键为 "connectedCallback"、"disconnectedCallback"、"adoptedCallback" 和 "attributeChangedCallback",每个键都属于一个其值为 null 的条目。

    4. 对于 lifecycleCallbacks 中的每个键 callbackName,按前一步中列出的顺序

      1. callbackValue 为 ? Get(prototype, callbackName).

      2. 如果 callbackValue 不为 undefined,则将 lifecycleCallbacks 中具有键 callbackName 的条目的值设置为将 callbackValue 转换为 Web IDL Function 回调类型的结果。重新抛出转换过程中的任何异常。

    5. 如果 lifecycleCallbacks 中具有键 "attributeChangedCallback" 的条目的值为非 null,则

      1. observedAttributesIterable 为 ? Get(constructor, "observedAttributes").

      2. 如果 observedAttributesIterable 不为 undefined,则将 observedAttributes 设置为将 observedAttributesIterable 转换为 sequence<DOMString> 的结果。重新抛出转换过程中的任何异常。

    6. disabledFeatures 为空的 sequence<DOMString>

    7. disabledFeaturesIterable 为 ? Get(constructor, "disabledFeatures").

    8. 如果 disabledFeaturesIterable 不为 undefined,则将 disabledFeatures 设置为将 disabledFeaturesIterable 转换为 sequence<DOMString> 的结果。重新抛出转换过程中的任何异常。

    9. 如果 disabledFeatures 包含 "internals",则将 disableInternals 设置为 true。

    10. 如果 disabledFeatures 包含 "shadow",则将 disableShadow 设置为 true。

    11. formAssociatedValue 为 ? Get( constructor, "formAssociated").

    12. formAssociated 设置为将 formAssociatedValue 转换为 boolean 的结果。重新抛出转换过程中的任何异常。

    13. 如果 formAssociated 为 true,对于 "formAssociatedCallback"、"formResetCallback"、"formDisabledCallback" 和 "formStateRestoreCallback" 的每个 callbackName

      1. callbackValue 为 ? Get(prototype, callbackName).

      2. 如果 callbackValue 不为 undefined,则将 lifecycleCallbacks 中具有键 callbackName 的条目的值设置为将 callbackValue 转换为 Web IDL Function 回调类型的结果。重新抛出转换过程中的任何异常。

    然后,执行以下子步骤,无论上面的步骤是否抛出异常

    1. 取消设置此 CustomElementRegistry元素定义正在运行 标志。

    最后,如果第一组子步骤抛出异常,则重新抛出该异常(从而终止此算法)。否则,继续执行。

  15. definition 为一个新的 自定义元素定义,具有 名称 name本地名称 localName构造函数 constructor观察属性 observedAttributes生命周期回调 lifecycleCallbacks与表单关联 formAssociated禁用内部 disableInternals禁用阴影 disableShadow

  16. definition 添加到此 CustomElementRegistry

  17. document 为此 CustomElementRegistry相关全局对象关联 Document

  18. 升级候选者 为所有在 包含阴影的树的顺序 中,是 document包含阴影的后代,其命名空间为 HTML 命名空间 且其本地名称为 localName 的元素。此外,如果 extends 不为 null,则仅包括 is 等于 name 的元素。

  19. 对于 升级候选者 中的每个元素 element将一个自定义元素升级反应排队,其中给定 elementdefinition

  20. 如果此 CustomElementRegistry已定义承诺映射 包含具有键 name 的条目

    1. promise 为该条目的值。

    2. constructor 解决 promise

    3. 从此 CustomElementRegistry已定义承诺映射 中删除具有键 name 的条目。

当调用时,get(name) 方法必须执行以下步骤

  1. 如果此 CustomElementRegistry 包含具有 名称 name 的条目,则返回该条目的 构造函数

  2. 否则,返回 undefined。

CustomElementRegistry/getName

Firefox116+Safari🔰 预览版+Chrome117+
Opera?Edge117+
Edge (Legacy)?Internet Explorer不支持
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

getName(constructor) 方法的步骤如下

  1. 如果此 CustomElementRegistry 包含一个条目,其 构造函数constructor,则返回该条目的 名称

  2. 返回 null。

当调用时,whenDefined(name) 方法必须执行以下步骤

  1. 如果 name 不是一个 有效的自定义元素名称,则返回 一个被以下内容拒绝的 Promise:一个 "SyntaxError" DOMException

  2. 如果此 CustomElementRegistry 包含一个条目,其 名称name,则返回 一个被以下内容决议的 Promise:该条目的 构造函数

  3. map 为此 CustomElementRegistrywhen-defined Promise 映射

  4. 如果 map 不包含一个键为 name 的条目,则在 map 中创建一个键为 name 的条目,其值为一个新的 Promise。

  5. promisemap 中键为 name 的条目的值。

  6. 返回 promise

whenDefined() 方法可用于避免在所有相关的 自定义元素 定义 之前执行操作。在此示例中,我们将它与 :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;
  });

当调用时,upgrade(root) 方法必须执行以下步骤

  1. candidates 为一个 列表,其中包含 root 的所有 包含阴影的包含后代 元素,按照 包含阴影的树序

  2. 对于 candidates 中的每个 candidate尝试升级 candidate

upgrade() 方法允许根据需要升级元素。通常元素会在它们变得 连接 时自动升级,但如果您需要在准备连接元素之前升级,可以使用此方法。

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!

4.13.5 升级

升级一个元素,将一个 自定义元素定义 definition 和一个元素 element 作为输入,执行以下步骤

  1. 如果 element自定义元素状态 不是 "undefined" 或 "uncustomized",则返回。

    由于此算法的递归调用,这种情况可能发生在以下示例中

    <!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" 第二次调用时,此步骤将尽早退出算法。

  2. element自定义元素定义 设置为 definition

  3. element自定义元素状态 设置为 "failed"。

    它将在 升级成功后 设置为 "custom"。现在,我们将其设置为 "failed",以便任何递归调用都会命中 上述的早期退出步骤

  4. 对于 element属性列表 中的每个 attribute,按顺序 将一个自定义元素回调反应入队,其 element,回调名称为 "attributeChangedCallback",并且参数列表包含 attribute 的本地名称、null、attribute 的值以及 attribute 的命名空间。

  5. 如果 element连接,则 将一个自定义元素回调反应入队,其 element,回调名称为 "connectedCallback",并且参数列表为空。

  6. element 添加到 definition构造栈 的末尾。

  7. Cdefinition构造函数

  8. 运行以下子步骤,同时捕获任何异常

    1. 如果 definition禁用阴影 为 true 并且 element影子根 不为 null,则抛出一个 "NotSupportedError" DOMException

      这是必需的,因为 attachShadow() 在调用时不使用 查找自定义元素定义,而 attachInternals() 会使用。

    2. element自定义元素状态 设置为 "precustomized"。

    3. constructResult 为使用无参数 构造 C 的结果。

      如果 C 不符合规范地 使用带有 [CEReactions] 扩展属性的 API,那么在此算法开始时入队的反应将在此步骤期间执行,在 C 完成并控制权返回到此算法之前。否则,它们将在 C 和升级过程的其余部分完成后执行。

    4. 如果 SameValue(constructResult, element) 为 false,则抛出一个 TypeError

      如果 C 在调用 super() 之前构造了同一个自定义元素的另一个实例,或者如果 C 使用 JavaScript 的 return 覆盖功能从构造函数中返回一个任意的 HTMLElement 对象,则可能会发生这种情况。

    然后,执行以下子步骤,无论上面的步骤是否抛出异常

    1. definition构造栈 的末尾移除最后一个条目。

      假设 C 调用 super()(如果它 符合规范,则会这样做),并且调用成功,这将是我们在此算法开始时压入的 element 所替换的 已构造 标记。(HTML 元素构造函数 执行此替换。)

      如果 C 没有调用 super()(即它 不符合规范),或者 HTML 元素构造函数 中的任何步骤都抛出异常,则此条目仍然为 element

    最后,如果上述步骤抛出异常,则

    1. element自定义元素定义 设置为 null。

    2. 清空 element自定义元素反应队列

    3. 重新抛出异常(从而终止此算法)。

    如果上述步骤抛出异常,则 element自定义元素状态 将保持 "failed" 或 "precustomized"。

  9. 如果 element 是一个 与表单关联的自定义元素,则

    1. 重置 element 的表单所有者。如果 element 与一个 form 元素关联,则 将一个自定义元素回调反应入队,其 element,回调名称为 "formAssociatedCallback",并且参数为 « 关联的 form »。

    2. 如果 element 已禁用,则 将一个自定义元素回调反应入队,其 element,回调名称为 "formDisabledCallback",并且参数为 « true »。

  10. element自定义元素状态 设置为 "custom"。

尝试升级一个元素,将元素 element 作为输入,执行以下步骤

  1. definition查找自定义元素定义 的结果,该定义使用 element节点文档element 的命名空间、element 的本地名称以及 elementis

  2. 如果 definition 不为 null,则 将一个自定义元素升级反应入队,其 elementdefinition

4.13.6 自定义元素反应

一个 自定义元素 具有通过运行作者代码来响应某些事件的能力

我们把这些反应统称为自定义元素反应

调用自定义元素反应的方式需要特别小心,以避免在执行复杂操作的过程中运行作者代码。实际上,它们被延迟到 "在返回用户脚本之前"。这意味着,对于大多数情况,它们看起来是同步执行的,但在复杂复合操作(如克隆范围操作)的情况下,它们将被延迟到所有相关的用户代理处理步骤完成后,然后作为一个批处理一起运行。

此外,这些反应的精确顺序通过一个稍微复杂的队列堆栈系统进行管理,下面将对此进行描述。这个系统背后的意图是保证自定义元素反应总是按照它们触发操作的顺序调用,至少在单个自定义元素的本地上下文中是如此。(因为自定义元素反应代码可以执行它自己的修改,所以不可能跨多个元素给出全局顺序保证。)


每个同源窗口代理都有一个自定义元素反应堆栈,该堆栈最初为空。一个同源窗口代理当前元素队列是其自定义元素反应堆栈顶部的元素队列。堆栈中的每一项都是一个元素队列,它最初也是空的。一个元素队列中的每一项都是一个元素。(这些元素不一定是自定义的,因为这个队列也用于升级。)

每个自定义元素反应堆栈都有一个相关的备份元素队列,它最初是一个空的元素队列。在影响 DOM 但不通过带有[CEReactions]装饰的 API 或通过解析器的创建元素算法进行的操作期间,元素会被推送到备份元素队列中。一个例子是用户发起的编辑操作,它修改了可编辑元素的后代或属性。为了防止在处理备份元素队列时出现重入,每个自定义元素反应堆栈还有一个处理备份元素队列标志,最初未设置。

所有元素都有一个相关的自定义元素反应队列,最初为空。自定义元素反应队列中的每一项都是以下两种类型之一

所有这些都总结在下面的示意图中

A custom element reactions stack consists of a stack of element queues. Zooming in on a particular queue, we see that it contains a number of elements (in our example, <x-a>, then <x-b>, then <x-c>). Any particular element in the queue then has a custom element reaction queue. Zooming in on the custom element reaction queue, we see that it contains a variety of queued-up reactions (in our example, upgrade, then attribute changed, then another attribute changed, then connected).

将元素排队到相应的元素队列,给定一个元素element,执行以下步骤

  1. reactionsStack成为element相关代理自定义元素反应堆栈

  2. 如果reactionsStack为空,则

    1. element添加到reactionsStack备份元素队列中。

    2. 如果reactionsStack处理备份元素队列标志已设置,则返回。

    3. 设置reactionsStack处理备份元素队列标志。

    4. 排队一个微任务来执行以下步骤

      1. reactionsStack备份元素队列调用自定义元素反应

      2. 取消设置reactionsStack处理备份元素队列标志。

  3. 否则,将element添加到element相关代理当前元素队列中。

排队一个自定义元素回调反应,给定一个自定义元素element、一个回调名称callbackName和一个参数列表args,执行以下步骤

  1. definition成为element自定义元素定义

  2. callback成为definition生命周期回调中键为callbackName的条目的值。

  3. 如果callback为 null,则返回。

  4. 如果callbackName为 "attributeChangedCallback",则

    1. attributeName成为args的第一个元素。

    2. 如果definition观察属性不包含attributeName,则返回。

  5. 将一个新的回调反应添加到element自定义元素反应队列中,回调函数为callback,参数为args

  6. 给定element将元素排队到相应的元素队列

排队一个自定义元素升级反应,给定一个元素element和一个自定义元素定义definition,执行以下步骤

  1. 将一个新的升级反应添加到element自定义元素反应队列中,自定义元素定义为definition

  2. 给定element将元素排队到相应的元素队列

要在一个元素队列queue调用自定义元素反应,执行以下步骤

  1. 只要queue为空

    1. element成为从queue出队的结果。

    2. reactions成为element自定义元素反应队列

    3. 重复执行,直到reactions为空

      1. 删除reactions的第一个元素,并让reaction成为该元素。根据reaction的类型进行切换

        升级反应

        使用reaction自定义元素定义升级element

        如果这抛出了异常,则捕获它,并为reaction自定义元素定义构造函数对应的 JavaScript 对象的关联领域全局对象报告该异常。

        回调反应

        调用 reaction 的回调函数,并使用 reaction 的参数和 "report",并将 回调 this 值 设置为 element


为了确保 自定义元素反应 被适当地触发,我们引入了 [CEReactions] IDL 扩展属性。它表明相关算法需要补充额外的步骤,以适当地跟踪和调用 自定义元素反应

[CEReactions] 扩展属性不能接受任何参数,并且不能出现在操作、属性、setter 或 deleter 以外的任何地方。此外,它不能出现在只读属性上。

使用 [CEReactions] 扩展属性注释的操作、属性、setter 或 deleter 必须运行以下步骤,而不是其描述中指定的步骤。

  1. 一个新的 元素队列 推入到此对象的 相关代理自定义元素反应栈 中。

  2. 运行此构造的最初指定的步骤,捕获任何异常。如果步骤返回值,则让 value 为返回值。如果它们抛出异常,则让 exception 为抛出的异常。

  3. queue 为从此对象的 相关代理自定义元素反应栈弹出 的结果。

  4. 调用 queue 中的自定义元素反应。

  5. 如果原始步骤抛出异常 exception,则重新抛出 exception

  6. 如果原始步骤返回一个值 value,则返回 value

此扩展属性背后的意图有些微妙。实现其目标的一种方法是说,平台上的每个操作、属性、setter 和 deleter 都必须插入这些步骤,并允许实现者优化掉不必要的案例(在其中不可能出现导致 自定义元素反应 发生的 DOM 突变)。

然而,在实践中,这种不精确性可能导致 自定义元素反应 的非互操作实现,因为某些实现可能会忘记在某些情况下调用这些步骤。相反,我们选择明确地注释所有相关的 IDL 构造,作为确保互操作行为和帮助实现轻松找出需要这些步骤的所有情况的一种方式。

用户代理引入的任何非标准 API,这些 API 可能以可能导致 将自定义元素回调反应入队将自定义元素升级反应入队 的方式修改 DOM(例如,通过修改任何属性或子元素),也必须使用 [CEReactions] 属性进行装饰。

截至撰写本文时,已知以下非标准或尚未标准化的 API 属于此类别。

4.13.7 元素内部

某些功能旨在让自定义元素作者可以使用,而不是让自定义元素使用者可以使用。这些功能由 element.attachInternals() 方法提供,该方法返回一个 ElementInternals 实例。 ElementInternals 的属性和方法允许控制用户代理为所有元素提供的内部功能。

element.attachInternals()

返回一个针对 自定义元素 elementElementInternals 对象。如果 element 不是 自定义元素,或者如果 "internals" 功能在元素定义中被禁用,或者如果它在同一个元素上被调用两次,则抛出异常。

每个 HTMLElement 都有一个 已附加的内部(null 或一个 ElementInternals 对象),最初为 null。

HTMLElement/attachInternals

所有当前引擎都支持。

Firefox93+Safari16.4+Chrome77+
Opera?Edge79+
Edge (Legacy)?Internet Explorer不支持
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

attachInternals() 方法的步骤如下:

  1. 如果 thisis 不是 null,则抛出一个 "NotSupportedError" DOMException

  2. definition 为根据 this节点文档、其命名空间、其本地名称以及 null 作为 is 查找自定义元素定义 的结果。

  3. 如果 definition 为 null,则抛出一个 "NotSupportedError" DOMException

  4. 如果 definition禁用内部 为 true,则抛出一个 "NotSupportedError" DOMException

  5. 如果 this已附加的内部 不是 null,则抛出一个 "NotSupportedError" DOMException

  6. 如果 this自定义元素状态 不是 "precustomized" 或 "custom",则抛出一个 "NotSupportedError" DOMException

  7. this已附加的内部 设置为一个新的 ElementInternals 实例,其 目标元素this

  8. 返回 this已附加的内部

4.13.7.1 ElementInternals 接口

ElementInternals

所有当前引擎都支持。

Firefox93+Safari16.4+Chrome77+
Opera?Edge79+
Edge (Legacy)?Internet Explorer不支持
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

ElementInternals 接口的 IDL 如下所示,各种操作和属性在以下部分定义

[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;

  // Custom state pseudo-class
  [SameObject] readonly attribute CustomStateSet states;
};

// 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;
};

每个 ElementInternals 都有一个 目标元素,它是一个 自定义元素

4.13.7.2 阴影根访问
internals.shadowRoot

返回 internals目标元素ShadowRoot,如果 目标元素 是一个 阴影宿主,否则返回 null。

ElementInternals/shadowRoot

所有当前引擎都支持。

Firefox93+Safari16.4+Chrome88+
Opera?Edge88+
Edge (Legacy)?Internet Explorer不支持
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

shadowRoot getter 的步骤如下:

  1. targetthis目标元素

  2. 如果 target 不是一个 阴影宿主,则返回 null。

  3. shadowtarget阴影根

  4. 如果 shadow对元素内部可用 为 false,则返回 null。

  5. 返回 shadow

4.13.7.3 与表单关联的自定义元素
internals.setFormValue(value)

internals目标元素状态提交值 同时设置为 value

如果 value 为 null,则该元素将不参与表单提交。

internals.setFormValue(value, state)

internals目标元素提交值 设置为 value,并将它的 状态 设置为 state

如果 value 为 null,则该元素将不参与表单提交。

internals.form

返回 internals目标元素表单所有者

internals.setValidity(flags, message [, anchor ])

internals目标元素 标记为存在 flags 参数指示的约束问题,并将元素的验证消息设置为 message。如果指定了 anchor,用户代理可能会在 表单所有者 以交互方式验证或调用 reportValidity() 时,使用它来指示 internals目标元素 约束方面的问题。

internals.setValidity({})

internals目标元素 标记为 满足其约束

internals.willValidate

如果 internals目标元素 在提交表单时将被验证,则返回 true;否则返回 false。

internals.validity

返回 internals目标元素ValidityState 对象。

internals.validationMessage

返回如果 internals目标元素 要进行有效性检查,将向用户显示的错误消息。

valid = internals.checkValidity()

如果 internals目标元素 没有有效性问题,则返回 true;否则返回 false。在后一种情况下,在元素上触发 invalid 事件。

valid = internals.reportValidity()

如果 internals目标元素 没有有效性问题,则返回 true;否则,返回 false,在元素上触发 invalid 事件,并且(如果事件没有被取消)将问题报告给用户。

internals.labels

返回一个 NodeList,其中包含 internals目标元素 关联的所有 label 元素。

每个 与表单关联的自定义元素 都有一个 提交值。它用于在表单提交时提供一个或多个 条目提交值 的初始值为 null,提交值 可以为 null、字符串、Filelist of 条目

每个 与表单关联的自定义元素 都有 状态。它包含用户代理可以用来恢复用户输入的信息。状态 的初始值为 null,状态 可以为 null、字符串、Filelist of 条目

自定义元素作者使用 setFormValue() 方法来设置元素的 提交值状态,从而将这些信息传达给用户代理。

当用户代理认为恢复 与表单关联的自定义元素状态 是一个好主意时(例如,导航后 或重新启动用户代理),他们可能会将自定义元素回调反应排队,其中包含该元素,回调名称“formStateRestoreCallback”,包含要恢复的状态的 arguments 列表,以及“restore”。

如果用户代理具有表单填充辅助功能,那么当该功能被调用时,它可能会将自定义元素回调反应排队,其中包含一个 与表单关联的自定义元素,回调名称“formStateRestoreCallback”,包含由状态值历史记录和一些启发式方法确定的状态值的 arguments 列表,以及“autocomplete”。

通常,状态 是用户指定的信息,提交值 是规范化或清理后的值,适合提交给服务器。以下示例具体说明了这一点。

假设我们有一个 与表单关联的自定义元素,它要求用户指定一个日期。用户指定了 "3/15/2019",但该控件希望将 "2019-03-15" 提交给服务器。"3/15/2019" 将是元素的 状态"2019-03-15" 将是 提交值

假设您开发了一个自定义元素,它模拟了现有 checkbox input 类型的行为。它的 提交值 将是其 value 内容属性的值,或者字符串 "on"。它的 状态 将是 "checked""unchecked""checked/indeterminate""unchecked/indeterminate" 之一。

ElementInternals/setFormValue

所有当前引擎都支持。

Firefox98+Safari16.4+Chrome77+
Opera?Edge79+
Edge (Legacy)?Internet Explorer不支持
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

setFormValue(value, state) 方法的步骤是

  1. elementthis目标元素

  2. 如果 element 不是 与表单关联的自定义元素,则抛出一个 "NotSupportedError" DOMException

  3. 如果 value 不是 FormData 对象,则将 目标元素提交值 设置为 value,否则将 目标元素提交值 设置为 value克隆条目列表

  4. 如果该函数的 state 参数被省略,则将 element状态 设置为其 提交值

  5. 否则,如果 stateFormData 对象,则将 element状态 设置为 state克隆条目列表

  6. 否则,将 element状态 设置为 state


每个 与表单关联的自定义元素 都有名为 valueMissingtypeMismatchpatternMismatchtooLongtooShortrangeUnderflowrangeOverflowstepMismatchcustomError 的有效性标志。它们最初为 false。

每个 与表单关联的自定义元素 都有一个 验证消息 字符串。它最初为空字符串。

每个 与表单关联的自定义元素 都有一个 验证锚点 元素。它最初为 null。

ElementInternals/setValidity

所有当前引擎都支持。

Firefox98+Safari16.4+Chrome77+
Opera?Edge79+
Edge (Legacy)?Internet Explorer不支持
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

setValidity(flags, message, anchor) 方法的步骤是

  1. elementthis目标元素

  2. 如果 element 不是 与表单关联的自定义元素,则抛出一个 "NotSupportedError" DOMException

  3. 如果 flags 包含一个或多个 true 值,并且 message 未给出或为空字符串,则抛出一个 TypeError

  4. 对于 flags 中的每个条目 flagvalue,将名称为 flagelement 的有效性标志设置为 value

  5. 如果 message 未给出或 element 的所有有效性标志都为 false,则将 element验证消息 设置为空字符串,否则将其设置为 message

  6. 如果 elementcustomError 有效性标志为 true,则将 element自定义有效性错误消息 设置为 element验证消息。否则,将 element自定义有效性错误消息 设置为空字符串。

  7. 如果 anchor 未给出,则将 element验证锚点 设置为 null。否则,如果 anchor 不是 element影子包含后代,则抛出一个 "NotFoundError" DOMException。否则,将 element验证锚点 设置为 anchor

ElementInternals/validationMessage

所有当前引擎都支持。

Firefox98+Safari16.4+Chrome77+
Opera?Edge79+
Edge (Legacy)?Internet Explorer不支持
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

以下步骤描述了 validationMessage 获取器。

  1. elementthis目标元素

  2. 如果 element 不是 与表单关联的自定义元素,则抛出一个 "NotSupportedError" DOMException

  3. 返回 element验证消息

对于 与表单关联的自定义元素,给定元素 element条目列表 entry list条目构造算法 包含以下步骤:

  1. 如果 element提交值 是一个 列表,其中包含 条目,则将 element提交值 中的每个项目 追加entry list 中,并返回。

    在这种情况下,用户代理不引用 name 内容属性值。 与表单关联的自定义元素 的实现负责决定 条目 的名称。它们可以是 name 内容属性值,它们可以是基于 name 内容属性值的字符串,或者它们可以与 name 内容属性无关。

  2. 如果元素没有指定 name 属性,或者其 name 属性的值为空字符串,则返回。

  3. 如果元素的 提交值 不是空值,则 创建一个条目,其 name 属性值为 提交值,并 追加entry list 中。

4.13.7.4 可访问性语义
internals.role [ = value ]

设置或获取 internals目标元素 的默认 ARIA 角色,除非页面作者使用 role 属性覆盖它,否则将使用此角色。

internals.aria* [ = value ]

设置或获取 internals目标元素 的各种默认 ARIA 状态或属性值,除非页面作者使用 aria-* 属性覆盖它们,否则将使用这些值。

每个 自定义元素 都有一个 内部内容属性映射,这是一个 映射,最初为空。有关这如何影响平台可访问性 API 的信息,请参阅 与 ARIA 和平台可访问性 API 相关的要求 部分。

4.13.7.5 自定义状态伪类
internals.states.add(value)

将字符串 value 添加到元素的 状态集 中,以便将其作为伪类公开。

internals.states.has(value)

如果 value 在元素的 状态集 中,则返回 true,否则返回 false。

internals.states.delete(value)

如果元素的 状态集 包含 value,则将其删除并返回 true。否则,返回 false。

internals.states.clear()

从元素的 状态集 中删除所有值。

for (const stateName of internals.states)
for (const stateName of internals.states.entries())
for (const stateName of internals.states.keys())
for (const stateName of internals.states.values())

遍历元素的 状态集 中的所有值。

internals.states.forEach(callback)

通过对元素的 状态集 中的每个值调用 callback 一次,来遍历所有值。

internals.states.size

返回元素的 状态集 中的值的数量。

每个 自定义元素 都有一个 状态集,它是一个 CustomStateSet,最初为空。

[Exposed=Window]
interface CustomStateSet {
  setlike<DOMString>;
};

以下步骤描述了 states 获取器:返回 this目标元素状态集

可以通过字符串值的存在/不存在来公开 状态集 中表示的布尔状态。如果作者想要公开一个可以有三个值的 state,则可以将其转换为三个互斥的布尔状态。例如,一个名为 readyState 的 state,具有 "loading""interactive""complete" 值,可以映射到三个互斥的布尔状态:"loading""interactive""complete"

// Change the readyState from anything to "complete".
this._readyState = "complete";
this._internals.states.delete("loading");
this._internals.states.delete("interactive");
this._internals.states.add("complete");