Skip to content

Every built-in element is :defined - #193

Open
jdalton wants to merge 1 commit into
dperini:masterfrom
jdalton:fix/defined-built-ins
Open

Every built-in element is :defined#193
jdalton wants to merge 1 commit into
dperini:masterfrom
jdalton:fix/defined-built-ins

Conversation

@jdalton

@jdalton jdalton commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

:defined asks the custom element registry about every candidate's tag name and requires an instanceof match, so it matches upgraded custom elements and nothing else. Every built-in element is defined as well, which means the selector currently returns almost nothing on an ordinary page.

Only a custom element can be undefined, and only until a definition exists and the element has been upgraded to it.

What it returns before and after
<div id=d1></div>
<my-thing id=mt></my-thing>
<button id=b1 is="fancy-btn">x</button>

Chromium matches d1 with :defined and skips the other two, since neither custom element has a definition yet. Before this change the engine matched none of the three, including the plain <div>. After customElements.define('my-thing', …) runs, both Chromium and the engine add mt.

Where browsers implement it, and why the test is ordered this way

Blink reads the element's custom element state and treats "uncustomized" and "custom" as defined, in element.h#L1201, which its own comment ties to the DOM definition. :defined is that and nothing else, in selector_checker.cc#L3139.

The helper checks the hyphen in the tag name first, because a hyphen is what makes a name a valid custom element name, and asks for the is attribute only when there is none. An ordinary element therefore costs one string scan and no host call, which matters because :defined matches nearly every element in a document.

References: the spec, the browser source, and what each part was reasoned from

This patch applies to master on its own. The sixteen in this series were checked by cherry-picking them onto master one after another, in this order and in reverse, and all sixteen land without a conflict.

@jdalton
jdalton force-pushed the fix/defined-built-ins branch from 863101b to e50f059 Compare September 4, 2026 18:00
':defined' asks the custom element registry about the candidate's tag name and requires an instanceof match, so it matches upgraded custom elements and nothing else. Every built-in element is defined; only a custom element can be undefined, and only until a definition exists and it has been upgraded. Browsers read it off the element's custom element state, where uncustomized and custom are the two that count as defined. https://github.com/chromium/chromium/blob/155.0.8041.1/third_party/blink/renderer/core/dom/element.h#L1201
 The test reads the hyphen in the name first, which is what makes a name a custom element name, and asks for the 'is' attribute only when there is none, so an ordinary element costs one string scan.

References:

- Spec: https://dom.spec.whatwg.org/#concept-element-defined — uncustomized and custom are the two states that count as defined
- Spec: https://html.spec.whatwg.org/#custom-elements-core-concepts — what makes a name a custom element name
- Chromium: https://github.com/chromium/chromium/blob/155.0.8041.1/third_party/blink/renderer/core/dom/element.h#L1201 — the state read, with the same spec link in its own comment
- Chromium: https://github.com/chromium/chromium/blob/155.0.8041.1/third_party/blink/renderer/core/css/selector_checker.cc#L3139 — ':defined' is that and nothing else
- MDN: https://developer.mozilla.org/en-US/docs/Web/CSS/:defined
@jdalton

jdalton commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator Author

This one sits with #190 and #191 and #192, which touch the conformance fixes found by comparing against Chromium.

They do not depend on each other. All seventeen in the series cherry-pick onto master in any order, and I checked that in both directions, so any one of these can land alone. The order below is the one they read best in:

  • #190 makes :enabled the complement of :disabled, fieldsets included.
  • #191 anchors the :required and :optional tests, and lets :optional take a button.
  • #192 makes a fieldset :valid when none of its controls is invalid.
  • #193 makes every built-in element :defined.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant