DOM Selector
A CSS selector engine built for strict specification compliance.
Features
- Strict Specification Compliance: Strictly adheres to modern web standards. It accurately parses, evaluates, and extracts elements across complex combinations of pseudo-classes and HTML attributes. Features comprehensive support for CSS Selectors Level 4 (e.g.,
:is(),:not(),:where(),:has()) and Shadow DOM pseudo-classes (:host,:host-context). - Utility Functions: Provides utility methods alongside standard querying, such as
check()for AST evaluation andextractSubjects()for extracting subject keys from selectors. - jsdom's Default Engine: Adopted as the CSS selector engine for jsdom.
Install
npm i @asamuzakjp/dom-selector
Usage
import { DOMSelector } from '@asamuzakjp/dom-selector';
import { JSDOM } from 'jsdom';
const { window } = new JSDOM();
// Destructuring methods (all methods are bound to the instance)
const {
check, closest, extractSubjects, matches, querySelector, querySelectorAll, supports
} = new DOMSelector(window);
API
new DOMSelector(window, document?, opt?)
Creates an instance of the DOMSelector.
window{Window} The window object.document{Document}? The document object. Defaults towindow.document.opt{object}? Options:opt.cacheSize{number}? Maximum number of items to store in the internal LRU cache. Defaults to4096. This applies to every internal cache, including the per-document cache that pairs parsed selectors with a document, so that repeatedly querying distinct selectors on a live document does not grow its cache without limits.opt.idlUtils{object}? The internal IDL wrapper used for jsdom.opt.maxLength{number}? The maximum length of the selector allowed. Defaults to2048.
matches(selector, node, opt?)
Equivalent to Element.matches().
selector{string} CSS selector.node{Element} Element node.opt{object}? Options:opt.noexcept{boolean}?trueto suppress exceptions (e.g.,DOMExceptionfor invalid selectors) and returnfalseinstead.opt.warn{boolean}?trueto output warnings to the console when encountering unsupported or deprecated features.
- Returns {boolean}
trueif matched,falseotherwise.
closest(selector, node, opt?)
Equivalent to Element.closest().
selector{string} CSS selector.node{Element} Element node.opt{object}? Options:opt.noexcept{boolean}?trueto suppress exceptions and returnnullinstead.opt.warn{boolean}?trueto output warnings to the console when encountering unsupported or deprecated features.
- Returns {Element | null} The matched ancestor node or
null.
querySelector(selector, node, opt?)
Equivalent to Document.querySelector(), DocumentFragment.querySelector() and Element.querySelector().
selector{string} CSS selector.node{Document | DocumentFragment | Element} Node to find within.opt{object}? Options:opt.noexcept{boolean}?trueto suppress exceptions and returnnullinstead.opt.warn{boolean}?trueto output warnings to the console when encountering unsupported or deprecated features.
- Returns {Element | null} The matched node or
null.
querySelectorAll(selector, node, opt?)
Equivalent to Document.querySelectorAll(), DocumentFragment.querySelectorAll() and Element.querySelectorAll().
NOTE: Returns a standard Array<Element>, not a NodeList.
selector{string} CSS selector.node{Document | DocumentFragment | Element} Node to find within.opt{object}? Options:opt.noexcept{boolean}?trueto suppress exceptions and return an empty array[]instead.opt.warn{boolean}?trueto output warnings to the console when encountering unsupported or deprecated features.
- Returns {Array} Array of matched elements.
check(selector, node, opt?)
Checks if an element matches a CSS selector and returns additional abstract syntax tree (AST) information. NOTE: Any pseudo-elements in the selector are excluded from the matching evaluation.
selector{string} CSS selector.node{Element} Element node.opt{object}? Options:opt.requireAst{boolean}? Indicates whether to always try to parse and return the AST.opt.noexcept{boolean}?trueto suppress exceptions and store the error in theerrorproperty of the result instead of throwing.opt.warn{boolean}?trueto output warnings to the console when encountering unsupported or deprecated features.
- Returns {CheckResult} An object containing the following properties:
match{boolean}trueif the element matches the selector,falseotherwise.pseudoElement{string | null} The pseudo-element extracted from the selector, if any.ast{object | null} The parsed AST object.error{DOMException | Error | null} The error object, if any.
extractSubjects(selector, caseSensitive?)
Parses a selector and extracts the rightmost subject keys (Id, Class, Tag).
selector{string} CSS selector.caseSensitive{boolean}?trueif the tag key should be case sensitive. Defaults tofalse.- Returns {Array} An array of extracted subject keys. Each object contains:
id{string | null} The ID.className{string | null} The class name.tag{string | null} The tag name.
supports(selector)
Checks if the given CSS selector is supported by this engine. See the table below for the full list of supported selectors.
selector{string} CSS selector.- Returns {boolean}
trueif the selector is supported,falseotherwise.
clear(clearAll?)
Clears the internal caches to free up memory.
clearAll{boolean}? Whether to clear all caches. If false, only cached matching results are cleared. Defaults tofalse.- Returns {void}
Supported CSS selectors
| Pattern | Supported | Note |
|---|---|---|
* | ✓ | |
E | ✓ | |
ns|E | ✓ | |
*|E | ✓ | |
|E | ✓ | |
E F | ✓ | |
E > F | ✓ | |
E + F | ✓ | |
E ~ F | ✓ | |
F || E | Unsupported | |
E.warning | ✓ | |
E#myid | ✓ | |
E[foo] | ✓ | |
E[foo="bar"] | ✓ | |
E[foo="bar" i] | ✓ | |
E[foo="bar" s] | ✓ | |
E[foo~="bar"] | ✓ | |
E[foo^="bar"] | ✓ | |
E[foo$="bar"] | ✓ | |
E[foo*="bar"] | ✓ | |
E[foo|="en"] | ✓ | |
E:is(s1, s2, …) | ✓ | |
E:not(s1, s2, …) | ✓ | |
E:where(s1, s2, …) | ✓ | |
E:has(rs1, rs2, …) | ✓ | |
E:defined | Partially supported | Matching with MathML is not yet supported. |
E:dir(ltr) | ✓ | |
E:lang(en) | ✓ | |
E:any-link | ✓ | |
E:link | ✓ | |
E:visited | ✓ | Returns false or null to prevent fingerprinting. |
E:local-link | ✓ | |
E:target | ✓ | |
E:target-within | Removed from spec. | |
E:scope | ✓ | |
E:hover | ✓ | |
E:active | ✓ | |
E:focus | ✓ | |
E:focus-visible | ✓ | |
E:focus-within | ✓ | |
E:current | Unsupported | |
E:current(s) | Unsupported | |
E:past | Unsupported | |
E:future | Unsupported | |
E:open | Partially supported | Matching with <select>, e.g. select:open, or <input type="color"> are not supported. |
E:popover-open | Unsupported | |
E:enabledE:disabled | ✓ | |
E:read-writeE:read-only | ✓ | |
E:placeholder-shown | ✓ | |
E:default | ✓ | |
E:checked | ✓ | |
E:indeterminate | ✓ | |
E:blank | Unsupported | |
E:validE:invalid | ✓ | |
E:in-rangeE:out-of-range | ✓ | |
E:requiredE:optional | ✓ | |
E:user-validE:user-invalid | Unsupported | |
E:root | ✓ | |
E:empty | ✓ | |
E:nth-child(n [of S]?) | ✓ | |
E:nth-last-child(n [of S]?) | ✓ | |
E:first-child | ✓ | |
E:last-child | ✓ | |
E:only-child | ✓ | |
E:nth-of-type(n) | ✓ | |
E:nth-last-of-type(n) | ✓ | |
E:first-of-type | ✓ | |
E:last-of-type | ✓ | |
E:only-of-type | ✓ | |
E:nth-col(n) | Unsupported | |
E:nth-last-col(n) | Unsupported | |
CE:state(v) | ✓ | *1 |
:host | ✓ | |
:host(s) | ✓ | |
:host(:state(v)) | ✓ | *1 |
:host:has(rs1, rs2, ...) | ✓ | |
:host(s):has(rs1, rs2, ...) | ✓ | |
:host-context(s) | ✓ | |
:host-context(s):has(rs1, rs2, ...) | ✓ | |
& | ✓ | Only supports outermost &, i.e. equivalent to :scope |
*1: ElementInternals.states, i.e. CustomStateSet, is not implemented in jsdom, so you need to apply a patch in the custom element constructor.
class LabeledCheckbox extends window.HTMLElement {
#internals;
constructor() {
super();
this.#internals = this.attachInternals();
// patch CustomStateSet
if (!this.#internals.states) {
this.#internals.states = new Set();
}
this.addEventListener('click', this._onClick.bind(this));
}
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;
}
}
Performance
See benchmark for the latest results.
Acknowledgments
The following resources have been of great help in the development of the DOM Selector.
Copyright (c) 2023 asamuzaK (Kazz)