From 1902ef03c2fb7f4804e15007393f948a5a29ca34 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Wed, 2 Sep 2026 12:20:53 -0700 Subject: [PATCH] Resolve effective linked keyframes AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction. --- cli/engine/browser/injected/index.mjs | 60 +++++++++++++++---- cli/engine/detect-antipatterns-browser.js | 60 +++++++++++++++---- tests/detect-antipatterns-browser.test.mjs | 29 ++++++++- .../antipatterns/linked-url-patterns.css | 20 +++++++ .../antipatterns/linked-url-patterns.html | 1 + 5 files changed, 149 insertions(+), 21 deletions(-) diff --git a/cli/engine/browser/injected/index.mjs b/cli/engine/browser/injected/index.mjs index dd7467fbc..77e805e90 100644 --- a/cli/engine/browser/injected/index.mjs +++ b/cli/engine/browser/injected/index.mjs @@ -1520,6 +1520,41 @@ if (IS_BROWSER) { return normalizeAnimationName(rule?.name || match?.[1] || ''); } + function cssPropertyName(property) { + if (property.startsWith('--')) return property; + return property.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`); + } + + function resolvedAnimationKeyframes(candidateNames) { + if (typeof document.getAnimations !== 'function') return null; + let animations; + try { animations = document.getAnimations(); } + catch { return null; } + + const resolved = new Map(); + const metadata = new Set(['offset', 'computedOffset', 'easing', 'composite']); + for (const animation of animations) { + const name = normalizeAnimationName(animation?.animationName || ''); + if (!name || !candidateNames.has(name) || resolved.has(name)) continue; + let frames; + try { frames = animation.effect?.getKeyframes?.() || []; } + catch { continue; } + const blocks = []; + for (const frame of frames) { + const rawOffset = Number.isFinite(frame.computedOffset) ? frame.computedOffset : frame.offset; + if (!Number.isFinite(rawOffset)) continue; + const offset = Math.round(rawOffset * 1000000) / 10000; + const declarations = Object.entries(frame) + .filter(([property, value]) => !metadata.has(property) && value != null && value !== '') + .map(([property, value]) => `${cssPropertyName(property)}: ${value};`); + if (declarations.length === 0) continue; + blocks.push(`${offset}% { ${declarations.join(' ')} }`); + } + if (blocks.length > 0) resolved.set(name, `@keyframes ${name} { ${blocks.join(' ')} }`); + } + return resolved; + } + // Read CSS that is absent from document.outerHTML. Inline