Read the class and the id as properties, and compare the id - #194
Open
jdalton wants to merge 1 commit into
Open
Conversation
Two of the tests the resolvers run per candidate ask the host for an attribute where the same value is reflected as a property, and one of them matches a pattern where the selector means an exact comparison.
A class test calls getAttribute('class'). The class attribute is reflected as Element.className, and reading a property is cheaper than calling through the host: 0.477ms against 0.851ms over 6344 elements in jsdom. The reflection is a string on an HTML element and an SVGAnimatedString on an SVG one, which SVG 1.1 defined and SVG 2 deprecated without removing, so classOf() checks the type and asks for the attribute when it is not a string. Reading it without that check matches the class against '[object SVGAnimatedString]' and quietly finds nothing.
An id test compiles to a regular expression over getAttribute('id'), where the selector asks whether the id equals one string. Comparing e.id measures 0.383ms against 0.717ms on the same document. The escapes have to survive the change, since a comparison holds a string where the pattern held a pattern, so the value goes through escapeIdentifier the way an attribute value already does.
Browsers do both: Blink matches a class against a parsed token list and compares an id for equality rather than matching it.
References:
- Spec: https://dom.spec.whatwg.org/#dom-element-classname — className reflects the class attribute
- Spec: https://dom.spec.whatwg.org/#dom-element-id — and id reflects the id attribute
- Spec: https://svgwg.org/svg2-draft/types.html#__svg__SVGElement__className — the SVG reflection that is not a string, deprecated but still shipping
- Chromium: https://github.com/chromium/chromium/blob/155.0.8041.1/third_party/blink/renderer/core/css/selector_checker.cc#L1532 — a class is matched against a parsed token list, not a string scan
- Chromium: https://github.com/chromium/chromium/blob/155.0.8041.1/third_party/blink/renderer/core/css/selector_checker.cc#L1536 — an id is compared for equality
- MDN: https://developer.mozilla.org/en-US/docs/Web/API/Element/className
jdalton
force-pushed
the
perf/property-reads
branch
from
September 5, 2026 02:47
a8537b4 to
04dd913
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two of the tests a compiled resolver runs per candidate ask the host for an attribute whose value is already reflected as a property, and one of them matches a pattern where the selector means an exact comparison.
What the two tests do today, what they measure, and where nothing changes
A class test calls
getAttribute('class'), which is a call through the host whereElement.classNameis a property read. An id test compiles to a regular expression overgetAttribute('id'), where the selector is asking whether the id equals one string.Interleaved against master in one process, on the 6344-element fixture in
test/speed/example/selectors.html:div.example > p#title p.example a.examplediv.exampleThe last two rows are the point of the table as much as the first three.
collect()fetches candidates by the last simple token of the selector, so for.examplethe class is what the fetch asked the host for and no resolver tests it afterwards. The gain only appears where a class or an id is tested per candidate, which is when it is not the token the fetch used.The per-read costs behind that, one read per element over the same document:
e.className0.477 ms againstgetAttribute('class')0.851 ms, and comparinge.id0.383 ms against a pattern overgetAttribute('id')0.717 ms.The SVG reflection, which is why the class read is not a one-liner
Element.classNameis a string, except on an SVG element, where SVG 1.1 definedSVGElement.classNameas anSVGAnimatedStringand SVG 2 deprecated it without removing it. Chrome, Safari, Firefox and jsdom all still expose it.Reading it without checking the type matches the class against
[object SVGAnimatedString]and quietly finds nothing, soclassOf()checks the type, readsbaseValwhen it is there, and asks for the attribute otherwise. Eleven selectors covering classes, escaped ids, SVG classes on<svg>and<rect>, and a negation answer the same as the reference engine before and after this patch.How this sits with the other patches in the series
It applies to master on its own. The seventeen patches extracted from #167 were checked by cherry-picking them onto master one after another, in PR order and in reverse, and all seventeen land without a conflict — the helper here sits next to
byClass(), which no other patch anchors on, and its export goes to a spaced-out key in the snapshot list.