mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 14:16:28 +03:00
Filter inactive linked CSS states
Keep valid empty pseudo-class matches authoritative and omit selector-less linked at-rules that cannot be tied to rendered nodes. AI assistance disclosure: Codex helped implement and test this fix under maintainer direction.
This commit is contained in:
@@ -1283,10 +1283,17 @@ if (IS_BROWSER) {
|
||||
function selectorNodesForLiveDom(root, selector) {
|
||||
const raw = String(selector || '').trim();
|
||||
if (!raw) return null;
|
||||
try {
|
||||
const exact = Array.from(root.querySelectorAll(raw));
|
||||
if (exact.length > 0) return exact;
|
||||
} catch { /* Dynamic/unsupported pseudos get the fallback below. */ }
|
||||
|
||||
const hasPseudoElement = (
|
||||
/::[a-zA-Z-]+(?:\([^)]*\))?|:(?:before|after|first-letter|first-line)\b/i.test(raw)
|
||||
);
|
||||
if (!hasPseudoElement) {
|
||||
// An empty result from a valid full selector is authoritative. In
|
||||
// particular, do not broaden inactive :hover/:focus/:not() rules to
|
||||
// their host element by stripping pseudo-classes.
|
||||
try { return Array.from(root.querySelectorAll(raw)); }
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
// Resolve pseudo-elements to their originating live elements. An attached
|
||||
// pseudo-element (`.card::before`) belongs to the element before it, while
|
||||
@@ -1296,9 +1303,8 @@ if (IS_BROWSER) {
|
||||
// invalid selector `main >` and makes absent hosts indistinguishable from
|
||||
// selectors the DOM API cannot parse.
|
||||
const fallback = raw
|
||||
.replace(/(^|[\s>+~,])::[a-zA-Z-]+(\([^)]*\))?/g, '$1*')
|
||||
.replace(/::[a-zA-Z-]+(\([^)]*\))?/g, '')
|
||||
.replace(/:[a-zA-Z-]+(\([^)]*\))?/g, '')
|
||||
.replace(/(^|[\s>+~,])(?:::[a-zA-Z-]+(?:\([^)]*\))?|:(?:before|after|first-letter|first-line)\b)/gi, '$1*')
|
||||
.replace(/::[a-zA-Z-]+(?:\([^)]*\))?|:(?:before|after|first-letter|first-line)\b/gi, '')
|
||||
.trim()
|
||||
.replace(/,\s*(?=,|$)/g, '');
|
||||
if (!fallback || /^[,\s]*$/.test(fallback)) return null;
|
||||
@@ -1408,7 +1414,10 @@ if (IS_BROWSER) {
|
||||
appendRules(nested, requiresAppliedMatch || isContainerCssRule(rule));
|
||||
continue;
|
||||
}
|
||||
if (cssText) parts.push(cssText);
|
||||
// Selector-less leaf at-rules (notably @keyframes) cannot be tied to a
|
||||
// rendered node. Their activating selector declarations are already
|
||||
// retained above, while admitting the leaf text would let unused or
|
||||
// conditionally inactive CSS create page-level findings.
|
||||
}
|
||||
};
|
||||
const appendSheet = (sheet) => {
|
||||
|
||||
@@ -8187,10 +8187,17 @@ if (IS_BROWSER) {
|
||||
function selectorNodesForLiveDom(root, selector) {
|
||||
const raw = String(selector || '').trim();
|
||||
if (!raw) return null;
|
||||
try {
|
||||
const exact = Array.from(root.querySelectorAll(raw));
|
||||
if (exact.length > 0) return exact;
|
||||
} catch { /* Dynamic/unsupported pseudos get the fallback below. */ }
|
||||
|
||||
const hasPseudoElement = (
|
||||
/::[a-zA-Z-]+(?:\([^)]*\))?|:(?:before|after|first-letter|first-line)\b/i.test(raw)
|
||||
);
|
||||
if (!hasPseudoElement) {
|
||||
// An empty result from a valid full selector is authoritative. In
|
||||
// particular, do not broaden inactive :hover/:focus/:not() rules to
|
||||
// their host element by stripping pseudo-classes.
|
||||
try { return Array.from(root.querySelectorAll(raw)); }
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
// Resolve pseudo-elements to their originating live elements. An attached
|
||||
// pseudo-element (`.card::before`) belongs to the element before it, while
|
||||
@@ -8200,9 +8207,8 @@ if (IS_BROWSER) {
|
||||
// invalid selector `main >` and makes absent hosts indistinguishable from
|
||||
// selectors the DOM API cannot parse.
|
||||
const fallback = raw
|
||||
.replace(/(^|[\s>+~,])::[a-zA-Z-]+(\([^)]*\))?/g, '$1*')
|
||||
.replace(/::[a-zA-Z-]+(\([^)]*\))?/g, '')
|
||||
.replace(/:[a-zA-Z-]+(\([^)]*\))?/g, '')
|
||||
.replace(/(^|[\s>+~,])(?:::[a-zA-Z-]+(?:\([^)]*\))?|:(?:before|after|first-letter|first-line)\b)/gi, '$1*')
|
||||
.replace(/::[a-zA-Z-]+(?:\([^)]*\))?|:(?:before|after|first-letter|first-line)\b/gi, '')
|
||||
.trim()
|
||||
.replace(/,\s*(?=,|$)/g, '');
|
||||
if (!fallback || /^[,\s]*$/.test(fallback)) return null;
|
||||
@@ -8312,7 +8318,10 @@ if (IS_BROWSER) {
|
||||
appendRules(nested, requiresAppliedMatch || isContainerCssRule(rule));
|
||||
continue;
|
||||
}
|
||||
if (cssText) parts.push(cssText);
|
||||
// Selector-less leaf at-rules (notably @keyframes) cannot be tied to a
|
||||
// rendered node. Their activating selector declarations are already
|
||||
// retained above, while admitting the leaf text would let unused or
|
||||
// conditionally inactive CSS create page-level findings.
|
||||
}
|
||||
};
|
||||
const appendSheet = (sheet) => {
|
||||
|
||||
@@ -1408,8 +1408,10 @@ describe('detectUrl — browser-only fixtures', () => {
|
||||
const containerBackgrounds = await linkedPage.evaluate(() => ({
|
||||
inactive: getComputedStyle(document.querySelector('.inactive-container-stripes')).backgroundImage,
|
||||
active: getComputedStyle(document.querySelector('.active-container-halo')).backgroundImage,
|
||||
pseudoClass: getComputedStyle(document.querySelector('.inactive-pseudo-stripes')).backgroundImage,
|
||||
}));
|
||||
assert.equal(containerBackgrounds.inactive, 'none');
|
||||
assert.equal(containerBackgrounds.pseudoClass, 'none');
|
||||
assert.match(containerBackgrounds.active, /radial-gradient/i);
|
||||
const grids = linkedFindings.filter(finding => finding.type === 'codex-grid-background');
|
||||
assert.equal(grids.length, 1, JSON.stringify({ linkedFindings, linkedCssom }));
|
||||
@@ -1421,7 +1423,16 @@ describe('detectUrl — browser-only fixtures', () => {
|
||||
JSON.stringify({ linkedFindings, linkedCssom }),
|
||||
);
|
||||
assert.equal(linkedFindings.some(finding => finding.type === 'radial-halo'), true);
|
||||
assert.equal(linkedFindings.some(finding => finding.type === 'repeating-stripes-gradient'), false);
|
||||
assert.equal(
|
||||
linkedFindings.some(finding => finding.type === 'repeating-stripes-gradient'),
|
||||
false,
|
||||
JSON.stringify({ linkedFindings, linkedCssom, containerBackgrounds }),
|
||||
);
|
||||
assert.equal(
|
||||
linkedFindings.some(finding => finding.type === 'gradient-text'),
|
||||
false,
|
||||
JSON.stringify({ linkedFindings, linkedCssom, containerBackgrounds }),
|
||||
);
|
||||
assert.equal(linkedFindings.some(finding => finding.type === 'layout-transition'), false);
|
||||
await linkedPage.close();
|
||||
|
||||
|
||||
@@ -45,6 +45,11 @@ main > ::before {
|
||||
}
|
||||
}
|
||||
|
||||
/* The host exists, but the complete pseudo-class selector is inactive. */
|
||||
.inactive-pseudo-stripes:not(.active) {
|
||||
background: repeating-linear-gradient(45deg, #eee, #eee 10px, #fafafa 10px, #fafafa 20px);
|
||||
}
|
||||
|
||||
.container-query-host {
|
||||
container-type: inline-size;
|
||||
width: 240px;
|
||||
@@ -66,6 +71,18 @@ main > ::before {
|
||||
}
|
||||
}
|
||||
|
||||
/* Non-selector at-rules in an inactive container must not enter page-level
|
||||
pattern scans just because their CSSOM text is readable. */
|
||||
@container (width > 2000px) {
|
||||
@keyframes inactive-container-gradient-text {
|
||||
from {
|
||||
background: linear-gradient(90deg, #111, #999);
|
||||
background-clip: text;
|
||||
}
|
||||
to { background: none; }
|
||||
}
|
||||
}
|
||||
|
||||
.unused-linked-transition {
|
||||
transition: width 200ms ease;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<div class="flag-linked-grid">Rendered decorative grid</div>
|
||||
<div class="inactive-media-stripes">Inactive media stripes</div>
|
||||
<div class="inactive-supports-stripes">Inactive supports stripes</div>
|
||||
<div class="inactive-pseudo-stripes active">Inactive pseudo-class stripes</div>
|
||||
<div class="container-query-host">
|
||||
<div class="inactive-container-stripes">Inactive container-query stripes</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user