Skip to content

Conversation

@TrevorBurnham
Copy link
Contributor

Fixes #1882

This pull request optimizes the querySelector method by fixing caching and avoiding an unnecessary sort step.

Caching Bug Fix

The Bug:

The previous implementation of querySelector did not update the cache with the result of the query. It would read from the cache, but it would never write the result back to the cache. This meant that for repeated calls with the same selector, the query would be re-executed every time, negating the benefits of caching.

The Fix:

The new implementation correctly updates the cache with the result of the query. If an element is found, a WeakRef to it is stored in the cache. If no element is found, null is stored.

Cache Invalidation:

happy-dom invalidates the cache for all selectors that were affected by the change. The existing unit test coverage confirms that stale values are not returned from the cache.

Sorting Algorithm Improvement

In this analysis:

  • n is the total number of nodes in the DOM (or the relevant subtree).
  • g is the number of selector groups in the query (e.g., div, p has g=2).

Old Algorithm:

  1. Find Matches: For each of the g selector groups, the algorithm would traverse the DOM to find the first matching element. In the worst case, this traversal could visit all n nodes. This step is repeated for each group, so the complexity is O(g * n).
  2. Sort Matches: The g matched elements were then sorted to determine the first one in document order. The complexity of this sort is O(g log g).

The total complexity of the old algorithm was O(g * n + g log g).

New Algorithm:

  1. Find Matches and Compare: The new algorithm also traverses the DOM for each of the g selector groups. However, instead of collecting all matches and sorting them at the end, it maintains a running "best candidate". When a new match is found, it's compared to the current best, which is a constant time O(1) operation.

The total complexity of the new algorithm is O(g * n), making it more efficient for selectors with multiple selector groups.

@TrevorBurnham TrevorBurnham force-pushed the fix-query-selector-cache branch 2 times, most recently from 8c9033b to a23e833 Compare December 22, 2025 03:44
This commit fixes a bug where querySelector never stored results in its
cache, causing a cache miss 100% of the time.

Changes:

- Store query results in the cache using WeakRef for found elements and
  false for "no element found" (distinguishing from null, which indicates
  an invalidated cache entry)
- Replace the sort-based approach for multiple selector groups with a
  running best-match comparison
- Clear the document cache synchronously when location.hash changes,
  fixing cache invalidation for the :target pseudo-class selector
@TrevorBurnham TrevorBurnham force-pushed the fix-query-selector-cache branch from a23e833 to a387864 Compare December 22, 2025 03:59
@TrevorBurnham
Copy link
Contributor Author

@capricorn86 I've updated this branch. All checks are passing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

querySelector cache always misses

1 participant