|
|
|
let dynamicStyleSheet = null; |
|
|
|
let dynamicExtensionStyleSheet = null; |
|
|
|
|
|
|
|
|
|
|
|
const observer = new MutationObserver(mutations => { |
|
mutations.forEach(mutation => { |
|
if (mutation.type !== 'childList') return; |
|
|
|
mutation.addedNodes.forEach(node => { |
|
if (node instanceof HTMLLinkElement && node.tagName === 'LINK' && node.rel === 'stylesheet') { |
|
node.addEventListener('load', () => { |
|
try { |
|
applyDynamicFocusStyles(node.sheet); |
|
} catch (e) { |
|
console.warn('Failed to process new stylesheet:', e); |
|
} |
|
}); |
|
} |
|
}); |
|
}); |
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function applyDynamicFocusStyles(styleSheet, { fromExtension = false } = {}) { |
|
|
|
const hoverRules = []; |
|
|
|
const focusRules = new Set(); |
|
|
|
const PLACEHOLDER = ':__PLACEHOLDER__'; |
|
|
|
|
|
|
|
|
|
|
|
function processRules(rules) { |
|
Array.from(rules).forEach(rule => { |
|
if (rule instanceof CSSImportRule) { |
|
|
|
processImportedStylesheet(rule.styleSheet); |
|
} else if (rule instanceof CSSStyleRule) { |
|
|
|
const selectors = rule.selectorText.split(',').map(s => s.trim()); |
|
|
|
|
|
selectors.forEach(selector => { |
|
const isHover = selector.includes(':hover'), isFocus = selector.includes(':focus'); |
|
if (isHover && isFocus) { |
|
|
|
} |
|
else if (isHover) { |
|
const baseSelector = selector.replace(':hover', PLACEHOLDER).trim(); |
|
hoverRules.push({ baseSelector, rule }); |
|
} else if (isFocus) { |
|
|
|
const baseSelector = selector.replace(':focus-within', PLACEHOLDER).replace(':focus-visible', PLACEHOLDER).replace(':focus', PLACEHOLDER).trim(); |
|
focusRules.add(baseSelector); |
|
} |
|
}); |
|
} else if (rule instanceof CSSMediaRule || rule instanceof CSSSupportsRule) { |
|
|
|
processRules(rule.cssRules); |
|
} |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function processImportedStylesheet(sheet) { |
|
if (sheet && sheet.cssRules) { |
|
processRules(sheet.cssRules); |
|
} |
|
} |
|
|
|
processRules(styleSheet.cssRules); |
|
|
|
|
|
let targetStyleSheet = null; |
|
|
|
|
|
hoverRules.forEach(({ baseSelector, rule }) => { |
|
if (!focusRules.has(baseSelector)) { |
|
|
|
targetStyleSheet ??= getDynamicStyleSheet({ fromExtension }); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const focusSelector = rule.selectorText.replace(/:hover/g, ':focus-visible'); |
|
const focusRule = `${focusSelector} { ${rule.style.cssText} }`; |
|
|
|
try { |
|
targetStyleSheet.insertRule(focusRule, targetStyleSheet.cssRules.length); |
|
} catch (e) { |
|
console.warn('Failed to insert focus rule:', e); |
|
} |
|
} |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getDynamicStyleSheet({ fromExtension = false } = {}) { |
|
if (fromExtension) { |
|
if (!dynamicExtensionStyleSheet) { |
|
const styleSheetElement = document.createElement('style'); |
|
styleSheetElement.setAttribute('id', 'dynamic-extension-styles'); |
|
document.head.appendChild(styleSheetElement); |
|
dynamicExtensionStyleSheet = styleSheetElement.sheet; |
|
} |
|
return dynamicExtensionStyleSheet; |
|
} else { |
|
if (!dynamicStyleSheet) { |
|
const styleSheetElement = document.createElement('style'); |
|
styleSheetElement.setAttribute('id', 'dynamic-styles'); |
|
document.head.appendChild(styleSheetElement); |
|
dynamicStyleSheet = styleSheetElement.sheet; |
|
} |
|
return dynamicStyleSheet; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
export function initDynamicStyles() { |
|
|
|
observer.observe(document.head, { |
|
childList: true, |
|
subtree: true, |
|
}); |
|
|
|
|
|
Array.from(document.styleSheets).forEach(sheet => { |
|
try { |
|
applyDynamicFocusStyles(sheet, { fromExtension: sheet.href?.toLowerCase().includes('scripts/extensions') == true }); |
|
} catch (e) { |
|
console.warn('Failed to process stylesheet on initial load:', e); |
|
} |
|
}); |
|
} |
|
|