From e60d8121376cfa58e8e501dae7654ef764b0db2f Mon Sep 17 00:00:00 2001 From: Peron Date: Mon, 28 Sep 2026 22:26:23 +0800 Subject: [PATCH] perf: read class attribute directly in selector adapter Avoids materializing classList for every class lookup during matching, and fixes [class=""] matching elements without a class attribute. --- cjs/shared/matches.js | 2 +- esm/shared/matches.js | 2 +- test/html/document.js | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/cjs/shared/matches.js b/cjs/shared/matches.js index d00b0c50..e4deed74 100644 --- a/cjs/shared/matches.js +++ b/cjs/shared/matches.js @@ -17,7 +17,7 @@ const existsOne = (test, elements) => elements.some( ); const getAttributeValue = (element, name) => name === 'class' ? - element.classList.value : element.getAttribute(name); + (element.getAttributeNode('class')?.value ?? null) : element.getAttribute(name); const getChildren = ({childNodes}) => childNodes; diff --git a/esm/shared/matches.js b/esm/shared/matches.js index fb2217ef..dbc3339a 100644 --- a/esm/shared/matches.js +++ b/esm/shared/matches.js @@ -16,7 +16,7 @@ const existsOne = (test, elements) => elements.some( ); const getAttributeValue = (element, name) => name === 'class' ? - element.classList.value : element.getAttribute(name); + (element.getAttributeNode('class')?.value ?? null) : element.getAttribute(name); const getChildren = ({childNodes}) => childNodes; diff --git a/test/html/document.js b/test/html/document.js index 524d1361..b5e15062 100644 --- a/test/html/document.js +++ b/test/html/document.js @@ -85,6 +85,13 @@ assert((new DocumentFragment).baseURI, null); const {document: svg} = (new DOMParser).parseFromString('', 'text/html').defaultView.window; assert(svg.querySelector('[class*="foo-"]'), svg.firstElementChild); +const {document: emptyClass} = parseHTML('

'); +assert(emptyClass.querySelectorAll('[class=""]').map(({id}) => id).join(','), 'a,c', '[class=""] only matches empty class attributes'); +emptyClass.getElementById('a').removeAttribute('class'); +emptyClass.getElementById('b').className = 'x'; +emptyClass.getElementById('b').classList.remove('x'); +assert(emptyClass.querySelectorAll('[class=""]').map(({id}) => id).join(','), 'b,c', '[class=""] follows class mutations'); + assert((new DOMParser).parseFromString('...', 'text/html').firstElementChild.localName, 'html'); const issue187 = (new DOMParser).parseFromString(``, 'text/html').defaultView.window.document;