Fix linked CSS selector filtering

Resolve pseudo-element selectors to live hosts, reject unresolvable linked CSS findings, and make the regression assertions independent. Also ignore comment delimiters when recovering CSS rule selectors.

AI assistance disclosure: This commit was prepared with Codex under maintainer direction.
This commit is contained in:
Paul Bakaus
2026-09-02 09:51:02 -07:00
parent 0fa4202ca9
commit 95b28ed6d7
6 changed files with 53 additions and 31 deletions
+16 -7
View File
@@ -1287,8 +1287,18 @@ if (IS_BROWSER) {
const exact = Array.from(root.querySelectorAll(raw));
if (exact.length > 0) return exact;
} catch { /* Dynamic/unsupported pseudos get the fallback below. */ }
// Resolve pseudo-elements to their originating live elements. An attached
// pseudo-element (`.card::before`) belongs to the element before it, while
// a hostless pseudo-element after a combinator (`main > ::before`) belongs
// to a matching element at that position (`main > *`). Replacing every
// pseudo indiscriminately with an empty string leaves the latter as the
// invalid selector `main >` and makes absent hosts indistinguishable from
// selectors the DOM API cannot parse.
const fallback = raw
.replace(/::?[a-zA-Z-]+(\([^)]*\))?/g, '')
.replace(/(^|[\s>+~,])::[a-zA-Z-]+(\([^)]*\))?/g, '$1*')
.replace(/::[a-zA-Z-]+(\([^)]*\))?/g, '')
.replace(/:[a-zA-Z-]+(\([^)]*\))?/g, '')
.trim()
.replace(/,\s*(?=,|$)/g, '');
if (!fallback || /^[,\s]*$/.test(fallback)) return null;
@@ -1333,11 +1343,10 @@ if (IS_BROWSER) {
const cssText = rule.cssText || '';
if (rule.selectorText) {
const matches = selectorNodesForLiveDom(document, rule.selectorText);
// A null result means the DOM selector API cannot resolve the CSSOM
// selector (commonly a hostless pseudo-element), not that it is
// unused. Keep those valid stylesheet rules; only a definitive empty
// result proves that no live element can receive the declaration.
if (matches === null || matches.length > 0) parts.push(cssText);
// Only declarations with a resolvable live host enter the corpus.
// Unresolvable selectors are uncertain, not evidence that a pattern
// rendered, and retaining them would leak unused CSS into findings.
if (matches?.length > 0) parts.push(cssText);
continue;
}
let nested = [];
@@ -1758,7 +1767,7 @@ if (IS_BROWSER) {
const scopedHtmlFindings = checkHtmlPatterns(html, corpora).filter(f => {
if (!f.selector) return true;
const matches = selectorNodesForLiveDom(document, f.selector);
if (!matches) return true;
if (!matches) return false;
if (matches.length === 0) return false;
return matches.some(el => !scopedIgnoreActive(el, f.id));
});