Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
@asamuzakjp/dom-selector
Advanced tools
A CSS selector engine.
npm i @asamuzakjp/dom-selector
import { DOMSelector } from '@asamuzakjp/dom-selector';
import { JSDOM } from 'jsdom';
const { window } = new JSDOM();
const {
closest, matches, querySelector, querySelectorAll
} = new DOMSelector(window);
matches - equivalent to Element.matches()
Returns boolean true
if matched, false
otherwise
closest - equivalent to Element.closest()
Returns object? matched node
querySelector - equivalent to Document.querySelector(), DocumentFragment.querySelector() and Element.querySelector()
selector
string CSS selectornode
object Document, DocumentFragment or Element nodeopt
object? options
Returns object? matched node
querySelectorAll - equivalent to Document.querySelectorAll(), DocumentFragment.querySelectorAll() and Element.querySelectorAll()
NOTE: returns Array, not NodeList
selector
string CSS selectornode
object Document, DocumentFragment or Element nodeopt
object? options
Returns Array<(object | undefined)> array of matched nodes
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);
};
}
});
Pattern | Supported | Note |
---|---|---|
* | ✓ | |
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: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 | ✓ | |
E:scope | ✓ | |
E:current | Unsupported | |
E:current(s) | Unsupported | |
E:past | Unsupported | |
E:future | Unsupported | |
E:active | ✓ | |
E:hover | ✓ | |
E:focus | ✓ | |
E:focus‑within | ✓ | |
E:focus‑visible | ✓ | |
E:open E:closed | Partially supported | Matching 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:blank | Unsupported | |
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 || E | Unsupported | |
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;
}
}
See benchmark for the latest results.
F
: Failed because the selector is not supported or the result was incorrect.
Selector | jsdom v25.0.1 (nwsapi) | happy-dom | linkeDom | patched-jsdom (dom-selector) | Result |
---|---|---|---|---|---|
simple selector:matches('.content') | 115,054 ops/sec ±2.48% | 356,979 ops/sec ±2.34% | 8,556 ops/sec ±0.90% | 118,715 ops/sec ±0.17% | happydom is the fastest and 3.0 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,730 ops/sec ±0.63% | 323,008 ops/sec ±18.02% | 8,419 ops/sec ±0.66% | 86,074 ops/sec ±0.97% | happydom is the fastest and 3.8 times faster than patched-jsdom. jsdom is 1.3 times faster than patched-jsdom. |
compound selector:matches('p.content[id]:is(:invalid-nth-child, :only-child)') | F | 353,239 ops/sec ±2.73% | F | 43,662 ops/sec ±1.86% | happydom is the fastest and 8.1 times faster than patched-jsdom. |
compound selector:matches('p.content[id]:not(:is(.foo, .bar))') | 101,636 ops/sec ±1.59% | 351,747 ops/sec ±0.59% | 8,082 ops/sec ±0.61% | 83,928 ops/sec ±0.66% | happydom is the fastest and 4.2 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,482 ops/sec ±0.38% | F | 5,261 ops/sec ±0.72% | 61,745 ops/sec ±0.56% | 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)') | 31,671 ops/sec ±1.14% | F | 5,288 ops/sec ±0.43% | 42,614 ops/sec ±2.51% | patched-jsdom is the fastest. patched-jsdom is 1.3 times faster than jsdom. |
complex selector within logical pseudo-class:matches(':is(.box > .content, .block > .content)') | 100,293 ops/sec ±0.67% | F | 5,593 ops/sec ±0.40% | 78,292 ops/sec ±26.38% | jsdom is the fastest and 1.3 times faster than patched-jsdom. |
nested and chained :not() selector:matches('p:not(:is(:not(.content))):not(.foo)') | F | 299,143 ops/sec ±28.61% | 5,538 ops/sec ±0.44% | 84,061 ops/sec ±0.71% | happydom is the fastest and 3.6 times faster than patched-jsdom. |
Selector | jsdom v25.0.1 (nwsapi) | happy-dom | linkeDom | patched-jsdom (dom-selector) | Result |
---|---|---|---|---|---|
simple selector:closest('.container') | 96,990 ops/sec ±0.60% | 326,924 ops/sec ±0.52% | 8,453 ops/sec ±0.65% | 90,555 ops/sec ±1.70% | happydom is the fastest and 3.6 times faster than patched-jsdom. jsdom is 1.1 times faster than patched-jsdom. |
compound selector:closest('div.container[id]:not(.foo, .box)') | 63,821 ops/sec ±0.74% | F | 7,942 ops/sec ±0.47% | 54,674 ops/sec ±0.83% | 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,863 ops/sec ±0.65% | F | 5,336 ops/sec ±0.55% | 57,088 ops/sec ±0.76% | 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:has(> .content)') | 23,941 ops/sec ±1.24% | F | 5,182 ops/sec ±1.02% | 36,066 ops/sec ±0.48% | patched-jsdom is the fastest. patched-jsdom is 1.5 times faster than jsdom. |
complex selector within logical pseudo-class:closest(':is(.container > .content, .container > .box)') | 76,340 ops/sec ±0.36% | 235,292 ops/sec ±34.48% | 5,533 ops/sec ±0.53% | 68,793 ops/sec ±0.78% | happydom is the fastest and 3.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)') | F | F | 7,777 ops/sec ±0.65% | 71,028 ops/sec ±0.55% | patched-jsdom is the fastest. |
Selector | jsdom v25.0.1 (nwsapi) | happy-dom | linkeDom | patched-jsdom (dom-selector) | Result |
---|---|---|---|---|---|
simple selector:querySelector('.content') | 21,898 ops/sec ±1.84% | 265,006 ops/sec ±42.03% | 9,921 ops/sec ±0.56% | 81,017 ops/sec ±1.27% | happydom is the fastest and 3.3 times faster than patched-jsdom. patched-jsdom is 3.7 times faster than jsdom. |
compound selector:querySelector('p.content[id]:is(:last-child, :only-child)') | 8,676 ops/sec ±0.71% | 327,427 ops/sec ±1.06% | 9,507 ops/sec ±0.76% | 37,303 ops/sec ±0.95% | happydom is the fastest and 8.8 times faster than patched-jsdom. patched-jsdom is 4.3 times faster than jsdom. |
complex selector:querySelector('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner > .content') | 197 ops/sec ±0.84% | F | 1,230 ops/sec ±0.91% | 616 ops/sec ±1.22% | linkedom is the fastest and 2.0 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)') | 57.15 ops/sec ±2.55% | F | 1,452 ops/sec ±0.62% | 453 ops/sec ±0.95% | linkedom is the fastest and 3.2 times faster than patched-jsdom. patched-jsdom is 7.9 times faster than jsdom. |
complex selector within logical pseudo-class:querySelector(':is(.box > .content, .block > .content)') | 3,045 ops/sec ±1.16% | F | 9,229 ops/sec ±0.76% | 74,154 ops/sec ±0.83% | patched-jsdom is the fastest. patched-jsdom is 24.4 times faster than jsdom. |
nested and chained :not() selector:querySelector('p:not(:is(:not(.content))):not(.foo)') | F | 323,503 ops/sec ±0.95% | 9,383 ops/sec ±0.63% | 68,866 ops/sec ±0.70% | happydom is the fastest and 4.7 times faster than patched-jsdom. |
Selector | jsdom v25.0.1 (nwsapi) | happy-dom | linkeDom | patched-jsdom (dom-selector) | Result |
---|---|---|---|---|---|
simple selector:querySelectorAll('.content') | 973 ops/sec ±49.13% | 477 ops/sec ±43.91% | 1,134 ops/sec ±0.38% | 1,221 ops/sec ±0.69% | patched-jsdom is the fastest. patched-jsdom is 1.3 times faster than jsdom. |
compound selector:querySelectorAll('p.content[id]:is(:last-child, :only-child)') | 681 ops/sec ±0.58% | 572 ops/sec ±3.54% | 1,124 ops/sec ±0.80% | 640 ops/sec ±0.62% | linkedom is the fastest and 1.8 times faster than patched-jsdom. jsdom is 1.1 times faster than patched-jsdom. |
complex selector:querySelectorAll('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner > .content') | 198 ops/sec ±0.92% | F | 386 ops/sec ±0.74% | 206 ops/sec ±0.70% | 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)') | 56.19 ops/sec ±1.54% | F | 419 ops/sec ±0.47% | 183 ops/sec ±0.57% | linkedom is the fastest and 2.3 times faster than patched-jsdom. patched-jsdom is 3.2 times faster than jsdom. |
complex selector within logical pseudo-class:querySelectorAll(':is(.box > .content, .block > .content)') | 276 ops/sec ±0.97% | F | 468 ops/sec ±0.74% | 811 ops/sec ±1.18% | patched-jsdom is the fastest. patched-jsdom is 2.9 times faster than jsdom. |
nested and chained :not() selector:querySelectorAll('p:not(:is(:not(.content))):not(.foo)') | F | 523 ops/sec ±5.90% | 1,160 ops/sec ±1.16% | 1,321 ops/sec ±0.52% | patched-jsdom is the fastest. |
The following resources have been of great help in the development of the DOM Selector.
Copyright (c) 2023 asamuzaK (Kazz)
FAQs
A CSS selector engine.
The npm package @asamuzakjp/dom-selector receives a total of 252,636 weekly downloads. As such, @asamuzakjp/dom-selector popularity was classified as popular.
We found that @asamuzakjp/dom-selector demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
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.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.