You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
performance regression on jest jsdom with 2.2.26 vs 2.2.25 - pseudo state rewrite #171
In current jest-environment-jsdom (30.5.0), there is performance issue with nswapi 2.2.26. On 2.2.25 it works fine. Problem is with the pseudo-state rewrites.
It's huge issue with the jest react jsdom based tests for floating-ui library based components.
On 2.2.25 it takes 94 ms on 2.2.26 it takes 24s - 270x slower.
Pseudo state rewrite in 2.2.26 uses new logic matchesNative(node, ":fullscreen") which is intended to call a browser’s native Element.matches. In jsdom, though, Element.matches is implemented by nwsapi itself. So evaluating :fullscreen re-enters nwsapi, which evaluates isFullscreen, which calls matchesNative, and so on. The focused runtime check recorded 940 nested Element.matches calls from one target.matches(":fullscreen").
In a real browser, native matches has some overhead compared with a property comparison, but it does not recurse. But under jsdom it becomes an expensive recursive loop until the try/catch unwinds from the stack limit.
It's on current jest-environment-jsdom which uses jsdom 26.0.1 -> nwsapi 2.2.27 so the problem is real.
I think jsdom has correct implementation, they have no choice, what do you think? Do you have some solution?
changed the title [-]performance regression in 2.2.26 vs 2.2.25 - pseudo state rewrite[/-][+]performance regression on jest jsdom with 2.2.26 vs 2.2.25 - pseudo state rewrite[/+]on Aug 30, 2026
Symptom: any userEvent interaction that opens a Radix Select portal hangs ~25-30s per interaction (tests fail on timeout). Same suite of 3 test files, only the nwsapi version changed (via npm overrides):
2.2.25: 38/38 pass, total 12s
2.2.26: fails, single affected test times out after 25s+
2.2.27: 7/38 fail, total 306s — identical hang signature
Consistent with the recursion described in #172 (:modal under jsdom, "2.2.26+"). Happy to provide more detail if useful; for now we remain pinned to 2.2.25.
Yes, bug was created after 2.2.27 was released, so nothing is fixed in 2.2.27. Bug describes between which versions change was made and describes current state. Sorry if isn't obvious.
The regression has the same root as #172: resolving a state pseudo-class asks the host whether it matches, and jsdom answers by calling back into this engine, so each call re-enters until the stack gives out.
What that costs, and what removing it costs instead
Reproduced with jsdom's wiring in place — window.Element.prototype.matches delegating to NW.match — and twenty calls averaged:
build
per match(':modal')
master at 2e9498f
577.799 ms
with the fix
0.026 ms
If your suite spends its time in matches() or closest() under jest's jsdom environment, this is the shape to check first: the cost only appears for the state pseudo-classes, and only on a host that routes matches back here.
The fix is #178. It applies to master on its own. If your workload is slower than 2.2.25 for a reason other than the state pseudo-classes, a selector or two from the slow path would help — the benchmark side of this is easy to measure once there is a shape to measure.
Activity
MirKml commentedon Aug 30, 2026
seem that #170 addressing this issue
[-]performance regression in 2.2.26 vs 2.2.25 - pseudo state rewrite[/-][+]performance regression on jest jsdom with 2.2.26 vs 2.2.25 - pseudo state rewrite[/+]fix(deps): pin nwsapi to 2.2.25, replacing the jest-only matches() shim
EGMW2 commentedon Sep 1, 2026
Confirming this regression is still present in 2.2.27 (it is not just 2.2.26 — the fix that went into 2.2.27 does not address it).
Environment: jsdom 25.0.1, vitest 4.1.11 (jsdom environment), @testing-library/user-event 14.6.6 driving @radix-ui/react-select 2.3.7.
Symptom: any userEvent interaction that opens a Radix Select portal hangs ~25-30s per interaction (tests fail on timeout). Same suite of 3 test files, only the nwsapi version changed (via npm overrides):
Consistent with the recursion described in #172 (
:modalunder jsdom, "2.2.26+"). Happy to provide more detail if useful; for now we remain pinned to 2.2.25.MirKml commentedon Sep 1, 2026
Yes, bug was created after 2.2.27 was released, so nothing is fixed in 2.2.27. Bug describes between which versions change was made and describes current state. Sorry if isn't obvious.
Follow #172 comments with proposed fix.
chore: upgrade root devDependencies, pin nwsapi to avoid jsdom regres…
chore: upgrade root devDependencies and fix fast-uri Dependabot alerts (
jdalton commentedon Sep 5, 2026
The regression has the same root as #172: resolving a state pseudo-class asks the host whether it matches, and jsdom answers by calling back into this engine, so each call re-enters until the stack gives out.
What that costs, and what removing it costs instead
Reproduced with jsdom's wiring in place —
window.Element.prototype.matchesdelegating toNW.match— and twenty calls averaged:match(':modal')2e9498fIf your suite spends its time in
matches()orclosest()under jest's jsdom environment, this is the shape to check first: the cost only appears for the state pseudo-classes, and only on a host that routesmatchesback here.The fix is #178. It applies to master on its own. If your workload is slower than 2.2.25 for a reason other than the state pseudo-classes, a selector or two from the slow path would help — the benchmark side of this is easy to measure once there is a shape to measure.
docs(test-support): cite the upstream nwsapi issues and when to delet…
Stop state pseudo-classes from re-entering the engine