Skip to content

performance regression on jest jsdom with 2.2.26 vs 2.2.25 - pseudo state rewrite #171

Closed

Description

@MirKml

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?

Activity

  1. MirKml commented on Aug 30, 2026

    @MirKml
    Author

    seem that #170 addressing this issue

  2. 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
  3. EGMW2 commented on Sep 1, 2026

    @EGMW2

    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):

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

  4. MirKml commented on Sep 1, 2026

    @MirKml
    Author

    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.

  5. jdalton commented on Sep 5, 2026

    @jdalton
    Collaborator

    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.

  6. added a commit that references this issue on Sep 17, 2026
    a6e79c4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions