Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@asamuzakjp/dom-selector

Package Overview
Dependencies
Maintainers
1
Versions
191
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@asamuzakjp/dom-selector

A CSS selector engine.

  • 6.3.5
  • latest
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
266K
decreased by-19.13%
Maintainers
1
Weekly downloads
 
Created
Source

DOM Selector

build CodeQL npm (scoped)

A CSS selector engine.

Install

npm i @asamuzakjp/dom-selector

Usage

import { DOMSelector } from '@asamuzakjp/dom-selector';
import { JSDOM } from 'jsdom';

const { window } = new JSDOM();
const {
  closest, matches, querySelector, querySelectorAll
} = new DOMSelector(window);

matches(selector, node, opt)

matches - equivalent to Element.matches()

Parameters
  • selector string CSS selector
  • node object Element node
  • opt object? options
    • opt.noexcept boolean? no exception
    • opt.warn boolean? console warn e.g. unsupported pseudo-class

Returns boolean true if matched, false otherwise

closest(selector, node, opt)

closest - equivalent to Element.closest()

Parameters
  • selector string CSS selector
  • node object Element node
  • opt object? options
    • opt.noexcept boolean? no exception
    • opt.warn boolean? console warn e.g. unsupported pseudo-class

Returns object? matched node

querySelector(selector, node, opt)

querySelector - equivalent to Document.querySelector(), DocumentFragment.querySelector() and Element.querySelector()

Parameters
  • selector string CSS selector
  • node object Document, DocumentFragment or Element node
  • opt object? options
    • opt.noexcept boolean? no exception
    • opt.warn boolean? console warn e.g. unsupported pseudo-class

Returns object? matched node

querySelectorAll(selector, node, opt)

querySelectorAll - equivalent to Document.querySelectorAll(), DocumentFragment.querySelectorAll() and Element.querySelectorAll()
NOTE: returns Array, not NodeList

Parameters
  • selector string CSS selector
  • node object Document, DocumentFragment or Element node
  • opt object? options
    • opt.noexcept boolean? no exception
    • opt.warn boolean? console warn e.g. unsupported pseudo-class

Returns Array<(object | undefined)> array of matched nodes

Monkey patch jsdom

import { DOMSelector } from '@asamuzakjp/dom-selector';
import { JSDOM } from 'jsdom';

const dom = new JSDOM('', {
  runScripts: 'dangerously',
  url: 'http://localhost/',
  beforeParse: window => {
    const domSelector = new DOMSelector(window);

    const matches = domSelector.matches.bind(domSelector);
    window.Element.prototype.matches = function (...args) {
      if (!args.length) {
        throw new window.TypeError('1 argument required, but only 0 present.');
      }
      const [selector] = args;
      return matches(selector, this);
    };

    const closest = domSelector.closest.bind(domSelector);
    window.Element.prototype.closest = function (...args) {
      if (!args.length) {
        throw new window.TypeError('1 argument required, but only 0 present.');
      }
      const [selector] = args;
      return closest(selector, this);
    };

    const querySelector = domSelector.querySelector.bind(domSelector);
    window.Document.prototype.querySelector = function (...args) {
      if (!args.length) {
        throw new window.TypeError('1 argument required, but only 0 present.');
      }
      const [selector] = args;
      return querySelector(selector, this);
    };
    window.DocumentFragment.prototype.querySelector = function (...args) {
      if (!args.length) {
        throw new window.TypeError('1 argument required, but only 0 present.');
      }
      const [selector] = args;
      return querySelector(selector, this);
    };
    window.Element.prototype.querySelector = function (...args) {
      if (!args.length) {
        throw new window.TypeError('1 argument required, but only 0 present.');
      }
      const [selector] = args;
      return querySelector(selector, this);
    };

    const querySelectorAll = domSelector.querySelectorAll.bind(domSelector);
    window.Document.prototype.querySelectorAll = function (...args) {
      if (!args.length) {
        throw new window.TypeError('1 argument required, but only 0 present.');
      }
      const [selector] = args;
      return querySelectorAll(selector, this);
    };
    window.DocumentFragment.prototype.querySelectorAll = function (...args) {
      if (!args.length) {
        throw new window.TypeError('1 argument required, but only 0 present.');
      }
      const [selector] = args;
      return querySelectorAll(selector, this);
    };
    window.Element.prototype.querySelectorAll = function (...args) {
      if (!args.length) {
        throw new window.TypeError('1 argument required, but only 0 present.');
      }
      const [selector] = args;
      return querySelectorAll(selector, this);
    };
  }
});

Supported CSS selectors

PatternSupportedNote
*
ns|E
*|E
|E
E
E:not(s1, s2, …)
E:is(s1, s2, …)
E:where(s1, s2, …)
E:has(rs1, rs2, …)
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:definedPartially supportedMatching with MathML is not yet supported.
E:dir(ltr)
E:lang(en)
E:any‑link
E:link
E:visitedReturns false or null to prevent fingerprinting.
E:local‑link
E:target
E:target‑within
E:scope
E:currentUnsupported
E:current(s)Unsupported
E:pastUnsupported
E:futureUnsupported
E:active
E:hover
E:focus
E:focus‑within
E:focus‑visible
E:open
E:closed
Partially supportedMatching with <select>, e.g. select:open, is not supported.
E:enabled
E:disabled
E:read‑write
E:read‑only
E:placeholder‑shown
E:default
E:checked
E:indeterminate
E:valid
E:invalid
E:required
E:optional
E:blankUnsupported
E:user‑valid
E: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 F
E > F
E + F
E ~ F
F || EUnsupported
E:nth‑col(n)Unsupported
E:nth‑last‑col(n)Unsupported
E:popover-open
E:state(v)*1
:host
:host(s)
:host‑context(s)
:host(:state(v))*1
:host:has(rs1, rs2, ...)
:host(s):has(rs1, rs2, ...)
:host‑context(s):has(rs1, rs2, ...)

*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.

F: Failed because the selector is not supported or the result was incorrect.

matches()

Selectorjsdom v25.0.1 (nwsapi)happy-domlinkeDompatched-jsdom (dom-selector)Result
simple selector:
matches('.content')
117,468 ops/sec ±4.45%349,037 ops/sec ±2.71%7,986 ops/sec ±0.83%121,277 ops/sec ±0.58%happydom is the fastest and 2.9 times faster than patched-jsdom. patched-jsdom is 1.0 times faster than jsdom.
compound selector:
matches('p.content[id]:is(:last-child, :only-child)')
109,103 ops/sec ±1.81%323,742 ops/sec ±16.05%7,826 ops/sec ±0.70%89,905 ops/sec ±0.25%happydom is the fastest and 3.6 times faster than patched-jsdom. jsdom is 1.2 times faster than patched-jsdom.
compound selector:
matches('p.content[id]:is(:invalid-nth-child, :only-child)')
F350,215 ops/sec ±2.68%F44,510 ops/sec ±0.33%happydom is the fastest and 7.9 times faster than patched-jsdom.
compound selector:
matches('p.content[id]:not(:is(.foo, .bar))')
106,858 ops/sec ±0.43%352,297 ops/sec ±0.17%7,639 ops/sec ±0.61%87,013 ops/sec ±0.63%happydom is the fastest and 4.0 times faster than patched-jsdom. jsdom is 1.2 times faster than patched-jsdom.
complex selector:
matches('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner > .content')
70,868 ops/sec ±0.57%F4,876 ops/sec ±0.49%64,719 ops/sec ±0.36%jsdom is the fastest and 1.1 times faster than patched-jsdom.
complex selector:
matches('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner:has(> .content)')
FF4,752 ops/sec ±0.54%15,398 ops/sec ±3.41%patched-jsdom is the fastest.
complex selector within logical pseudo-class:
matches(':is(.box > .content, .block > .content)')
99,626 ops/sec ±0.63%F5,129 ops/sec ±0.45%95,577 ops/sec ±0.65%jsdom is the fastest and 1.0 times faster than patched-jsdom.
nested and chained :not() selector:
matches('p:not(:is(:not(.content))):not(.foo)')
FF4,701 ops/sec ±13.49%82,153 ops/sec ±0.68%patched-jsdom is the fastest.

closest()

Selectorjsdom v25.0.1 (nwsapi)happy-domlinkeDompatched-jsdom (dom-selector)Result
simple selector:
closest('.container')
93,212 ops/sec ±0.46%263,009 ops/sec ±30.00%7,996 ops/sec ±0.64%91,205 ops/sec ±0.35%happydom is the fastest and 2.9 times faster than patched-jsdom. jsdom is 1.0 times faster than patched-jsdom.
compound selector:
closest('div.container[id]:not(.foo, .box)')
64,257 ops/sec ±0.27%F7,412 ops/sec ±0.84%55,072 ops/sec ±0.33%jsdom is the fastest and 1.2 times faster than patched-jsdom.
complex selector:
closest('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner > .content')
65,291 ops/sec ±0.22%F4,890 ops/sec ±0.41%58,028 ops/sec ±0.64%jsdom is the fastest and 1.1 times faster than patched-jsdom.
complex selector:
closest('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner:has(> .content)')
FF4,703 ops/sec ±1.06%13,256 ops/sec ±0.48%patched-jsdom is the fastest.
complex selector within logical pseudo-class:
closest(':is(.container > .content, .container > .box)')
75,927 ops/sec ±0.36%307,805 ops/sec ±0.48%5,108 ops/sec ±0.41%69,864 ops/sec ±0.18%happydom is the fastest and 4.4 times faster than patched-jsdom. jsdom is 1.1 times faster than patched-jsdom.
nested and chained :not() selector:
closest('div:not(:is(:not(.container))):not(.box)')
FF7,399 ops/sec ±0.61%70,308 ops/sec ±0.48%patched-jsdom is the fastest.

querySelector()

Selectorjsdom v25.0.1 (nwsapi)happy-domlinkeDompatched-jsdom (dom-selector)Result
simple selector:
querySelector('.content')
22,886 ops/sec ±0.82%193,155 ops/sec ±54.70%9,439 ops/sec ±0.63%78,132 ops/sec ±0.17%happydom is the fastest and 2.5 times faster than patched-jsdom. patched-jsdom is 3.4 times faster than jsdom.
compound selector:
querySelector('p.content[id]:is(:last-child, :only-child)')
8,542 ops/sec ±0.62%336,066 ops/sec ±0.67%9,046 ops/sec ±0.77%35,397 ops/sec ±1.69%happydom is the fastest and 9.5 times faster than patched-jsdom. patched-jsdom is 4.1 times faster than jsdom.
complex selector:
querySelector('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner > .content')
204 ops/sec ±0.44%F1,186 ops/sec ±0.50%626 ops/sec ±0.57%linkedom is the fastest and 1.9 times faster than patched-jsdom. patched-jsdom is 3.1 times faster than jsdom.
complex selector:
querySelector('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner:has(> .content)')
FF1,476 ops/sec ±0.74%453 ops/sec ±0.78%linkedom is the fastest and 3.3 times faster than patched-jsdom.
complex selector within logical pseudo-class:
querySelector(':is(.box > .content, .block > .content)')
3,144 ops/sec ±0.45%F8,766 ops/sec ±0.78%77,257 ops/sec ±0.50%patched-jsdom is the fastest. patched-jsdom is 24.6 times faster than jsdom.
nested and chained :not() selector:
querySelector('p:not(:is(:not(.content))):not(.foo)')
FF8,972 ops/sec ±0.70%70,248 ops/sec ±0.39%patched-jsdom is the fastest.

querySelectorAll()

Selectorjsdom v25.0.1 (nwsapi)happy-domlinkeDompatched-jsdom (dom-selector)Result
simple selector:
querySelectorAll('.content')
1,292 ops/sec ±0.58%494 ops/sec ±44.77%1,091 ops/sec ±0.72%1,226 ops/sec ±0.45%jsdom is the fastest and 1.1 times faster than patched-jsdom.
compound selector:
querySelectorAll('p.content[id]:is(:last-child, :only-child)')
688 ops/sec ±0.91%455 ops/sec ±42.98%1,096 ops/sec ±0.90%656 ops/sec ±0.50%linkedom is the fastest and 1.7 times faster than patched-jsdom. jsdom is 1.0 times faster than patched-jsdom.
complex selector:
querySelectorAll('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner > .content')
206 ops/sec ±0.58%F393 ops/sec ±0.62%209 ops/sec ±0.58%linkedom is the fastest and 1.9 times faster than patched-jsdom. patched-jsdom is 1.0 times faster than jsdom.
complex selector:
querySelectorAll('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner:has(> .content)')
FF421 ops/sec ±0.83%186 ops/sec ±0.49%linkedom is the fastest and 2.3 times faster than patched-jsdom.
complex selector within logical pseudo-class:
querySelectorAll(':is(.box > .content, .block > .content)')
288 ops/sec ±0.64%F471 ops/sec ±0.71%796 ops/sec ±2.11%patched-jsdom is the fastest. patched-jsdom is 2.8 times faster than jsdom.
nested and chained :not() selector:
querySelectorAll('p:not(:is(:not(.content))):not(.foo)')
FF1,108 ops/sec ±0.45%1,316 ops/sec ±0.48%patched-jsdom is the fastest.

Acknowledgments

The following resources have been of great help in the development of the DOM Selector.


Copyright (c) 2023 asamuzaK (Kazz)

FAQs

Package last updated on 14 Nov 2024

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc