Skip to content

Drop memoized queries when a node changes document - #4246

Merged
domenic merged 2 commits into
jsdom:mainfrom
Kjubikstronk:html-ness-case
Aug 22, 2026
Merged

domenic merged 2 commits into
jsdom:mainfrom
Kjubikstronk:html-ness-case

Conversation

@Kjubikstronk

Copy link
Copy Markdown
Contributor

getElementsByTagName bakes the document's HTML-ness into the collection it returns, and per the spec a collection that already exists keeps behaving that way after its root moves to a document of the other kind. A new call has to reflect the new document, but the memoized collection was handed back instead, so it kept lowercasing.

Adoption now drops the memoized queries on the nodes it moves. Collections already handed out are untouched, which is what Element-getElementsByTagName-change-document-HTMLNess.html checks.

Note this touches the same loop in _adoptNode as #4245, so whichever lands second will need a trivial rebase.

Motivation: still working through the failures recorded in to-run.yaml rather than hitting this in an application.

getElementsByTagName bakes the document's HTML-ness into the collection it
returns, which is what the spec wants for a collection that already exists.
The memoized copy was handed back after the node moved to a document of the
other kind, so a fresh call kept the old lowercasing behaviour.

Co-authored-by: Claude Opus 5 <[email protected]>

@domenic domenic left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work, although it could be better in a couple ways.

Comment thread lib/jsdom/living/nodes/Document-impl.js Outdated

// Collections built before the move captured the old document's HTML-ness, and the spec
// keeps those working. A fresh call has to see the new document, so drop the memoized ones.
inclusiveDescendant._memoizedQueries = {};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
inclusiveDescendant._memoizedQueries = {};
inclusiveDescendant._memoizedQueries = Object.create(null);

Bonus points if you also fix

this._memoizedQueries = {};

inclusiveDescendant._ownerDocument = newDocument;

// Collections built before the move captured the old document's HTML-ness, and the spec
// keeps those working. A fresh call has to see the new document, so drop the memoized ones.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment explaining why we don't use _clearMemoizedQueries().

@Kjubikstronk

Copy link
Copy Markdown
Contributor Author

Adding the spec text this rests on, since I should have quoted it up front.

"The list of elements with qualified name qualifiedName for a node root" picks its filter once, from whether root's node document is an HTML document, and the collection keeps that filter. The caching allowance right below it is the part jsdom was over-reaching:

When invoked with the same argument, and as long as root's node document's type has not changed, the same HTMLCollection object may be returned as returned by an earlier call.

astMetaCache aside, jsdom's memoizeQuery was returning the earlier collection after the document type had changed, which is what Element-getElementsByTagName-change-document-HTMLNess.html catches. Dropping the memoized queries during adopt keeps the reuse allowed by that sentence and stops it where the sentence stops.

Collections already handed out are deliberately left alone, which the same test checks in its first two assertions.

@Kjubikstronk

Copy link
Copy Markdown
Contributor Author

All three done — Object.create(null) in the adopt loop, the same in _clearMemoizedQueries() and in the NodeImpl constructor so the three sites agree, and a comment on why this doesn't go through _clearMemoizedQueries(): that walks up to the ancestors, and it's this subtree that changed document — the old parent's collections aren't affected by the move.

dom/, html/dom, custom-elements, shadow-dom and the API tests are all still green.

@domenic
domenic merged commit 52be38e into jsdom:main Aug 22, 2026
7 checks passed
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.

2 participants