Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
@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:host | ✓ | |
E:host(s) | ✓ | |
E:host‑context(s) | ✓ | |
E:popover-open | ✓ | |
E:state(v) | ✓ | *1 |
E:host(:state(v)) | ✓ | *1 |
*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.0 (nwsapi) | happy-dom | linkeDom | patched-jsdom (dom-selector) | Result |
---|---|---|---|---|---|
simple selector:matches('.content') | 937,822 ops/sec ±0.50% | 7,910,671 ops/sec ±0.27% | 8,620 ops/sec ±0.75% | 772,780 ops/sec ±0.30% | happydom is the fastest and 10.2 times faster than patched-jsdom. jsdom is 1.2 times faster than patched-jsdom. |
compound selector:matches('p.content[id]:is(:last-child, :only-child)') | 572,176 ops/sec ±0.38% | 7,693,760 ops/sec ±1.88% | 8,257 ops/sec ±0.63% | 402,508 ops/sec ±0.21% | happydom is the fastest and 19.1 times faster than patched-jsdom. jsdom is 1.4 times faster than patched-jsdom. |
compound selector:matches('p.content[id]:is(:invalid-nth-child, :only-child)') | F | 6,987,723 ops/sec ±0.39% | F | 74,756 ops/sec ±1.26% | happydom is the fastest and 93.5 times faster than patched-jsdom. |
compound selector:matches('p.content[id]:not(:is(.foo, .bar))') | 455,833 ops/sec ±1.90% | 6,645,611 ops/sec ±1.63% | 8,205 ops/sec ±1.12% | 326,773 ops/sec ±1.38% | happydom is the fastest and 20.3 times faster than patched-jsdom. jsdom is 1.4 times faster than patched-jsdom. |
complex selector:matches('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner > .content') | 144,430 ops/sec ±1.65% | F | 5,445 ops/sec ±1.00% | 121,298 ops/sec ±1.34% | jsdom is the fastest and 1.2 times faster than patched-jsdom. |
complex selector:matches('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner:has(> .content)') | F | F | 5,337 ops/sec ±0.86% | 17,419 ops/sec ±2.05% | patched-jsdom is the fastest. |
complex selector within logical pseudo-class:matches(':is(.box > .content, .block > .content)') | 398,206 ops/sec ±0.51% | F | 5,719 ops/sec ±0.84% | 326,625 ops/sec ±1.25% | jsdom is the fastest and 1.2 times faster than patched-jsdom. |
Selector | jsdom v25.0.0 (nwsapi) | happy-dom | linkeDom | patched-jsdom (dom-selector) | Result |
---|---|---|---|---|---|
simple selector:closest('.container') | 357,783 ops/sec ±1.70% | 2,319,384 ops/sec ±0.92% | 8,581 ops/sec ±0.84% | 328,849 ops/sec ±1.31% | happydom is the fastest and 7.1 times faster than patched-jsdom. jsdom is 1.1 times faster than patched-jsdom. |
compound selector:closest('div.container[id]:not(.foo, .box)') | 131,236 ops/sec ±0.73% | F | 8,052 ops/sec ±0.95% | 119,653 ops/sec ±1.44% | 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 > .content') | 137,291 ops/sec ±0.17% | F | 5,483 ops/sec ±0.36% | 116,733 ops/sec ±0.21% | 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)') | F | F | 5,354 ops/sec ±0.87% | 14,363 ops/sec ±1.58% | patched-jsdom is the fastest. |
complex selector within logical pseudo-class:closest(':is(.container > .content, .container > .box)') | 190,643 ops/sec ±1.69% | 2,819,109 ops/sec ±1.22% | 5,732 ops/sec ±0.34% | 169,772 ops/sec ±1.48% | happydom is the fastest and 16.6 times faster than patched-jsdom. jsdom is 1.1 times faster than patched-jsdom. |
Selector | jsdom v25.0.0 (nwsapi) | happy-dom | linkeDom | patched-jsdom (dom-selector) | Result |
---|---|---|---|---|---|
simple selector:querySelector('.content') | 30,752 ops/sec ±1.40% | 3,402,064 ops/sec ±1.11% | 10,466 ops/sec ±0.91% | 30,353 ops/sec ±1.46% | happydom is the fastest and 112.1 times faster than patched-jsdom. jsdom is 1.0 times faster than patched-jsdom. |
compound selector:querySelector('p.content[id]:is(:last-child, :only-child)') | 9,716 ops/sec ±1.63% | 3,318,668 ops/sec ±1.19% | 9,481 ops/sec ±0.59% | 9,123 ops/sec ±0.47% | happydom is the fastest and 363.8 times faster than patched-jsdom. jsdom is 1.1 times faster than patched-jsdom. |
complex selector:querySelector('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner > .content') | 219 ops/sec ±1.75% | F | 1,270 ops/sec ±0.34% | 779 ops/sec ±2.08% | linkedom is the fastest and 1.6 times faster than patched-jsdom. patched-jsdom is 3.6 times faster than jsdom. |
complex selector:querySelector('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner:has(> .content)') | F | F | 1,532 ops/sec ±1.81% | 516 ops/sec ±1.93% | linkedom is the fastest and 3.0 times faster than patched-jsdom. |
complex selector within logical pseudo-class:querySelector(':is(.box > .content, .block > .content)') | 3,215 ops/sec ±0.60% | F | 9,656 ops/sec ±1.10% | 159,781 ops/sec ±2.23% | patched-jsdom is the fastest. patched-jsdom is 49.7 times faster than jsdom. |
Selector | jsdom v25.0.0 (nwsapi) | happy-dom | linkeDom | patched-jsdom (dom-selector) | Result |
---|---|---|---|---|---|
simple selector:querySelectorAll('.content') | 3,145 ops/sec ±1.24% | 7,471,952 ops/sec ±0.97% | 1,187 ops/sec ±0.18% | 3,330 ops/sec ±0.40% | happydom is the fastest and 2244.1 times faster than patched-jsdom. patched-jsdom is 1.1 times faster than jsdom. |
compound selector:querySelectorAll('p.content[id]:is(:last-child, :only-child)') | 969 ops/sec ±1.23% | 6,444,347 ops/sec ±0.86% | 1,155 ops/sec ±1.22% | 993 ops/sec ±0.21% | happydom is the fastest and 6486.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 > .content') | 221 ops/sec ±1.66% | F | 418 ops/sec ±1.84% | 905 ops/sec ±1.45% | patched-jsdom is the fastest. patched-jsdom is 4.1 times faster than jsdom. |
complex selector:querySelectorAll('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner:has(> .content)') | F | F | 454 ops/sec ±0.15% | 567 ops/sec ±1.60% | patched-jsdom is the fastest. |
complex selector within logical pseudo-class:querySelectorAll(':is(.box > .content, .block > .content)') | 295 ops/sec ±1.89% | F | 515 ops/sec ±1.81% | 790 ops/sec ±1.60% | patched-jsdom is the fastest. patched-jsdom is 2.7 times faster than jsdom. |
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 251,608 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 1 open source maintainer 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’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.