(function () { "use strict"; var MessageTypeUItoBG; (function (MessageTypeUItoBG) { MessageTypeUItoBG["GET_DATA"] = "ui-bg-get-data"; MessageTypeUItoBG["GET_DEVTOOLS_DATA"] = "ui-bg-get-devtools-data"; MessageTypeUItoBG["SUBSCRIBE_TO_CHANGES"] = "ui-bg-subscribe-to-changes"; MessageTypeUItoBG["UNSUBSCRIBE_FROM_CHANGES"] = "ui-bg-unsubscribe-from-changes"; MessageTypeUItoBG["CHANGE_SETTINGS"] = "ui-bg-change-settings"; MessageTypeUItoBG["SET_THEME"] = "ui-bg-set-theme"; MessageTypeUItoBG["TOGGLE_ACTIVE_TAB"] = "ui-bg-toggle-active-tab"; MessageTypeUItoBG["MARK_NEWS_AS_READ"] = "ui-bg-mark-news-as-read"; MessageTypeUItoBG["MARK_NEWS_AS_DISPLAYED"] = "ui-bg-mark-news-as-displayed"; MessageTypeUItoBG["LOAD_CONFIG"] = "ui-bg-load-config"; MessageTypeUItoBG["APPLY_DEV_DYNAMIC_THEME_FIXES"] = "ui-bg-apply-dev-dynamic-theme-fixes"; MessageTypeUItoBG["RESET_DEV_DYNAMIC_THEME_FIXES"] = "ui-bg-reset-dev-dynamic-theme-fixes"; MessageTypeUItoBG["APPLY_DEV_INVERSION_FIXES"] = "ui-bg-apply-dev-inversion-fixes"; MessageTypeUItoBG["RESET_DEV_INVERSION_FIXES"] = "ui-bg-reset-dev-inversion-fixes"; MessageTypeUItoBG["APPLY_DEV_STATIC_THEMES"] = "ui-bg-apply-dev-static-themes"; MessageTypeUItoBG["RESET_DEV_STATIC_THEMES"] = "ui-bg-reset-dev-static-themes"; MessageTypeUItoBG["COLOR_SCHEME_CHANGE"] = "ui-bg-color-scheme-change"; MessageTypeUItoBG["HIDE_HIGHLIGHTS"] = "ui-bg-hide-highlights"; })(MessageTypeUItoBG || (MessageTypeUItoBG = {})); var MessageTypeBGtoUI; (function (MessageTypeBGtoUI) { MessageTypeBGtoUI["CHANGES"] = "bg-ui-changes"; })(MessageTypeBGtoUI || (MessageTypeBGtoUI = {})); var DebugMessageTypeBGtoUI; (function (DebugMessageTypeBGtoUI) { DebugMessageTypeBGtoUI["CSS_UPDATE"] = "debug-bg-ui-css-update"; DebugMessageTypeBGtoUI["UPDATE"] = "debug-bg-ui-update"; })(DebugMessageTypeBGtoUI || (DebugMessageTypeBGtoUI = {})); var MessageTypeBGtoCS; (function (MessageTypeBGtoCS) { MessageTypeBGtoCS["ADD_CSS_FILTER"] = "bg-cs-add-css-filter"; MessageTypeBGtoCS["ADD_DYNAMIC_THEME"] = "bg-cs-add-dynamic-theme"; MessageTypeBGtoCS["ADD_STATIC_THEME"] = "bg-cs-add-static-theme"; MessageTypeBGtoCS["ADD_SVG_FILTER"] = "bg-cs-add-svg-filter"; MessageTypeBGtoCS["CLEAN_UP"] = "bg-cs-clean-up"; MessageTypeBGtoCS["FETCH_RESPONSE"] = "bg-cs-fetch-response"; MessageTypeBGtoCS["UNSUPPORTED_SENDER"] = "bg-cs-unsupported-sender"; })(MessageTypeBGtoCS || (MessageTypeBGtoCS = {})); var DebugMessageTypeBGtoCS; (function (DebugMessageTypeBGtoCS) { DebugMessageTypeBGtoCS["RELOAD"] = "debug-bg-cs-reload"; })(DebugMessageTypeBGtoCS || (DebugMessageTypeBGtoCS = {})); var MessageTypeCStoBG; (function (MessageTypeCStoBG) { MessageTypeCStoBG["COLOR_SCHEME_CHANGE"] = "cs-bg-color-scheme-change"; MessageTypeCStoBG["DARK_THEME_DETECTED"] = "cs-bg-dark-theme-detected"; MessageTypeCStoBG["DARK_THEME_NOT_DETECTED"] = "cs-bg-dark-theme-not-detected"; MessageTypeCStoBG["FETCH"] = "cs-bg-fetch"; MessageTypeCStoBG["DOCUMENT_CONNECT"] = "cs-bg-document-connect"; MessageTypeCStoBG["DOCUMENT_FORGET"] = "cs-bg-document-forget"; MessageTypeCStoBG["DOCUMENT_FREEZE"] = "cs-bg-document-freeze"; MessageTypeCStoBG["DOCUMENT_RESUME"] = "cs-bg-document-resume"; })(MessageTypeCStoBG || (MessageTypeCStoBG = {})); var DebugMessageTypeCStoBG; (function (DebugMessageTypeCStoBG) { DebugMessageTypeCStoBG["LOG"] = "debug-cs-bg-log"; })(DebugMessageTypeCStoBG || (DebugMessageTypeCStoBG = {})); var MessageTypeCStoUI; (function (MessageTypeCStoUI) { MessageTypeCStoUI["EXPORT_CSS_RESPONSE"] = "cs-ui-export-css-response"; })(MessageTypeCStoUI || (MessageTypeCStoUI = {})); var MessageTypeUItoCS; (function (MessageTypeUItoCS) { MessageTypeUItoCS["EXPORT_CSS"] = "ui-cs-export-css"; })(MessageTypeUItoCS || (MessageTypeUItoCS = {})); function logInfo(...args) {} function logWarn(...args) {} function logInfoCollapsed(title, ...args) {} function throttle(callback) { let pending = false; let frameId = null; let lastArgs; const throttled = (...args) => { lastArgs = args; if (frameId) { pending = true; } else { callback(...lastArgs); frameId = requestAnimationFrame(() => { frameId = null; if (pending) { callback(...lastArgs); pending = false; } }); } }; const cancel = () => { cancelAnimationFrame(frameId); pending = false; frameId = null; }; return Object.assign(throttled, {cancel}); } function createAsyncTasksQueue() { const tasks = []; let frameId = null; function runTasks() { let task; while ((task = tasks.shift())) { task(); } frameId = null; } function add(task) { tasks.push(task); if (!frameId) { frameId = requestAnimationFrame(runTasks); } } function cancel() { tasks.splice(0); cancelAnimationFrame(frameId); frameId = null; } return {add, cancel}; } function isArrayLike(items) { return items.length != null; } function forEach(items, iterator) { if (isArrayLike(items)) { for (let i = 0, len = items.length; i < len; i++) { iterator(items[i]); } } else { for (const item of items) { iterator(item); } } } function push(array, addition) { forEach(addition, (a) => array.push(a)); } function toArray(items) { const results = []; for (let i = 0, len = items.length; i < len; i++) { results.push(items[i]); } return results; } function getDuration(time) { let duration = 0; if (time.seconds) { duration += time.seconds * 1000; } if (time.minutes) { duration += time.minutes * 60 * 1000; } if (time.hours) { duration += time.hours * 60 * 60 * 1000; } if (time.days) { duration += time.days * 24 * 60 * 60 * 1000; } return duration; } function createNodeAsap({ selectNode, createNode, updateNode, selectTarget, createTarget, isTargetMutation }) { const target = selectTarget(); if (target) { const prev = selectNode(); if (prev) { updateNode(prev); } else { createNode(target); } } else { const observer = new MutationObserver((mutations) => { const mutation = mutations.find(isTargetMutation); if (mutation) { unsubscribe(); const target = selectTarget(); selectNode() || createNode(target); } }); const ready = () => { if (document.readyState !== "complete") { return; } unsubscribe(); const target = selectTarget() || createTarget(); selectNode() || createNode(target); }; const unsubscribe = () => { document.removeEventListener("readystatechange", ready); observer.disconnect(); }; if (document.readyState === "complete") { ready(); } else { document.addEventListener("readystatechange", ready); observer.observe(document, {childList: true, subtree: true}); } } } function removeNode(node) { node && node.parentNode && node.parentNode.removeChild(node); } function watchForNodePosition(node, mode, onRestore = Function.prototype) { const MAX_ATTEMPTS_COUNT = 10; const RETRY_TIMEOUT = getDuration({seconds: 2}); const ATTEMPTS_INTERVAL = getDuration({seconds: 10}); const prevSibling = node.previousSibling; let parent = node.parentNode; if (!parent) { throw new Error( "Unable to watch for node position: parent element not found" ); } if (mode === "prev-sibling" && !prevSibling) { throw new Error( "Unable to watch for node position: there is no previous sibling" ); } let attempts = 0; let start = null; let timeoutId = null; const restore = throttle(() => { if (timeoutId) { return; } attempts++; const now = Date.now(); if (start == null) { start = now; } else if (attempts >= MAX_ATTEMPTS_COUNT) { if (now - start < ATTEMPTS_INTERVAL) { timeoutId = setTimeout(() => { start = null; attempts = 0; timeoutId = null; restore(); }, RETRY_TIMEOUT); return; } start = now; attempts = 1; } if (mode === "head") { if (prevSibling && prevSibling.parentNode !== parent) { stop(); return; } } if (mode === "prev-sibling") { if (prevSibling.parentNode == null) { stop(); return; } if (prevSibling.parentNode !== parent) { updateParent(prevSibling.parentNode); } } if (mode === "head" && !parent.isConnected) { parent = document.head; } parent.insertBefore( node, prevSibling && prevSibling.isConnected ? prevSibling.nextSibling : parent.firstChild ); observer.takeRecords(); onRestore && onRestore(); }); const observer = new MutationObserver(() => { if ( (mode === "head" && (node.parentNode !== parent || !node.parentNode.isConnected)) || (mode === "prev-sibling" && node.previousSibling !== prevSibling) ) { restore(); } }); const run = () => { observer.observe(parent, {childList: true}); }; const stop = () => { clearTimeout(timeoutId); observer.disconnect(); restore.cancel(); }; const skip = () => { observer.takeRecords(); }; const updateParent = (parentNode) => { parent = parentNode; stop(); run(); }; run(); return {run, stop, skip}; } function iterateShadowHosts(root, iterator) { if (root == null) { return; } const walker = document.createTreeWalker( root, NodeFilter.SHOW_ELEMENT, { acceptNode(node) { return node.shadowRoot == null ? NodeFilter.FILTER_SKIP : NodeFilter.FILTER_ACCEPT; } } ); for ( let node = root.shadowRoot ? walker.currentNode : walker.nextNode(); node != null; node = walker.nextNode() ) { if (node.classList.contains("surfingkeys_hints_host")) { continue; } iterator(node); iterateShadowHosts(node.shadowRoot, iterator); } } let isDOMReady = () => { return ( document.readyState === "complete" || document.readyState === "interactive" ); }; function setIsDOMReady(newFunc) { isDOMReady = newFunc; } const readyStateListeners = new Set(); function addDOMReadyListener(listener) { isDOMReady() ? listener() : readyStateListeners.add(listener); } function removeDOMReadyListener(listener) { readyStateListeners.delete(listener); } function isReadyStateComplete() { return document.readyState === "complete"; } const readyStateCompleteListeners = new Set(); function addReadyStateCompleteListener(listener) { isReadyStateComplete() ? listener() : readyStateCompleteListeners.add(listener); } function cleanReadyStateCompleteListeners() { readyStateCompleteListeners.clear(); } if (!isDOMReady()) { const onReadyStateChange = () => { if (isDOMReady()) { readyStateListeners.forEach((listener) => listener()); readyStateListeners.clear(); if (isReadyStateComplete()) { document.removeEventListener( "readystatechange", onReadyStateChange ); readyStateCompleteListeners.forEach((listener) => listener() ); readyStateCompleteListeners.clear(); } } }; document.addEventListener("readystatechange", onReadyStateChange); } const HUGE_MUTATIONS_COUNT = 1000; function isHugeMutation(mutations) { if (mutations.length > HUGE_MUTATIONS_COUNT) { return true; } let addedNodesCount = 0; for (let i = 0; i < mutations.length; i++) { addedNodesCount += mutations[i].addedNodes.length; if (addedNodesCount > HUGE_MUTATIONS_COUNT) { return true; } } return false; } function getElementsTreeOperations(mutations) { const additions = new Set(); const deletions = new Set(); const moves = new Set(); mutations.forEach((m) => { forEach(m.addedNodes, (n) => { if (n instanceof Element && n.isConnected) { additions.add(n); } }); forEach(m.removedNodes, (n) => { if (n instanceof Element) { if (n.isConnected) { moves.add(n); additions.delete(n); } else { deletions.add(n); } } }); }); const duplicateAdditions = []; const duplicateDeletions = []; additions.forEach((node) => { if (additions.has(node.parentElement)) { duplicateAdditions.push(node); } }); deletions.forEach((node) => { if (deletions.has(node.parentElement)) { duplicateDeletions.push(node); } }); duplicateAdditions.forEach((node) => additions.delete(node)); duplicateDeletions.forEach((node) => deletions.delete(node)); return {additions, moves, deletions}; } const optimizedTreeObservers = new Map(); const optimizedTreeCallbacks = new WeakMap(); function createOptimizedTreeObserver(root, callbacks) { let observer; let observerCallbacks; let domReadyListener; if (optimizedTreeObservers.has(root)) { observer = optimizedTreeObservers.get(root); observerCallbacks = optimizedTreeCallbacks.get(observer); } else { let hadHugeMutationsBefore = false; let subscribedForReadyState = false; observer = new MutationObserver((mutations) => { if (isHugeMutation(mutations)) { if (!hadHugeMutationsBefore || isDOMReady()) { observerCallbacks.forEach(({onHugeMutations}) => onHugeMutations(root) ); } else if (!subscribedForReadyState) { domReadyListener = () => observerCallbacks.forEach(({onHugeMutations}) => onHugeMutations(root) ); addDOMReadyListener(domReadyListener); subscribedForReadyState = true; } hadHugeMutationsBefore = true; } else { const elementsOperations = getElementsTreeOperations(mutations); observerCallbacks.forEach(({onMinorMutations}) => onMinorMutations(root, elementsOperations) ); } }); observer.observe(root, {childList: true, subtree: true}); optimizedTreeObservers.set(root, observer); observerCallbacks = new Set(); optimizedTreeCallbacks.set(observer, observerCallbacks); } observerCallbacks.add(callbacks); return { disconnect() { observerCallbacks.delete(callbacks); if (domReadyListener) { removeDOMReadyListener(domReadyListener); } if (observerCallbacks.size === 0) { observer.disconnect(); optimizedTreeCallbacks.delete(observer); optimizedTreeObservers.delete(root); } } }; } function createOrUpdateStyle$1(css, type) { createNodeAsap({ selectNode: () => document.getElementById("dark-reader-style"), createNode: (target) => { document.documentElement.setAttribute( "data-darkreader-mode", type ); const style = document.createElement("style"); style.id = "dark-reader-style"; style.classList.add("darkreader"); style.type = "text/css"; style.textContent = css; target.appendChild(style); }, updateNode: (existing) => { if ( css.replace(/^\s+/gm, "") !== existing.textContent.replace(/^\s+/gm, "") ) { existing.textContent = css; } }, selectTarget: () => document.head, createTarget: () => { const head = document.createElement("head"); document.documentElement.insertBefore( head, document.documentElement.firstElementChild ); return head; }, isTargetMutation: (mutation) => mutation.target.nodeName.toLowerCase() === "head" }); } function removeStyle() { removeNode(document.getElementById("dark-reader-style")); document.documentElement.removeAttribute("data-darkreader-mode"); } function createOrUpdateSVGFilter(svgMatrix, svgReverseMatrix) { createNodeAsap({ selectNode: () => document.getElementById("dark-reader-svg"), createNode: (target) => { const SVG_NS = "http://www.w3.org/2000/svg"; const createMatrixFilter = (id, matrix) => { const filter = document.createElementNS(SVG_NS, "filter"); filter.id = id; filter.style.colorInterpolationFilters = "sRGB"; filter.setAttribute("x", "0"); filter.setAttribute("y", "0"); filter.setAttribute("width", "99999"); filter.setAttribute("height", "99999"); filter.appendChild(createColorMatrix(matrix)); return filter; }; const createColorMatrix = (matrix) => { const colorMatrix = document.createElementNS( SVG_NS, "feColorMatrix" ); colorMatrix.setAttribute("type", "matrix"); colorMatrix.setAttribute("values", matrix); return colorMatrix; }; const svg = document.createElementNS(SVG_NS, "svg"); svg.id = "dark-reader-svg"; svg.style.height = "0"; svg.style.width = "0"; svg.appendChild( createMatrixFilter("dark-reader-filter", svgMatrix) ); svg.appendChild( createMatrixFilter( "dark-reader-reverse-filter", svgReverseMatrix ) ); target.appendChild(svg); }, updateNode: (existing) => { const existingMatrix = existing.firstChild.firstChild; if (existingMatrix.getAttribute("values") !== svgMatrix) { existingMatrix.setAttribute("values", svgMatrix); const style = document.getElementById("dark-reader-style"); const css = style.textContent; style.textContent = ""; style.textContent = css; } }, selectTarget: () => document.head, createTarget: () => { const head = document.createElement("head"); document.documentElement.insertBefore( head, document.documentElement.firstElementChild ); return head; }, isTargetMutation: (mutation) => mutation.target.nodeName.toLowerCase() === "head" }); } function removeSVGFilter() { removeNode(document.getElementById("dark-reader-svg")); } function evalMath(expression) { const rpnStack = []; const workingStack = []; let lastToken; for (let i = 0, len = expression.length; i < len; i++) { const token = expression[i]; if (!token || token === " ") { continue; } if (operators.has(token)) { const op = operators.get(token); while (workingStack.length) { const currentOp = operators.get(workingStack[0]); if (!currentOp) { break; } if (op.lessOrEqualThan(currentOp)) { rpnStack.push(workingStack.shift()); } else { break; } } workingStack.unshift(token); } else if (!lastToken || operators.has(lastToken)) { rpnStack.push(token); } else { rpnStack[rpnStack.length - 1] += token; } lastToken = token; } rpnStack.push(...workingStack); const stack = []; for (let i = 0, len = rpnStack.length; i < len; i++) { const op = operators.get(rpnStack[i]); if (op) { const args = stack.splice(0, 2); stack.push(op.exec(args[1], args[0])); } else { stack.unshift(parseFloat(rpnStack[i])); } } return stack[0]; } class Operator { precendce; execMethod; constructor(precedence, method) { this.precendce = precedence; this.execMethod = method; } exec(left, right) { return this.execMethod(left, right); } lessOrEqualThan(op) { return this.precendce <= op.precendce; } } const operators = new Map([ ["+", new Operator(1, (left, right) => left + right)], ["-", new Operator(1, (left, right) => left - right)], ["*", new Operator(2, (left, right) => left * right)], ["/", new Operator(2, (left, right) => left / right)] ]); function getMatches(regex, input, group = 0) { const matches = []; let m; while ((m = regex.exec(input))) { matches.push(m[group]); } return matches; } function getHashCode(text) { const len = text.length; let hash = 0; for (let i = 0; i < len; i++) { const c = text.charCodeAt(i); hash = ((hash << 5) - hash + c) & 4294967295; } return hash; } function escapeRegExpSpecialChars(input) { return input.replaceAll(/[\^$.*+?\(\)\[\]{}|\-\\]/g, "\\$&"); } function getParenthesesRange(input, searchStartIndex = 0) { return getOpenCloseRange(input, searchStartIndex, "(", ")", []); } function getOpenCloseRange( input, searchStartIndex, openToken, closeToken, excludeRanges ) { let indexOf; if (excludeRanges.length === 0) { indexOf = (token, pos) => input.indexOf(token, pos); } else { indexOf = (token, pos) => indexOfExcluding(input, token, pos, excludeRanges); } const {length} = input; let depth = 0; let firstOpenIndex = -1; for (let i = searchStartIndex; i < length; i++) { if (depth === 0) { const openIndex = indexOf(openToken, i); if (openIndex < 0) { break; } firstOpenIndex = openIndex; depth++; i = openIndex; } else { const closeIndex = indexOf(closeToken, i); if (closeIndex < 0) { break; } const openIndex = indexOf(openToken, i); if (openIndex < 0 || closeIndex <= openIndex) { depth--; if (depth === 0) { return {start: firstOpenIndex, end: closeIndex + 1}; } i = closeIndex; } else { depth++; i = openIndex; } } } return null; } function indexOfExcluding(input, search, position, excludeRanges) { const i = input.indexOf(search, position); const exclusion = excludeRanges.find((r) => i >= r.start && i < r.end); if (exclusion) { return indexOfExcluding( input, search, exclusion.end, excludeRanges ); } return i; } function splitExcluding(input, separator, excludeRanges) { const parts = []; let commaIndex = -1; let currIndex = 0; while ( (commaIndex = indexOfExcluding( input, separator, currIndex, excludeRanges )) >= 0 ) { parts.push(input.substring(currIndex, commaIndex).trim()); currIndex = commaIndex + 1; } parts.push(input.substring(currIndex).trim()); return parts; } const isNavigatorDefined = typeof navigator !== "undefined"; const userAgent = isNavigatorDefined ? navigator.userAgentData && Array.isArray(navigator.userAgentData.brands) ? navigator.userAgentData.brands .map( (brand) => `${brand.brand.toLowerCase()} ${brand.version}` ) .join(" ") : navigator.userAgent.toLowerCase() : "some useragent"; const platform = isNavigatorDefined ? navigator.userAgentData && typeof navigator.userAgentData.platform === "string" ? navigator.userAgentData.platform.toLowerCase() : navigator.platform.toLowerCase() : "some platform"; userAgent.includes("vivaldi"); userAgent.includes("yabrowser"); userAgent.includes("opr") || userAgent.includes("opera"); userAgent.includes("edg"); platform.startsWith("win"); platform.startsWith("mac"); isNavigatorDefined && navigator.userAgentData ? navigator.userAgentData.mobile : userAgent.includes("mobile"); const isShadowDomSupported = typeof ShadowRoot === "function"; const isLayerRuleSupported = typeof CSSLayerBlockRule === "function"; (isNavigatorDefined && navigator.userAgentData && ["Linux", "Android"].includes(navigator.userAgentData.platform)) || platform.startsWith("linux"); (() => { const m = userAgent.match(/chrom(?:e|ium)(?:\/| )([^ ]+)/); if (m && m[1]) { return m[1]; } return ""; })(); (() => { const m = userAgent.match(/(?:firefox|librewolf)(?:\/| )([^ ]+)/); if (m && m[1]) { return m[1]; } return ""; })(); const isDefinedSelectorSupported = (() => { try { document.querySelector(":defined"); return true; } catch (err) { return false; } })(); let query = null; const onChange = ({matches}) => listeners.forEach((listener) => listener(matches)); const listeners = new Set(); function runColorSchemeChangeDetector(callback) { listeners.add(callback); if (query) { return; } query = matchMedia("(prefers-color-scheme: dark)"); { query.addEventListener("change", onChange); } } function stopColorSchemeChangeDetector() { if (!query || !onChange) { return; } { query.removeEventListener("change", onChange); } listeners.clear(); query = null; } const isSystemDarkModeEnabled = () => (query || matchMedia("(prefers-color-scheme: dark)")).matches; const hslaParseCache = new Map(); const rgbaParseCache = new Map(); function parseColorWithCache($color) { $color = $color.trim(); if (rgbaParseCache.has($color)) { return rgbaParseCache.get($color); } if ($color.includes("calc(")) { $color = lowerCalcExpression($color); } const color = parse($color); color && rgbaParseCache.set($color, color); return color; } function parseToHSLWithCache(color) { if (hslaParseCache.has(color)) { return hslaParseCache.get(color); } const rgb = parseColorWithCache(color); if (!rgb) { return null; } const hsl = rgbToHSL(rgb); hslaParseCache.set(color, hsl); return hsl; } function clearColorCache() { hslaParseCache.clear(); rgbaParseCache.clear(); } function hslToRGB({h, s, l, a = 1}) { if (s === 0) { const [r, b, g] = [l, l, l].map((x) => Math.round(x * 255)); return {r, g, b, a}; } const c = (1 - Math.abs(2 * l - 1)) * s; const x = c * (1 - Math.abs(((h / 60) % 2) - 1)); const m = l - c / 2; const [r, g, b] = ( h < 60 ? [c, x, 0] : h < 120 ? [x, c, 0] : h < 180 ? [0, c, x] : h < 240 ? [0, x, c] : h < 300 ? [x, 0, c] : [c, 0, x] ).map((n) => Math.round((n + m) * 255)); return {r, g, b, a}; } function rgbToHSL({r: r255, g: g255, b: b255, a = 1}) { const r = r255 / 255; const g = g255 / 255; const b = b255 / 255; const max = Math.max(r, g, b); const min = Math.min(r, g, b); const c = max - min; const l = (max + min) / 2; if (c === 0) { return {h: 0, s: 0, l, a}; } let h = (max === r ? ((g - b) / c) % 6 : max === g ? (b - r) / c + 2 : (r - g) / c + 4) * 60; if (h < 0) { h += 360; } const s = c / (1 - Math.abs(2 * l - 1)); return {h, s, l, a}; } function toFixed(n, digits = 0) { const fixed = n.toFixed(digits); if (digits === 0) { return fixed; } const dot = fixed.indexOf("."); if (dot >= 0) { const zerosMatch = fixed.match(/0+$/); if (zerosMatch) { if (zerosMatch.index === dot + 1) { return fixed.substring(0, dot); } return fixed.substring(0, zerosMatch.index); } } return fixed; } function rgbToString(rgb) { const {r, g, b, a} = rgb; if (a != null && a < 1) { return `rgba(${toFixed(r)}, ${toFixed(g)}, ${toFixed(b)}, ${toFixed(a, 2)})`; } return `rgb(${toFixed(r)}, ${toFixed(g)}, ${toFixed(b)})`; } function rgbToHexString({r, g, b, a}) { return `#${(a != null && a < 1 ? [r, g, b, Math.round(a * 255)] : [r, g, b] ) .map((x) => { return `${x < 16 ? "0" : ""}${x.toString(16)}`; }) .join("")}`; } function hslToString(hsl) { const {h, s, l, a} = hsl; if (a != null && a < 1) { return `hsla(${toFixed(h)}, ${toFixed(s * 100)}%, ${toFixed(l * 100)}%, ${toFixed(a, 2)})`; } return `hsl(${toFixed(h)}, ${toFixed(s * 100)}%, ${toFixed(l * 100)}%)`; } const rgbMatch = /^rgba?\([^\(\)]+\)$/; const hslMatch = /^hsla?\([^\(\)]+\)$/; const hexMatch = /^#[0-9a-f]+$/i; function parse($color) { const c = $color.trim().toLowerCase(); if (c.match(rgbMatch)) { return parseRGB(c); } if (c.match(hslMatch)) { return parseHSL(c); } if (c.match(hexMatch)) { return parseHex(c); } if (knownColors.has(c)) { return getColorByName(c); } if (systemColors.has(c)) { return getSystemColor(c); } if ($color === "transparent") { return {r: 0, g: 0, b: 0, a: 0}; } if ( (c.startsWith("color(") || c.startsWith("color-mix(")) && c.endsWith(")") ) { return domParseColor(c); } if (c.startsWith("light-dark(") && c.endsWith(")")) { const match = c.match( /^light-dark\(\s*([a-z]+(\(.*\))?),\s*([a-z]+(\(.*\))?)\s*\)$/ ); if (match) { const schemeColor = isSystemDarkModeEnabled() ? match[3] : match[1]; return parse(schemeColor); } } return null; } function getNumbers($color) { const numbers = []; let prevPos = 0; let isMining = false; const startIndex = $color.indexOf("("); $color = $color.substring(startIndex + 1, $color.length - 1); for (let i = 0; i < $color.length; i++) { const c = $color[i]; if ((c >= "0" && c <= "9") || c === "." || c === "+" || c === "-") { isMining = true; } else if (isMining && (c === " " || c === "," || c === "/")) { numbers.push($color.substring(prevPos, i)); isMining = false; prevPos = i + 1; } else if (!isMining) { prevPos = i + 1; } } if (isMining) { numbers.push($color.substring(prevPos, $color.length)); } return numbers; } function getNumbersFromString(str, range, units) { const raw = getNumbers(str); const unitsList = Object.entries(units); const numbers = raw .map((r) => r.trim()) .map((r, i) => { let n; const unit = unitsList.find(([u]) => r.endsWith(u)); if (unit) { n = (parseFloat(r.substring(0, r.length - unit[0].length)) / unit[1]) * range[i]; } else { n = parseFloat(r); } if (range[i] > 1) { return Math.round(n); } return n; }); return numbers; } const rgbRange = [255, 255, 255, 1]; const rgbUnits = {"%": 100}; function parseRGB($rgb) { const [r, g, b, a = 1] = getNumbersFromString($rgb, rgbRange, rgbUnits); return {r, g, b, a}; } const hslRange = [360, 1, 1, 1]; const hslUnits = {"%": 100, "deg": 360, "rad": 2 * Math.PI, "turn": 1}; function parseHSL($hsl) { const [h, s, l, a = 1] = getNumbersFromString($hsl, hslRange, hslUnits); return hslToRGB({h, s, l, a}); } function parseHex($hex) { const h = $hex.substring(1); switch (h.length) { case 3: case 4: { const [r, g, b] = [0, 1, 2].map((i) => parseInt(`${h[i]}${h[i]}`, 16) ); const a = h.length === 3 ? 1 : parseInt(`${h[3]}${h[3]}`, 16) / 255; return {r, g, b, a}; } case 6: case 8: { const [r, g, b] = [0, 2, 4].map((i) => parseInt(h.substring(i, i + 2), 16) ); const a = h.length === 6 ? 1 : parseInt(h.substring(6, 8), 16) / 255; return {r, g, b, a}; } } return null; } function getColorByName($color) { const n = knownColors.get($color); return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: (n >> 0) & 255, a: 1 }; } function getSystemColor($color) { const n = systemColors.get($color); return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: (n >> 0) & 255, a: 1 }; } function lowerCalcExpression(color) { let searchIndex = 0; const replaceBetweenIndices = (start, end, replacement) => { color = color.substring(0, start) + replacement + color.substring(end); }; while ((searchIndex = color.indexOf("calc(")) !== -1) { const range = getParenthesesRange(color, searchIndex); if (!range) { break; } let slice = color.slice(range.start + 1, range.end - 1); const includesPercentage = slice.includes("%"); slice = slice.split("%").join(""); const output = Math.round(evalMath(slice)); replaceBetweenIndices( range.start - 4, range.end, output + (includesPercentage ? "%" : "") ); } return color; } const knownColors = new Map( Object.entries({ aliceblue: 0xf0f8ff, antiquewhite: 0xfaebd7, aqua: 0x00ffff, aquamarine: 0x7fffd4, azure: 0xf0ffff, beige: 0xf5f5dc, bisque: 0xffe4c4, black: 0x000000, blanchedalmond: 0xffebcd, blue: 0x0000ff, blueviolet: 0x8a2be2, brown: 0xa52a2a, burlywood: 0xdeb887, cadetblue: 0x5f9ea0, chartreuse: 0x7fff00, chocolate: 0xd2691e, coral: 0xff7f50, cornflowerblue: 0x6495ed, cornsilk: 0xfff8dc, crimson: 0xdc143c, cyan: 0x00ffff, darkblue: 0x00008b, darkcyan: 0x008b8b, darkgoldenrod: 0xb8860b, darkgray: 0xa9a9a9, darkgrey: 0xa9a9a9, darkgreen: 0x006400, darkkhaki: 0xbdb76b, darkmagenta: 0x8b008b, darkolivegreen: 0x556b2f, darkorange: 0xff8c00, darkorchid: 0x9932cc, darkred: 0x8b0000, darksalmon: 0xe9967a, darkseagreen: 0x8fbc8f, darkslateblue: 0x483d8b, darkslategray: 0x2f4f4f, darkslategrey: 0x2f4f4f, darkturquoise: 0x00ced1, darkviolet: 0x9400d3, deeppink: 0xff1493, deepskyblue: 0x00bfff, dimgray: 0x696969, dimgrey: 0x696969, dodgerblue: 0x1e90ff, firebrick: 0xb22222, floralwhite: 0xfffaf0, forestgreen: 0x228b22, fuchsia: 0xff00ff, gainsboro: 0xdcdcdc, ghostwhite: 0xf8f8ff, gold: 0xffd700, goldenrod: 0xdaa520, gray: 0x808080, grey: 0x808080, green: 0x008000, greenyellow: 0xadff2f, honeydew: 0xf0fff0, hotpink: 0xff69b4, indianred: 0xcd5c5c, indigo: 0x4b0082, ivory: 0xfffff0, khaki: 0xf0e68c, lavender: 0xe6e6fa, lavenderblush: 0xfff0f5, lawngreen: 0x7cfc00, lemonchiffon: 0xfffacd, lightblue: 0xadd8e6, lightcoral: 0xf08080, lightcyan: 0xe0ffff, lightgoldenrodyellow: 0xfafad2, lightgray: 0xd3d3d3, lightgrey: 0xd3d3d3, lightgreen: 0x90ee90, lightpink: 0xffb6c1, lightsalmon: 0xffa07a, lightseagreen: 0x20b2aa, lightskyblue: 0x87cefa, lightslategray: 0x778899, lightslategrey: 0x778899, lightsteelblue: 0xb0c4de, lightyellow: 0xffffe0, lime: 0x00ff00, limegreen: 0x32cd32, linen: 0xfaf0e6, magenta: 0xff00ff, maroon: 0x800000, mediumaquamarine: 0x66cdaa, mediumblue: 0x0000cd, mediumorchid: 0xba55d3, mediumpurple: 0x9370db, mediumseagreen: 0x3cb371, mediumslateblue: 0x7b68ee, mediumspringgreen: 0x00fa9a, mediumturquoise: 0x48d1cc, mediumvioletred: 0xc71585, midnightblue: 0x191970, mintcream: 0xf5fffa, mistyrose: 0xffe4e1, moccasin: 0xffe4b5, navajowhite: 0xffdead, navy: 0x000080, oldlace: 0xfdf5e6, olive: 0x808000, olivedrab: 0x6b8e23, orange: 0xffa500, orangered: 0xff4500, orchid: 0xda70d6, palegoldenrod: 0xeee8aa, palegreen: 0x98fb98, paleturquoise: 0xafeeee, palevioletred: 0xdb7093, papayawhip: 0xffefd5, peachpuff: 0xffdab9, peru: 0xcd853f, pink: 0xffc0cb, plum: 0xdda0dd, powderblue: 0xb0e0e6, purple: 0x800080, rebeccapurple: 0x663399, red: 0xff0000, rosybrown: 0xbc8f8f, royalblue: 0x4169e1, saddlebrown: 0x8b4513, salmon: 0xfa8072, sandybrown: 0xf4a460, seagreen: 0x2e8b57, seashell: 0xfff5ee, sienna: 0xa0522d, silver: 0xc0c0c0, skyblue: 0x87ceeb, slateblue: 0x6a5acd, slategray: 0x708090, slategrey: 0x708090, snow: 0xfffafa, springgreen: 0x00ff7f, steelblue: 0x4682b4, tan: 0xd2b48c, teal: 0x008080, thistle: 0xd8bfd8, tomato: 0xff6347, turquoise: 0x40e0d0, violet: 0xee82ee, wheat: 0xf5deb3, white: 0xffffff, whitesmoke: 0xf5f5f5, yellow: 0xffff00, yellowgreen: 0x9acd32 }) ); const systemColors = new Map( Object.entries({ "ActiveBorder": 0x3b99fc, "ActiveCaption": 0x000000, "AppWorkspace": 0xaaaaaa, "Background": 0x6363ce, "ButtonFace": 0xffffff, "ButtonHighlight": 0xe9e9e9, "ButtonShadow": 0x9fa09f, "ButtonText": 0x000000, "CaptionText": 0x000000, "GrayText": 0x7f7f7f, "Highlight": 0xb2d7ff, "HighlightText": 0x000000, "InactiveBorder": 0xffffff, "InactiveCaption": 0xffffff, "InactiveCaptionText": 0x000000, "InfoBackground": 0xfbfcc5, "InfoText": 0x000000, "Menu": 0xf6f6f6, "MenuText": 0xffffff, "Scrollbar": 0xaaaaaa, "ThreeDDarkShadow": 0x000000, "ThreeDFace": 0xc0c0c0, "ThreeDHighlight": 0xffffff, "ThreeDLightShadow": 0xffffff, "ThreeDShadow": 0x000000, "Window": 0xececec, "WindowFrame": 0xaaaaaa, "WindowText": 0x000000, "-webkit-focus-ring-color": 0xe59700 }).map(([key, value]) => [key.toLowerCase(), value]) ); function getSRGBLightness(r, g, b) { return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255; } let canvas$1; let context$1; function domParseColor($color) { if (!context$1) { canvas$1 = document.createElement("canvas"); canvas$1.width = 1; canvas$1.height = 1; context$1 = canvas$1.getContext("2d", {willReadFrequently: true}); } context$1.fillStyle = $color; context$1.fillRect(0, 0, 1, 1); const d = context$1.getImageData(0, 0, 1, 1).data; const color = `rgba(${d[0]}, ${d[1]}, ${d[2]}, ${(d[3] / 255).toFixed(2)})`; return parseRGB(color); } const COLOR_SCHEME_META_SELECTOR = 'meta[name="color-scheme"]'; function hasBuiltInDarkTheme() { const rootStyle = getComputedStyle(document.documentElement); if (rootStyle.filter.includes("invert(1)")) { return true; } const CELL_SIZE = 256; const MAX_ROW_COUNT = 4; const winWidth = innerWidth; const winHeight = innerHeight; const stepX = Math.floor( winWidth / Math.min(MAX_ROW_COUNT, Math.ceil(winWidth / CELL_SIZE)) ); const stepY = Math.floor( winHeight / Math.min(MAX_ROW_COUNT, Math.ceil(winHeight / CELL_SIZE)) ); const processedElements = new Set(); for (let y = Math.floor(stepY / 2); y < winHeight; y += stepY) { for (let x = Math.floor(stepX / 2); x < winWidth; x += stepX) { const element = document.elementFromPoint(x, y); if (!element || processedElements.has(element)) { continue; } processedElements.add(element); const style = element === document.documentElement ? rootStyle : getComputedStyle(element); const bgColor = parseColorWithCache(style.backgroundColor); if (bgColor.a === 1) { const bgLightness = getSRGBLightness( bgColor.r, bgColor.g, bgColor.b ); if (bgLightness > 0.5) { return false; } } else { const textColor = parseColorWithCache(style.color); const textLightness = getSRGBLightness( textColor.r, textColor.g, textColor.b ); if (textLightness < 0.5) { return false; } } } } const rootColor = parseColorWithCache(rootStyle.backgroundColor); const bodyColor = document.body ? parseColorWithCache( getComputedStyle(document.body).backgroundColor ) : {r: 0, g: 0, b: 0, a: 0}; const rootLightness = 1 - rootColor.a + rootColor.a * getSRGBLightness(rootColor.r, rootColor.g, rootColor.b); const finalLightness = (1 - bodyColor.a) * rootLightness + bodyColor.a * getSRGBLightness(bodyColor.r, bodyColor.g, bodyColor.b); return finalLightness < 0.5; } function runCheck(callback) { const colorSchemeMeta = document.querySelector( COLOR_SCHEME_META_SELECTOR ); if (colorSchemeMeta) { const isMetaDark = colorSchemeMeta.content === "dark" || (colorSchemeMeta.content.includes("dark") && isSystemDarkModeEnabled()); callback(isMetaDark); return; } const drStyles = document.querySelectorAll(".darkreader"); drStyles.forEach((style) => (style.disabled = true)); const darkThemeDetected = hasBuiltInDarkTheme(); drStyles.forEach((style) => (style.disabled = false)); callback(darkThemeDetected); } function hasSomeStyle() { if (document.querySelector(COLOR_SCHEME_META_SELECTOR) != null) { return true; } if ( document.documentElement.style.backgroundColor || (document.body && document.body.style.backgroundColor) ) { return true; } for (const style of document.styleSheets) { if ( style && style.ownerNode && !( style.ownerNode.classList && style.ownerNode.classList.contains("darkreader") ) ) { return true; } } return false; } let observer$1; let readyStateListener; function canCheckForStyle() { return ( document.body && document.body.scrollHeight > 0 && document.body.clientHeight > 0 && hasSomeStyle() ); } function runDarkThemeDetector(callback, hints) { stopDarkThemeDetector(); if (hints && hints.length > 0) { const hint = hints[0]; if (hint.noDarkTheme) { callback(false); return; } if (hint.systemTheme && isSystemDarkModeEnabled()) { callback(true); return; } detectUsingHint(hint, () => callback(true)); return; } if (canCheckForStyle()) { runCheck(callback); return; } observer$1 = new MutationObserver(() => { if (canCheckForStyle()) { stopDarkThemeDetector(); runCheck(callback); } }); observer$1.observe(document.documentElement, {childList: true}); if (document.readyState !== "complete") { readyStateListener = () => { if (document.readyState === "complete") { stopDarkThemeDetector(); runCheck(callback); } }; document.addEventListener("readystatechange", readyStateListener); } } function stopDarkThemeDetector() { if (observer$1) { observer$1.disconnect(); observer$1 = null; } if (readyStateListener) { document.removeEventListener( "readystatechange", readyStateListener ); readyStateListener = null; } stopDetectingUsingHint(); } let hintTargetObserver; let hintMatchObserver; function detectUsingHint(hint, success) { stopDetectingUsingHint(); const matchSelector = (hint.match || []).join(", "); function checkMatch(target) { if (target.matches?.(matchSelector)) { stopDetectingUsingHint(); success(); return true; } return false; } function setupMatchObserver(target) { hintMatchObserver?.disconnect(); if (checkMatch(target)) { return; } hintMatchObserver = new MutationObserver(() => checkMatch(target)); hintMatchObserver.observe(target, {attributes: true}); } const target = document.querySelector(hint.target); if (target) { setupMatchObserver(target); } else { hintTargetObserver = new MutationObserver((mutations) => { const handledTargets = new Set(); for (const mutation of mutations) { if (handledTargets.has(mutation.target)) { continue; } handledTargets.add(mutation.target); if (mutation.target instanceof Element) { const target = mutation.target.querySelector( hint.target ); if (target) { hintTargetObserver.disconnect(); setupMatchObserver(target); break; } } } }); hintTargetObserver.observe(document.documentElement, { childList: true, subtree: true }); } } function stopDetectingUsingHint() { hintTargetObserver?.disconnect(); hintMatchObserver?.disconnect(); } function cachedFactory(factory, size) { const cache = new Map(); return (key) => { if (cache.has(key)) { return cache.get(key); } const value = factory(key); cache.set(key, value); if (cache.size > size) { const first = cache.keys().next().value; cache.delete(first); } return value; }; } let anchor; const parsedURLCache = new Map(); function fixBaseURL($url) { if (!anchor) { anchor = document.createElement("a"); } anchor.href = $url; return anchor.href; } function parseURL($url, $base = null) { const key = `${$url}${$base ? `;${$base}` : ""}`; if (parsedURLCache.has(key)) { return parsedURLCache.get(key); } if ($base) { const parsedURL = new URL($url, fixBaseURL($base)); parsedURLCache.set(key, parsedURL); return parsedURL; } const parsedURL = new URL(fixBaseURL($url)); parsedURLCache.set($url, parsedURL); return parsedURL; } function getAbsoluteURL($base, $relative) { if ($relative.match(/^data\\?\:/)) { return $relative; } if (/^\/\//.test($relative)) { return `${location.protocol}${$relative}`; } const b = parseURL($base); const a = parseURL($relative, b.href); return a.href; } function isRelativeHrefOnAbsolutePath(href) { if (href.startsWith("data:")) { return true; } const url = parseURL(href); if (url.protocol !== location.protocol) { return false; } if (url.hostname !== location.hostname) { return false; } if (url.port !== location.port) { return false; } return url.pathname === location.pathname; } function isURLInList(url, list) { for (let i = 0; i < list.length; i++) { if (isURLMatched(url, list[i])) { return true; } } return false; } function isURLMatched(url, urlTemplate) { if (isRegExp(urlTemplate)) { const regexp = createRegExp(urlTemplate); return regexp ? regexp.test(url) : false; } return matchURLPattern(url, urlTemplate); } const URL_CACHE_SIZE = 32; const prepareURL = cachedFactory((url) => { let parsed; try { parsed = new URL(url); } catch (err) { return null; } const {hostname, pathname, protocol, port} = parsed; const hostParts = hostname.split(".").reverse(); const pathParts = pathname.split("/").slice(1); if (!pathParts[pathParts.length - 1]) { pathParts.splice(pathParts.length - 1, 1); } return { hostParts, pathParts, port, protocol }; }, URL_CACHE_SIZE); const URL_MATCH_CACHE_SIZE = 32 * 1024; const preparePattern = cachedFactory((pattern) => { if (!pattern) { return null; } const exactStart = pattern.startsWith("^"); const exactEnd = pattern.endsWith("$"); if (exactStart) { pattern = pattern.substring(1); } if (exactEnd) { pattern = pattern.substring(0, pattern.length - 1); } let protocol = ""; const protocolIndex = pattern.indexOf("://"); if (protocolIndex > 0) { protocol = pattern.substring(0, protocolIndex + 1); pattern = pattern.substring(protocolIndex + 3); } const slashIndex = pattern.indexOf("/"); const host = slashIndex < 0 ? pattern : pattern.substring(0, slashIndex); let hostName = host; let isIPv6 = false; let ipV6End = -1; if (host.startsWith("[")) { ipV6End = host.indexOf("]"); if (ipV6End > 0) { isIPv6 = true; } } let port = "*"; const portIndex = host.lastIndexOf(":"); if (portIndex >= 0 && (!isIPv6 || ipV6End < portIndex)) { hostName = host.substring(0, portIndex); port = host.substring(portIndex + 1); } if (isIPv6) { try { const ipV6URL = new URL(`http://${hostName}`); hostName = ipV6URL.hostname; } catch (err) {} } const hostParts = hostName.split(".").reverse(); const path = slashIndex < 0 ? "" : pattern.substring(slashIndex + 1); const pathParts = path.split("/"); if (!pathParts[pathParts.length - 1]) { pathParts.splice(pathParts.length - 1, 1); } return { hostParts, pathParts, port, exactStart, exactEnd, protocol }; }, URL_MATCH_CACHE_SIZE); function matchURLPattern(url, pattern) { const u = prepareURL(url); const p = preparePattern(pattern); if ( !(u && p) || p.hostParts.length > u.hostParts.length || (p.exactStart && p.hostParts.length !== u.hostParts.length) || (p.exactEnd && p.pathParts.length !== u.pathParts.length) || (p.port !== "*" && p.port !== u.port) || (p.protocol && p.protocol !== u.protocol) ) { return false; } for (let i = 0; i < p.hostParts.length; i++) { const pHostPart = p.hostParts[i]; const uHostPart = u.hostParts[i]; if (pHostPart !== "*" && pHostPart !== uHostPart) { return false; } } if ( p.hostParts.length >= 2 && p.hostParts.at(-1) !== "*" && (p.hostParts.length < u.hostParts.length - 1 || (p.hostParts.length === u.hostParts.length - 1 && u.hostParts.at(-1) !== "www")) ) { return false; } if (p.pathParts.length === 0) { return true; } if (p.pathParts.length > u.pathParts.length) { return false; } for (let i = 0; i < p.pathParts.length; i++) { const pPathPart = p.pathParts[i]; const uPathPart = u.pathParts[i]; if (pPathPart !== "*" && pPathPart !== uPathPart) { return false; } } return true; } function isRegExp(pattern) { return ( pattern.startsWith("/") && pattern.endsWith("/") && pattern.length > 2 ); } const REGEXP_CACHE_SIZE = 1024; const createRegExp = cachedFactory((pattern) => { if (pattern.startsWith("/")) { pattern = pattern.substring(1); } if (pattern.endsWith("/")) { pattern = pattern.substring(0, pattern.length - 1); } try { return new RegExp(pattern); } catch (err) { return null; } }, REGEXP_CACHE_SIZE); function iterateCSSRules(rules, iterate, onImportError) { forEach(rules, (rule) => { if (isStyleRule(rule)) { iterate(rule); } else if (isImportRule(rule)) { try { iterateCSSRules( rule.styleSheet.cssRules, iterate, onImportError ); } catch (err) { onImportError?.(); } } else if (isMediaRule(rule)) { const media = Array.from(rule.media); const isScreenOrAllOrQuery = media.some( (m) => m.startsWith("screen") || m.startsWith("all") || m.startsWith("(") ); const isPrintOrSpeech = media.some( (m) => m.startsWith("print") || m.startsWith("speech") ); if (isScreenOrAllOrQuery || !isPrintOrSpeech) { iterateCSSRules(rule.cssRules, iterate, onImportError); } } else if (isSupportsRule(rule)) { if (CSS.supports(rule.conditionText)) { iterateCSSRules(rule.cssRules, iterate, onImportError); } } else if (isLayerRule(rule)) { iterateCSSRules(rule.cssRules, iterate, onImportError); } else; }); } const shorthandVarDependantProperties = [ "background", "border", "border-color", "border-bottom", "border-left", "border-right", "border-top", "outline", "outline-color" ]; function iterateCSSDeclarations(style, iterate) { forEach(style, (property) => { const value = style.getPropertyValue(property).trim(); if (!value) { return; } iterate(property, value); }); const cssText = style.cssText; if (cssText.includes("var(")) { { shorthandVarDependantProperties.forEach((prop) => { const val = style.getPropertyValue(prop); if (val && val.includes("var(")) { iterate(prop, val); } }); } } if ( cssText.includes("background-color: ;") && !style.getPropertyValue("background") ) { handleEmptyShorthand("background", style, iterate); } if ( cssText.includes("border-") && cssText.includes("-color: ;") && !style.getPropertyValue("border") ) { handleEmptyShorthand("border", style, iterate); } } function handleEmptyShorthand(shorthand, style, iterate) { const parentRule = style.parentRule; if (isStyleRule(parentRule)) { const sourceCSSText = parentRule.parentStyleSheet?.ownerNode?.textContent; if (sourceCSSText) { let escapedSelector = escapeRegExpSpecialChars( parentRule.selectorText ); escapedSelector = escapedSelector.replaceAll(/\s+/g, "\\s*"); escapedSelector = escapedSelector.replaceAll(/::/g, "::?"); const regexp = new RegExp( `${escapedSelector}\\s*{[^}]*${shorthand}:\\s*([^;}]+)` ); const match = sourceCSSText.match(regexp); if (match) { iterate(shorthand, match[1]); } } else if (shorthand === "background") { iterate("background-color", "#ffffff"); } } } const cssURLRegex = /url\((('.*?')|(".*?")|([^\)]*?))\)/g; const cssImportRegex = /@import\s*(url\()?(('.+?')|(".+?")|([^\)]*?))\)? ?(screen)?;?/gi; function getCSSURLValue(cssURL) { return cssURL .trim() .replace(/[\n\r\\]+/g, "") .replace(/^url\((.*)\)$/, "$1") .trim() .replace(/^"(.*)"$/, "$1") .replace(/^'(.*)'$/, "$1") .replace(/(?:\\(.))/g, "$1"); } function getCSSBaseBath(url) { const cssURL = parseURL(url); return `${cssURL.origin}${cssURL.pathname.replace(/\?.*$/, "").replace(/(\/)([^\/]+)$/i, "$1")}`; } function replaceCSSRelativeURLsWithAbsolute($css, cssBasePath) { return $css.replace(cssURLRegex, (match) => { try { const url = getCSSURLValue(match); const absoluteURL = getAbsoluteURL(cssBasePath, url); const escapedURL = absoluteURL.replaceAll("'", "\\'"); return `url('${escapedURL}')`; } catch (err) { return match; } }); } const fontFaceRegex = /@font-face\s*{[^}]*}/g; function replaceCSSFontFace($css) { return $css.replace(fontFaceRegex, ""); } const styleRules = new WeakSet(); const importRules = new WeakSet(); const mediaRules = new WeakSet(); const supportsRules = new WeakSet(); const layerRules = new WeakSet(); function isStyleRule(rule) { if (!rule) { return false; } if (styleRules.has(rule)) { return true; } if (rule.selectorText) { styleRules.add(rule); return true; } return false; } function isImportRule(rule) { if (!rule) { return false; } if (styleRules.has(rule)) { return false; } if (importRules.has(rule)) { return true; } if (rule.href) { importRules.add(rule); return true; } return false; } function isMediaRule(rule) { if (!rule) { return false; } if (styleRules.has(rule)) { return false; } if (mediaRules.has(rule)) { return true; } if (rule.media) { mediaRules.add(rule); return true; } return false; } function isSupportsRule(rule) { if (!rule) { return false; } if (styleRules.has(rule)) { return false; } if (supportsRules.has(rule)) { return true; } if (rule instanceof CSSSupportsRule) { supportsRules.add(rule); return true; } return false; } function isLayerRule(rule) { if (!rule) { return false; } if (styleRules.has(rule)) { return false; } if (layerRules.has(rule)) { return true; } if (isLayerRuleSupported && rule instanceof CSSLayerBlockRule) { layerRules.add(rule); return true; } return false; } function scale(x, inLow, inHigh, outLow, outHigh) { return ((x - inLow) * (outHigh - outLow)) / (inHigh - inLow) + outLow; } function clamp(x, min, max) { return Math.min(max, Math.max(min, x)); } function multiplyMatrices(m1, m2) { const result = []; for (let i = 0, len = m1.length; i < len; i++) { result[i] = []; for (let j = 0, len2 = m2[0].length; j < len2; j++) { let sum = 0; for (let k = 0, len3 = m1[0].length; k < len3; k++) { sum += m1[i][k] * m2[k][j]; } result[i][j] = sum; } } return result; } function createFilterMatrix(config) { let m = Matrix.identity(); if (config.sepia !== 0) { m = multiplyMatrices(m, Matrix.sepia(config.sepia / 100)); } if (config.grayscale !== 0) { m = multiplyMatrices(m, Matrix.grayscale(config.grayscale / 100)); } if (config.contrast !== 100) { m = multiplyMatrices(m, Matrix.contrast(config.contrast / 100)); } if (config.brightness !== 100) { m = multiplyMatrices(m, Matrix.brightness(config.brightness / 100)); } if (config.mode === 1) { m = multiplyMatrices(m, Matrix.invertNHue()); } return m; } function applyColorMatrix([r, g, b], matrix) { const rgb = [[r / 255], [g / 255], [b / 255], [1], [1]]; const result = multiplyMatrices(matrix, rgb); return [0, 1, 2].map((i) => clamp(Math.round(result[i][0] * 255), 0, 255) ); } const Matrix = { identity() { return [ [1, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 1, 0], [0, 0, 0, 0, 1] ]; }, invertNHue() { return [ [0.333, -0.667, -0.667, 0, 1], [-0.667, 0.333, -0.667, 0, 1], [-0.667, -0.667, 0.333, 0, 1], [0, 0, 0, 1, 0], [0, 0, 0, 0, 1] ]; }, brightness(v) { return [ [v, 0, 0, 0, 0], [0, v, 0, 0, 0], [0, 0, v, 0, 0], [0, 0, 0, 1, 0], [0, 0, 0, 0, 1] ]; }, contrast(v) { const t = (1 - v) / 2; return [ [v, 0, 0, 0, t], [0, v, 0, 0, t], [0, 0, v, 0, t], [0, 0, 0, 1, 0], [0, 0, 0, 0, 1] ]; }, sepia(v) { return [ [ 0.393 + 0.607 * (1 - v), 0.769 - 0.769 * (1 - v), 0.189 - 0.189 * (1 - v), 0, 0 ], [ 0.349 - 0.349 * (1 - v), 0.686 + 0.314 * (1 - v), 0.168 - 0.168 * (1 - v), 0, 0 ], [ 0.272 - 0.272 * (1 - v), 0.534 - 0.534 * (1 - v), 0.131 + 0.869 * (1 - v), 0, 0 ], [0, 0, 0, 1, 0], [0, 0, 0, 0, 1] ]; }, grayscale(v) { return [ [ 0.2126 + 0.7874 * (1 - v), 0.7152 - 0.7152 * (1 - v), 0.0722 - 0.0722 * (1 - v), 0, 0 ], [ 0.2126 - 0.2126 * (1 - v), 0.7152 + 0.2848 * (1 - v), 0.0722 - 0.0722 * (1 - v), 0, 0 ], [ 0.2126 - 0.2126 * (1 - v), 0.7152 - 0.7152 * (1 - v), 0.0722 + 0.9278 * (1 - v), 0, 0 ], [0, 0, 0, 1, 0], [0, 0, 0, 0, 1] ]; } }; function getBgPole(theme) { const isDarkScheme = theme.mode === 1; const prop = isDarkScheme ? "darkSchemeBackgroundColor" : "lightSchemeBackgroundColor"; return theme[prop]; } function getFgPole(theme) { const isDarkScheme = theme.mode === 1; const prop = isDarkScheme ? "darkSchemeTextColor" : "lightSchemeTextColor"; return theme[prop]; } const colorModificationCache = new Map(); function clearColorModificationCache() { colorModificationCache.clear(); } const rgbCacheKeys = ["r", "g", "b", "a"]; const themeCacheKeys$1 = [ "mode", "brightness", "contrast", "grayscale", "sepia", "darkSchemeBackgroundColor", "darkSchemeTextColor", "lightSchemeBackgroundColor", "lightSchemeTextColor" ]; function getCacheId(rgb, theme) { let resultId = ""; rgbCacheKeys.forEach((key) => { resultId += `${rgb[key]};`; }); themeCacheKeys$1.forEach((key) => { resultId += `${theme[key]};`; }); return resultId; } function modifyColorWithCache( rgb, theme, modifyHSL, poleColor, anotherPoleColor ) { let fnCache; if (colorModificationCache.has(modifyHSL)) { fnCache = colorModificationCache.get(modifyHSL); } else { fnCache = new Map(); colorModificationCache.set(modifyHSL, fnCache); } const id = getCacheId(rgb, theme); if (fnCache.has(id)) { return fnCache.get(id); } const hsl = rgbToHSL(rgb); const pole = poleColor == null ? null : parseToHSLWithCache(poleColor); const anotherPole = anotherPoleColor == null ? null : parseToHSLWithCache(anotherPoleColor); const modified = modifyHSL(hsl, pole, anotherPole); const {r, g, b, a} = hslToRGB(modified); const matrix = createFilterMatrix(theme); const [rf, gf, bf] = applyColorMatrix([r, g, b], matrix); const color = a === 1 ? rgbToHexString({r: rf, g: gf, b: bf}) : rgbToString({r: rf, g: gf, b: bf, a}); fnCache.set(id, color); return color; } function modifyLightSchemeColor(rgb, theme) { const poleBg = getBgPole(theme); const poleFg = getFgPole(theme); return modifyColorWithCache( rgb, theme, modifyLightModeHSL, poleFg, poleBg ); } function modifyLightModeHSL({h, s, l, a}, poleFg, poleBg) { const isDark = l < 0.5; let isNeutral; if (isDark) { isNeutral = l < 0.2 || s < 0.12; } else { const isBlue = h > 200 && h < 280; isNeutral = s < 0.24 || (l > 0.8 && isBlue); } let hx = h; let sx = l; if (isNeutral) { if (isDark) { hx = poleFg.h; sx = poleFg.s; } else { hx = poleBg.h; sx = poleBg.s; } } const lx = scale(l, 0, 1, poleFg.l, poleBg.l); return {h: hx, s: sx, l: lx, a}; } const MAX_BG_LIGHTNESS = 0.4; function modifyBgHSL({h, s, l, a}, pole) { const isDark = l < 0.5; const isBlue = h > 200 && h < 280; const isNeutral = s < 0.12 || (l > 0.8 && isBlue); if (isDark) { const lx = scale(l, 0, 0.5, 0, MAX_BG_LIGHTNESS); if (isNeutral) { const hx = pole.h; const sx = pole.s; return {h: hx, s: sx, l: lx, a}; } return {h, s, l: lx, a}; } let lx = scale(l, 0.5, 1, MAX_BG_LIGHTNESS, pole.l); if (isNeutral) { const hx = pole.h; const sx = pole.s; return {h: hx, s: sx, l: lx, a}; } let hx = h; const isYellow = h > 60 && h < 180; if (isYellow) { const isCloserToGreen = h > 120; if (isCloserToGreen) { hx = scale(h, 120, 180, 135, 180); } else { hx = scale(h, 60, 120, 60, 105); } } if (hx > 40 && hx < 80) { lx *= 0.75; } return {h: hx, s, l: lx, a}; } function modifyBackgroundColor(rgb, theme) { if (theme.mode === 0) { return modifyLightSchemeColor(rgb, theme); } const pole = getBgPole(theme); return modifyColorWithCache( rgb, {...theme, mode: 0}, modifyBgHSL, pole ); } const MIN_FG_LIGHTNESS = 0.55; function modifyBlueFgHue(hue) { return scale(hue, 205, 245, 205, 220); } function modifyFgHSL({h, s, l, a}, pole) { const isLight = l > 0.5; const isNeutral = l < 0.2 || s < 0.24; const isBlue = !isNeutral && h > 205 && h < 245; if (isLight) { const lx = scale(l, 0.5, 1, MIN_FG_LIGHTNESS, pole.l); if (isNeutral) { const hx = pole.h; const sx = pole.s; return {h: hx, s: sx, l: lx, a}; } let hx = h; if (isBlue) { hx = modifyBlueFgHue(h); } return {h: hx, s, l: lx, a}; } if (isNeutral) { const hx = pole.h; const sx = pole.s; const lx = scale(l, 0, 0.5, pole.l, MIN_FG_LIGHTNESS); return {h: hx, s: sx, l: lx, a}; } let hx = h; let lx; if (isBlue) { hx = modifyBlueFgHue(h); lx = scale(l, 0, 0.5, pole.l, Math.min(1, MIN_FG_LIGHTNESS + 0.05)); } else { lx = scale(l, 0, 0.5, pole.l, MIN_FG_LIGHTNESS); } return {h: hx, s, l: lx, a}; } function modifyForegroundColor(rgb, theme) { if (theme.mode === 0) { return modifyLightSchemeColor(rgb, theme); } const pole = getFgPole(theme); return modifyColorWithCache( rgb, {...theme, mode: 0}, modifyFgHSL, pole ); } function modifyBorderHSL({h, s, l, a}, poleFg, poleBg) { const isDark = l < 0.5; const isNeutral = l < 0.2 || s < 0.24; let hx = h; let sx = s; if (isNeutral) { if (isDark) { hx = poleFg.h; sx = poleFg.s; } else { hx = poleBg.h; sx = poleBg.s; } } const lx = scale(l, 0, 1, 0.5, 0.2); return {h: hx, s: sx, l: lx, a}; } function modifyBorderColor(rgb, theme) { if (theme.mode === 0) { return modifyLightSchemeColor(rgb, theme); } const poleFg = getFgPole(theme); const poleBg = getBgPole(theme); return modifyColorWithCache( rgb, {...theme, mode: 0}, modifyBorderHSL, poleFg, poleBg ); } function modifyShadowColor(rgb, theme) { return modifyBackgroundColor(rgb, theme); } function modifyGradientColor(rgb, theme) { return modifyBackgroundColor(rgb, theme); } const excludedSelectors = [ "pre", "pre *", "code", '[aria-hidden="true"]', '[class*="fa-"]', ".fa", ".fab", ".fad", ".fal", ".far", ".fas", ".fass", ".fasr", ".fat", ".icofont", '[style*="font-"]', '[class*="icon"]', '[class*="Icon"]', '[class*="symbol"]', '[class*="Symbol"]', ".glyphicon", '[class*="material-symbol"]', '[class*="material-icon"]', "mu", '[class*="mu-"]', ".typcn", '[class*="vjs-"]' ]; function createTextStyle(config) { const lines = []; lines.push(`*:not(${excludedSelectors.join(", ")}) {`); if (config.useFont && config.fontFamily) { lines.push(` font-family: ${config.fontFamily} !important;`); } if (config.textStroke > 0) { lines.push( ` -webkit-text-stroke: ${config.textStroke}px !important;` ); lines.push(` text-stroke: ${config.textStroke}px !important;`); } lines.push("}"); return lines.join("\n"); } var FilterMode; (function (FilterMode) { FilterMode[(FilterMode["light"] = 0)] = "light"; FilterMode[(FilterMode["dark"] = 1)] = "dark"; })(FilterMode || (FilterMode = {})); function getCSSFilterValue(config) { const filters = []; if (config.mode === FilterMode.dark) { filters.push("invert(100%) hue-rotate(180deg)"); } if (config.brightness !== 100) { filters.push(`brightness(${config.brightness}%)`); } if (config.contrast !== 100) { filters.push(`contrast(${config.contrast}%)`); } if (config.grayscale !== 0) { filters.push(`grayscale(${config.grayscale}%)`); } if (config.sepia !== 0) { filters.push(`sepia(${config.sepia}%)`); } if (filters.length === 0) { return null; } return filters.join(" "); } function toSVGMatrix(matrix) { return matrix .slice(0, 4) .map((m) => m.map((m) => m.toFixed(3)).join(" ")) .join(" "); } function getSVGFilterMatrixValue(config) { return toSVGMatrix(createFilterMatrix(config)); } function hexify(number) { return (number < 16 ? "0" : "") + number.toString(16); } function generateUID() { if ("randomUUID" in crypto) { const uuid = crypto.randomUUID(); return ( uuid.substring(0, 8) + uuid.substring(9, 13) + uuid.substring(14, 18) + uuid.substring(19, 23) + uuid.substring(24) ); } if ("getRandomValues" in crypto) { return Array.from(crypto.getRandomValues(new Uint8Array(16))) .map((x) => hexify(x)) .join(""); } return Math.floor(Math.random() * 2 ** 55).toString(36); } const resolvers$1 = new Map(); const rejectors = new Map(); async function bgFetch(request) { if (window.DarkReader?.Plugins?.fetch) { return window.DarkReader.Plugins.fetch(request); } return new Promise((resolve, reject) => { const id = generateUID(); resolvers$1.set(id, resolve); rejectors.set(id, reject); chrome.runtime.sendMessage({ type: MessageTypeCStoBG.FETCH, data: request, id }); }); } chrome.runtime.onMessage.addListener(({type, data, error, id}) => { if (type === MessageTypeBGtoCS.FETCH_RESPONSE) { const resolve = resolvers$1.get(id); const reject = rejectors.get(id); resolvers$1.delete(id); rejectors.delete(id); if (error) { reject && reject(error); } else { resolve && resolve(data); } } }); async function getOKResponse(url, mimeType, origin) { const response = await fetch(url, { cache: "force-cache", credentials: "omit", referrer: origin }); if ( mimeType && !response.headers.get("Content-Type").startsWith(mimeType) ) { throw new Error(`Mime type mismatch when loading ${url}`); } if (!response.ok) { throw new Error( `Unable to load ${url} ${response.status} ${response.statusText}` ); } return response; } async function loadAsDataURL(url, mimeType) { const response = await getOKResponse(url, mimeType); return await readResponseAsDataURL(response); } async function loadAsBlob(url, mimeType) { const response = await getOKResponse(url, mimeType); return await response.blob(); } async function readResponseAsDataURL(response) { const blob = await response.blob(); const dataURL = await new Promise((resolve) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result); reader.readAsDataURL(blob); }); return dataURL; } async function loadAsText(url, mimeType, origin) { const response = await getOKResponse(url, mimeType, origin); return await response.text(); } const MAX_FRAME_DURATION = 1000 / 60; class AsyncQueue { queue = []; timerId = null; addTask(task) { this.queue.push(task); this.scheduleFrame(); } stop() { if (this.timerId !== null) { cancelAnimationFrame(this.timerId); this.timerId = null; } this.queue = []; } scheduleFrame() { if (this.timerId) { return; } this.timerId = requestAnimationFrame(() => { this.timerId = null; const start = Date.now(); let cb; while ((cb = this.queue.shift())) { cb(); if (Date.now() - start >= MAX_FRAME_DURATION) { this.scheduleFrame(); break; } } }); } } const imageManager = new AsyncQueue(); async function getImageDetails(url) { return new Promise(async (resolve, reject) => { try { const dataURL = url.startsWith("data:") ? url : await getDataURL(url); const blob = tryConvertDataURLToBlobSync(dataURL) ?? (await loadAsBlob(url)); let image; if (dataURL.startsWith("data:image/svg+xml")) { image = await loadImage(dataURL); } else { image = (await tryCreateImageBitmap(blob)) ?? (await loadImage(dataURL)); } imageManager.addTask(() => { const analysis = analyzeImage(image); resolve({ src: url, dataURL: analysis.isLarge ? "" : dataURL, width: image.width, height: image.height, ...analysis }); }); } catch (error) { reject(error); } }); } async function getDataURL(url) { const parsedURL = new URL(url); if (parsedURL.origin === location.origin) { return await loadAsDataURL(url); } return await bgFetch({url, responseType: "data-url"}); } async function tryCreateImageBitmap(blob) { try { return await createImageBitmap(blob); } catch (err) { logWarn( `Unable to create image bitmap for type ${blob.type}: ${String(err)}` ); return null; } } const INCOMPLETE_DOC_LOADING_IMAGE_LIMIT = 256; let loadingImagesCount = 0; async function loadImage(url) { return new Promise((resolve, reject) => { const image = new Image(); image.onload = () => resolve(image); image.onerror = () => reject(`Unable to load image ${url}`); if ( ++loadingImagesCount <= INCOMPLETE_DOC_LOADING_IMAGE_LIMIT || isReadyStateComplete() ) { image.src = url; } else { addReadyStateCompleteListener(() => (image.src = url)); } }); } const MAX_ANALYSIS_PIXELS_COUNT = 32 * 32; let canvas; let context; function createCanvas() { const maxWidth = MAX_ANALYSIS_PIXELS_COUNT; const maxHeight = MAX_ANALYSIS_PIXELS_COUNT; canvas = document.createElement("canvas"); canvas.width = maxWidth; canvas.height = maxHeight; context = canvas.getContext("2d", {willReadFrequently: true}); context.imageSmoothingEnabled = false; } function removeCanvas() { canvas = null; context = null; } const LARGE_IMAGE_PIXELS_COUNT = 512 * 512; function analyzeImage(image) { if (!canvas) { createCanvas(); } let sw; let sh; if (image instanceof HTMLImageElement) { sw = image.naturalWidth; sh = image.naturalHeight; } else { sw = image.width; sh = image.height; } if (sw === 0 || sh === 0) { return { isDark: false, isLight: false, isTransparent: false, isLarge: false }; } const isLarge = sw * sh > LARGE_IMAGE_PIXELS_COUNT; const sourcePixelsCount = sw * sh; const k = Math.min( 1, Math.sqrt(MAX_ANALYSIS_PIXELS_COUNT / sourcePixelsCount) ); const width = Math.ceil(sw * k); const height = Math.ceil(sh * k); context.clearRect(0, 0, width, height); context.drawImage(image, 0, 0, sw, sh, 0, 0, width, height); const imageData = context.getImageData(0, 0, width, height); const d = imageData.data; const TRANSPARENT_ALPHA_THRESHOLD = 0.05; const DARK_LIGHTNESS_THRESHOLD = 0.4; const LIGHT_LIGHTNESS_THRESHOLD = 0.7; let transparentPixelsCount = 0; let darkPixelsCount = 0; let lightPixelsCount = 0; let i, x, y; let r, g, b, a; let l; for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { i = 4 * (y * width + x); r = d[i + 0]; g = d[i + 1]; b = d[i + 2]; a = d[i + 3]; if (a / 255 < TRANSPARENT_ALPHA_THRESHOLD) { transparentPixelsCount++; } else { l = getSRGBLightness(r, g, b); if (l < DARK_LIGHTNESS_THRESHOLD) { darkPixelsCount++; } if (l > LIGHT_LIGHTNESS_THRESHOLD) { lightPixelsCount++; } } } } const totalPixelsCount = width * height; const opaquePixelsCount = totalPixelsCount - transparentPixelsCount; const DARK_IMAGE_THRESHOLD = 0.7; const LIGHT_IMAGE_THRESHOLD = 0.7; const TRANSPARENT_IMAGE_THRESHOLD = 0.1; return { isDark: darkPixelsCount / opaquePixelsCount >= DARK_IMAGE_THRESHOLD, isLight: lightPixelsCount / opaquePixelsCount >= LIGHT_IMAGE_THRESHOLD, isTransparent: transparentPixelsCount / totalPixelsCount >= TRANSPARENT_IMAGE_THRESHOLD, isLarge }; } let isBlobURLSupported = null; let canUseProxy = false; let blobURLCheckRequested = false; const blobURLCheckAwaiters = []; document.addEventListener( "__darkreader__inlineScriptsAllowed", () => (canUseProxy = true), {once: true} ); async function requestBlobURLCheck() { if (!canUseProxy) { return; } if (blobURLCheckRequested) { return await new Promise((resolve) => blobURLCheckAwaiters.push(resolve) ); } blobURLCheckRequested = true; await new Promise((resolve) => { document.addEventListener( "__darkreader__blobURLCheckResponse", (e) => { isBlobURLSupported = e.detail.blobURLAllowed; resolve(); blobURLCheckAwaiters.forEach((r) => r()); blobURLCheckAwaiters.splice(0); }, {once: true} ); document.dispatchEvent( new CustomEvent("__darkreader__blobURLCheckRequest") ); }); } function isBlobURLCheckResultReady() { return isBlobURLSupported != null || !canUseProxy; } function onCSPError(err) { if (err.blockedURI === "blob") { isBlobURLSupported = false; document.removeEventListener("securitypolicyviolation", onCSPError); } } document.addEventListener("securitypolicyviolation", onCSPError); const objectURLs = new Set(); function getFilteredImageURL({dataURL, width, height}, theme) { if (dataURL.startsWith("data:image/svg+xml")) { dataURL = escapeXML(dataURL); } const matrix = getSVGFilterMatrixValue(theme); const svg = [ ``, "", '', ``, "", "", ``, "" ].join(""); if (!isBlobURLSupported) { return `data:image/svg+xml;base64,${btoa(svg)}`; } const bytes = new Uint8Array(svg.length); for (let i = 0; i < svg.length; i++) { bytes[i] = svg.charCodeAt(i); } const blob = new Blob([bytes], {type: "image/svg+xml"}); const objectURL = URL.createObjectURL(blob); objectURLs.add(objectURL); return objectURL; } const xmlEscapeChars = { "<": "<", ">": ">", "&": "&", "'": "'", '"': """ }; function escapeXML(str) { return str.replace(/[<>&'"]/g, (c) => xmlEscapeChars[c] ?? c); } const dataURLBlobURLs = new Map(); function tryConvertDataURLToBlobSync(dataURL) { const colonIndex = dataURL.indexOf(":"); const semicolonIndex = dataURL.indexOf(";", colonIndex + 1); const commaIndex = dataURL.indexOf(",", semicolonIndex + 1); const encoding = dataURL .substring(semicolonIndex + 1, commaIndex) .toLocaleLowerCase(); const mediaType = dataURL.substring(colonIndex + 1, semicolonIndex); if (encoding !== "base64" || !mediaType) { return null; } const characters = atob(dataURL.substring(commaIndex + 1)); const bytes = new Uint8Array(characters.length); for (let i = 0; i < characters.length; i++) { bytes[i] = characters.charCodeAt(i); } return new Blob([bytes], {type: mediaType}); } async function tryConvertDataURLToBlobURL(dataURL) { if (!isBlobURLSupported) { return null; } const hash = getHashCode(dataURL); let blobURL = dataURLBlobURLs.get(hash); if (blobURL) { return blobURL; } let blob = tryConvertDataURLToBlobSync(dataURL); if (!blob) { const response = await fetch(dataURL); blob = await response.blob(); } blobURL = URL.createObjectURL(blob); dataURLBlobURLs.set(hash, blobURL); return blobURL; } function cleanImageProcessingCache() { imageManager && imageManager.stop(); removeCanvas(); objectURLs.forEach((u) => URL.revokeObjectURL(u)); objectURLs.clear(); dataURLBlobURLs.forEach((u) => URL.revokeObjectURL(u)); dataURLBlobURLs.clear(); } const gradientLength = "gradient".length; const conicGradient = "conic-"; const conicGradientLength = conicGradient.length; const radialGradient = "radial-"; const linearGradient = "linear-"; function parseGradient(value) { const result = []; let index = 0; let startIndex = conicGradient.length; while ((index = value.indexOf("gradient", startIndex)) !== -1) { let typeGradient; [linearGradient, radialGradient, conicGradient].find( (possibleType) => { if (index - possibleType.length >= 0) { const possibleGradient = value.substring( index - possibleType.length, index ); if (possibleGradient === possibleType) { if ( value.slice( index - possibleType.length - 10, index - possibleType.length - 1 ) === "repeating" ) { typeGradient = `repeating-${possibleType}gradient`; return true; } if ( value.slice( index - possibleType.length - 8, index - possibleType.length - 1 ) === "-webkit" ) { typeGradient = `-webkit-${possibleType}gradient`; return true; } typeGradient = `${possibleType}gradient`; return true; } } } ); if (!typeGradient) { break; } const {start, end} = getParenthesesRange( value, index + gradientLength ); const match = value.substring(start + 1, end - 1); startIndex = end + 1 + conicGradientLength; result.push({ typeGradient, match, offset: typeGradient.length + 2, index: index - typeGradient.length + gradientLength, hasComma: true }); } if (result.length) { result[result.length - 1].hasComma = false; } return result; } function getPriority(ruleStyle, property) { return Boolean(ruleStyle && ruleStyle.getPropertyPriority(property)); } function getModifiableCSSDeclaration( property, value, rule, variablesStore, ignoreImageSelectors, isCancelled ) { let modifier = null; if (property.startsWith("--")) { modifier = getVariableModifier( variablesStore, property, value, rule, ignoreImageSelectors, isCancelled ); } else if (value.includes("var(")) { modifier = getVariableDependantModifier( variablesStore, property, value ); } else if (property === "color-scheme") { modifier = getColorSchemeModifier(); } else if (property === "scrollbar-color") { modifier = getScrollbarColorModifier(value); } else if ( (property.includes("color") && property !== "-webkit-print-color-adjust") || property === "fill" || property === "stroke" || property === "stop-color" ) { if ( property.startsWith("border") && property !== "border-color" && value === "initial" ) { const borderSideProp = property.substring( 0, property.length - 6 ); const borderSideVal = rule.style.getPropertyValue(borderSideProp); if ( borderSideVal.startsWith("0px") || borderSideVal === "none" ) { property = borderSideProp; modifier = borderSideVal; } else { modifier = value; } } else { modifier = getColorModifier(property, value, rule); } } else if ( property === "background-image" || property === "list-style-image" ) { modifier = getBgImageModifier( value, rule, ignoreImageSelectors, isCancelled ); } else if (property.includes("shadow")) { modifier = getShadowModifier(value); } if (!modifier) { return null; } return { property, value: modifier, important: getPriority(rule.style, property), sourceValue: value }; } function joinSelectors(...selectors) { return selectors.filter(Boolean).join(", "); } function getModifiedUserAgentStyle(theme, isIFrame, styleSystemControls) { const lines = []; if (!isIFrame) { lines.push("html {"); lines.push( ` background-color: ${modifyBackgroundColor({r: 255, g: 255, b: 255}, theme)} !important;` ); lines.push("}"); } if (theme.mode === 1) { lines.push("html {"); lines.push(` color-scheme: dark !important;`); lines.push("}"); lines.push("iframe {"); lines.push(` color-scheme: dark !important;`); lines.push("}"); } const bgSelectors = joinSelectors( isIFrame ? "" : "html, body", styleSystemControls ? "input, textarea, select, button, dialog" : "" ); if (bgSelectors) { lines.push(`${bgSelectors} {`); lines.push( ` background-color: ${modifyBackgroundColor({r: 255, g: 255, b: 255}, theme)};` ); lines.push("}"); } lines.push( `${joinSelectors("html, body", styleSystemControls ? "input, textarea, select, button" : "")} {` ); lines.push( ` border-color: ${modifyBorderColor({r: 76, g: 76, b: 76}, theme)};` ); lines.push( ` color: ${modifyForegroundColor({r: 0, g: 0, b: 0}, theme)};` ); lines.push("}"); lines.push("a {"); lines.push( ` color: ${modifyForegroundColor({r: 0, g: 64, b: 255}, theme)};` ); lines.push("}"); lines.push("table {"); lines.push( ` border-color: ${modifyBorderColor({r: 128, g: 128, b: 128}, theme)};` ); lines.push("}"); lines.push("mark {"); lines.push( ` color: ${modifyForegroundColor({r: 0, g: 0, b: 0}, theme)};` ); lines.push("}"); lines.push("::placeholder {"); lines.push( ` color: ${modifyForegroundColor({r: 169, g: 169, b: 169}, theme)};` ); lines.push("}"); lines.push("input:-webkit-autofill,"); lines.push("textarea:-webkit-autofill,"); lines.push("select:-webkit-autofill {"); lines.push( ` background-color: ${modifyBackgroundColor({r: 250, g: 255, b: 189}, theme)} !important;` ); lines.push( ` color: ${modifyForegroundColor({r: 0, g: 0, b: 0}, theme)} !important;` ); lines.push("}"); if (theme.scrollbarColor) { lines.push(getModifiedScrollbarStyle(theme)); } if (theme.selectionColor) { lines.push(getModifiedSelectionStyle(theme)); } if (isLayerRuleSupported) { lines.unshift("@layer {"); lines.push("}"); } return lines.join("\n"); } function getSelectionColor(theme) { let backgroundColorSelection; let foregroundColorSelection; if (theme.selectionColor === "auto") { backgroundColorSelection = modifyBackgroundColor( {r: 0, g: 96, b: 212}, {...theme, grayscale: 0} ); foregroundColorSelection = modifyForegroundColor( {r: 255, g: 255, b: 255}, {...theme, grayscale: 0} ); } else { const rgb = parseColorWithCache(theme.selectionColor); const hsl = rgbToHSL(rgb); backgroundColorSelection = theme.selectionColor; if (hsl.l < 0.5) { foregroundColorSelection = "#FFF"; } else { foregroundColorSelection = "#000"; } } return {backgroundColorSelection, foregroundColorSelection}; } function getModifiedSelectionStyle(theme) { const lines = []; const modifiedSelectionColor = getSelectionColor(theme); const backgroundColorSelection = modifiedSelectionColor.backgroundColorSelection; const foregroundColorSelection = modifiedSelectionColor.foregroundColorSelection; ["::selection", "::-moz-selection"].forEach((selection) => { lines.push(`${selection} {`); lines.push( ` background-color: ${backgroundColorSelection} !important;` ); lines.push(` color: ${foregroundColorSelection} !important;`); lines.push("}"); }); return lines.join("\n"); } function getModifiedScrollbarStyle(theme) { let colorTrack; let colorThumb; if (theme.scrollbarColor === "auto") { colorTrack = modifyBackgroundColor({r: 241, g: 241, b: 241}, theme); colorThumb = modifyBackgroundColor({r: 176, g: 176, b: 176}, theme); } else { const rgb = parseColorWithCache(theme.scrollbarColor); const hsl = rgbToHSL(rgb); const darken = (darker) => ({ ...hsl, l: clamp(hsl.l - darker, 0, 1) }); colorTrack = hslToString(darken(0.4)); colorThumb = hslToString(hsl); } return [ `* {`, ` scrollbar-color: ${colorThumb} ${colorTrack};`, `}` ].join("\n"); } function getModifiedFallbackStyle(theme, {strict}) { const factory = defaultFallbackFactory; return factory(theme, {strict}); } function defaultFallbackFactory(theme, {strict}) { const lines = []; lines.push( `html, body, ${strict ? "body :not(iframe)" : "body > :not(iframe)"} {` ); lines.push( ` background-color: ${modifyBackgroundColor({r: 255, g: 255, b: 255}, theme)} !important;` ); lines.push( ` border-color: ${modifyBorderColor({r: 64, g: 64, b: 64}, theme)} !important;` ); lines.push( ` color: ${modifyForegroundColor({r: 0, g: 0, b: 0}, theme)} !important;` ); lines.push("}"); lines.push(`div[style*="background-color: rgb(135, 135, 135)"] {`); lines.push(` background-color: #878787 !important;`); lines.push("}"); return lines.join("\n"); } const unparsableColors = new Set([ "inherit", "transparent", "initial", "currentcolor", "none", "unset", "auto" ]); function getColorModifier(prop, value, rule) { if (unparsableColors.has(value.toLowerCase())) { return value; } const rgb = parseColorWithCache(value); if (!rgb) { return null; } if (prop.includes("background")) { if ( (rule.style.webkitMaskImage && rule.style.webkitMaskImage !== "none") || (rule.style.webkitMask && !rule.style.webkitMask.startsWith("none")) || (rule.style.mask && rule.style.mask !== "none") || (rule.style.getPropertyValue("mask-image") && rule.style.getPropertyValue("mask-image") !== "none") ) { return (theme) => modifyForegroundColor(rgb, theme); } return (theme) => modifyBackgroundColor(rgb, theme); } if (prop.includes("border") || prop.includes("outline")) { return (theme) => modifyBorderColor(rgb, theme); } return (theme) => modifyForegroundColor(rgb, theme); } const imageDetailsCache = new Map(); const awaitingForImageLoading = new Map(); function shouldIgnoreImage(selectorText, selectors) { if (!selectorText || selectors.length === 0) { return false; } if (selectors.some((s) => s === "*")) { return true; } const ruleSelectors = selectorText.split(/,\s*/g); for (let i = 0; i < selectors.length; i++) { const ignoredSelector = selectors[i]; if (ruleSelectors.some((s) => s === ignoredSelector)) { return true; } } return false; } function getBgImageModifier( value, rule, ignoreImageSelectors, isCancelled ) { try { if (shouldIgnoreImage(rule.selectorText, ignoreImageSelectors)) { return value; } const gradients = parseGradient(value); const urls = getMatches(cssURLRegex, value); if (urls.length === 0 && gradients.length === 0) { return value; } const getIndices = (matches) => { let index = 0; return matches.map((match) => { const valueIndex = value.indexOf(match, index); index = valueIndex + match.length; return {match, index: valueIndex}; }); }; const matches = gradients .map((i) => ({type: "gradient", ...i})) .concat( getIndices(urls).map((i) => ({ type: "url", offset: 0, ...i })) ) .sort((a, b) => (a.index > b.index ? 1 : -1)); const getGradientModifier = (gradient) => { const {typeGradient, match, hasComma} = gradient; const partsRegex = /([^\(\),]+(\([^\(\)]*(\([^\(\)]*\)*[^\(\)]*)?\))?([^\(\), ]|( (?!calc)))*),?/g; const colorStopRegex = /^(from|color-stop|to)\(([^\(\)]*?,\s*)?(.*?)\)$/; const parts = getMatches(partsRegex, match, 1).map((part) => { part = part.trim(); let rgb = parseColorWithCache(part); if (rgb) { return (theme) => modifyGradientColor(rgb, theme); } const space = part.lastIndexOf(" "); rgb = parseColorWithCache(part.substring(0, space)); if (rgb) { return (theme) => `${modifyGradientColor(rgb, theme)} ${part.substring(space + 1)}`; } const colorStopMatch = part.match(colorStopRegex); if (colorStopMatch) { rgb = parseColorWithCache(colorStopMatch[3]); if (rgb) { return (theme) => `${colorStopMatch[1]}(${colorStopMatch[2] ? `${colorStopMatch[2]}, ` : ""}${modifyGradientColor(rgb, theme)})`; } } return () => part; }); return (theme) => { return `${typeGradient}(${parts.map((modify) => modify(theme)).join(", ")})${hasComma ? ", " : ""}`; }; }; const getURLModifier = (urlValue) => { let url = getCSSURLValue(urlValue); const isURLEmpty = url.length === 0; const {parentStyleSheet} = rule; const baseURL = parentStyleSheet && parentStyleSheet.href ? getCSSBaseBath(parentStyleSheet.href) : parentStyleSheet?.ownerNode?.baseURI || location.origin; url = getAbsoluteURL(baseURL, url); return async (theme) => { if (isURLEmpty) { return "url('')"; } let imageDetails = null; if (imageDetailsCache.has(url)) { imageDetails = imageDetailsCache.get(url); } else { try { if (!isBlobURLCheckResultReady()) { await requestBlobURLCheck(); } if (awaitingForImageLoading.has(url)) { const awaiters = awaitingForImageLoading.get(url); imageDetails = await new Promise((resolve) => awaiters.push(resolve) ); if (!imageDetails) { return null; } } else { awaitingForImageLoading.set(url, []); imageDetails = await getImageDetails(url); imageDetailsCache.set(url, imageDetails); awaitingForImageLoading .get(url) .forEach((resolve) => resolve(imageDetails) ); awaitingForImageLoading.delete(url); } if (isCancelled()) { return null; } } catch (err) { logWarn(err); if (awaitingForImageLoading.has(url)) { awaitingForImageLoading .get(url) .forEach((resolve) => resolve(null)); awaitingForImageLoading.delete(url); } } } if (imageDetails) { const bgImageValue = getBgImageValue( imageDetails, theme ); if (bgImageValue) { return bgImageValue; } } if (url.startsWith("data:")) { const blobURL = await tryConvertDataURLToBlobURL(url); if (blobURL) { return `url("${blobURL}")`; } } return `url("${url}")`; }; }; const getBgImageValue = (imageDetails, theme) => { const {isDark, isLight, isTransparent, isLarge, width} = imageDetails; let result; const logSrc = imageDetails.src.startsWith("data:") ? "data:" : imageDetails.src; if (isLarge && isLight && !isTransparent && theme.mode === 1) { logInfo(`Hiding large light image ${logSrc}`); result = "none"; } else if ( isDark && isTransparent && theme.mode === 1 && width > 2 ) { logInfo(`Inverting dark image ${logSrc}`); const inverted = getFilteredImageURL(imageDetails, { ...theme, sepia: clamp(theme.sepia + 10, 0, 100) }); result = `url("${inverted}")`; } else if (isLight && !isTransparent && theme.mode === 1) { logInfo(`Dimming light image ${logSrc}`); const dimmed = getFilteredImageURL(imageDetails, theme); result = `url("${dimmed}")`; } else if (theme.mode === 0 && isLight) { logInfo(`Applying filter to image ${logSrc}`); const filtered = getFilteredImageURL(imageDetails, { ...theme, brightness: clamp(theme.brightness - 10, 5, 200), sepia: clamp(theme.sepia + 10, 0, 100) }); result = `url("${filtered}")`; } else { logInfo(`Not modifying the image ${logSrc}`); result = null; } return result; }; const modifiers = []; let matchIndex = 0; let prevHasComma = false; matches.forEach( ({type, match, index, typeGradient, hasComma, offset}, i) => { const matchStart = index; const prefixStart = matchIndex; const matchEnd = matchStart + match.length + offset; matchIndex = matchEnd; if (prefixStart !== matchStart) { if (prevHasComma) { modifiers.push(() => { let betweenValue = value.substring( prefixStart, matchStart ); if (betweenValue[0] === ",") { betweenValue = betweenValue.substring(1); } return betweenValue; }); } else { modifiers.push(() => value.substring(prefixStart, matchStart) ); } } prevHasComma = hasComma || false; if (type === "url") { modifiers.push(getURLModifier(match)); } else if (type === "gradient") { modifiers.push( getGradientModifier({ match, index, typeGradient: typeGradient, hasComma: hasComma || false, offset }) ); } if (i === matches.length - 1) { modifiers.push(() => value.substring(matchEnd)); } } ); return (theme) => { const results = modifiers .filter(Boolean) .map((modify) => modify(theme)); if (results.some((r) => r instanceof Promise)) { return Promise.all(results).then((asyncResults) => { return asyncResults.filter(Boolean).join(""); }); } const combinedResult = results.join(""); if (combinedResult.endsWith(", initial")) { return combinedResult.slice(0, -9); } return combinedResult; }; } catch (err) { return null; } } function getShadowModifierWithInfo(value) { try { let index = 0; const colorMatches = getMatches( /(^|\s)(?!calc)([a-z]+\(.+?\)|#[0-9a-f]+|[a-z]+)(.*?(inset|outset)?($|,))/gi, value, 2 ); let notParsed = 0; const modifiers = colorMatches.map((match, i) => { const prefixIndex = index; const matchIndex = value.indexOf(match, index); const matchEnd = matchIndex + match.length; index = matchEnd; const rgb = parseColorWithCache(match); if (!rgb) { notParsed++; return () => value.substring(prefixIndex, matchEnd); } return (theme) => `${value.substring(prefixIndex, matchIndex)}${modifyShadowColor(rgb, theme)}${i === colorMatches.length - 1 ? value.substring(matchEnd) : ""}`; }); return (theme) => { const modified = modifiers .map((modify) => modify(theme)) .join(""); return { matchesLength: colorMatches.length, unparseableMatchesLength: notParsed, result: modified }; }; } catch (err) { return null; } } function getShadowModifier(value) { const shadowModifier = getShadowModifierWithInfo(value); if (!shadowModifier) { return null; } return (theme) => shadowModifier(theme).result; } function getScrollbarColorModifier(value) { const colorsMatch = value.match( /^\s*([a-z]+(\(.*\))?)\s+([a-z]+(\(.*\))?)\s*$/ ); if (!colorsMatch) { return value; } const thumb = parseColorWithCache(colorsMatch[1]); const track = parseColorWithCache(colorsMatch[3]); if (!thumb || !track) { return null; } return (theme) => `${modifyForegroundColor(thumb, theme)} ${modifyBackgroundColor(thumb, theme)}`; } function getColorSchemeModifier() { return (theme) => (theme.mode === 0 ? "dark light" : "dark"); } function getVariableModifier( variablesStore, prop, value, rule, ignoredImgSelectors, isCancelled ) { return variablesStore.getModifierForVariable({ varName: prop, sourceValue: value, rule, ignoredImgSelectors, isCancelled }); } function getVariableDependantModifier(variablesStore, prop, value) { return variablesStore.getModifierForVarDependant(prop, value); } function cleanModificationCache() { clearColorModificationCache(); imageDetailsCache.clear(); cleanImageProcessingCache(); awaitingForImageLoading.clear(); } const VAR_TYPE_BGCOLOR = 1 << 0; const VAR_TYPE_TEXTCOLOR = 1 << 1; const VAR_TYPE_BORDERCOLOR = 1 << 2; const VAR_TYPE_BGIMG = 1 << 3; class VariablesStore { varTypes = new Map(); rulesQueue = new Set(); inlineStyleQueue = []; definedVars = new Set(); varRefs = new Map(); unknownColorVars = new Set(); unknownBgVars = new Set(); undefinedVars = new Set(); initialVarTypes = new Map(); changedTypeVars = new Set(); typeChangeSubscriptions = new Map(); unstableVarValues = new Map(); onRootVariableDefined; clear() { this.varTypes.clear(); this.rulesQueue.clear(); this.inlineStyleQueue.splice(0); this.definedVars.clear(); this.varRefs.clear(); this.unknownColorVars.clear(); this.unknownBgVars.clear(); this.undefinedVars.clear(); this.initialVarTypes.clear(); this.changedTypeVars.clear(); this.typeChangeSubscriptions.clear(); this.unstableVarValues.clear(); } isVarType(varName, typeNum) { return ( this.varTypes.has(varName) && (this.varTypes.get(varName) & typeNum) > 0 ); } addRulesForMatching(rules) { this.rulesQueue.add(rules); } addInlineStyleForMatching(style) { this.inlineStyleQueue.push(style); } matchVariablesAndDependents() { if ( this.rulesQueue.size === 0 && this.inlineStyleQueue.length === 0 ) { return; } this.changedTypeVars.clear(); this.initialVarTypes = new Map(this.varTypes); this.collectRootVariables(); this.collectVariablesAndVarDep(); this.collectRootVarDependents(); this.varRefs.forEach((refs, v) => { refs.forEach((r) => { if (this.varTypes.has(v)) { this.resolveVariableType(r, this.varTypes.get(v)); } }); }); this.unknownColorVars.forEach((v) => { if (this.unknownBgVars.has(v)) { this.unknownColorVars.delete(v); this.unknownBgVars.delete(v); this.resolveVariableType(v, VAR_TYPE_BGCOLOR); } else if ( this.isVarType( v, VAR_TYPE_BGCOLOR | VAR_TYPE_TEXTCOLOR | VAR_TYPE_BORDERCOLOR ) ) { this.unknownColorVars.delete(v); } else { this.undefinedVars.add(v); } }); this.unknownBgVars.forEach((v) => { const hasColor = this.findVarRef(v, (ref) => { return ( this.unknownColorVars.has(ref) || this.isVarType( ref, VAR_TYPE_BGCOLOR | VAR_TYPE_TEXTCOLOR | VAR_TYPE_BORDERCOLOR ) ); }) != null; if (hasColor) { this.iterateVarRefs(v, (ref) => { this.resolveVariableType(ref, VAR_TYPE_BGCOLOR); }); } else if ( this.isVarType(v, VAR_TYPE_BGCOLOR | VAR_TYPE_BGIMG) ) { this.unknownBgVars.delete(v); } else { this.undefinedVars.add(v); } }); this.changedTypeVars.forEach((varName) => { if (this.typeChangeSubscriptions.has(varName)) { this.typeChangeSubscriptions .get(varName) .forEach((callback) => { callback(); }); } }); this.changedTypeVars.clear(); } getModifierForVariable(options) { return (theme) => { const { varName, sourceValue, rule, ignoredImgSelectors, isCancelled } = options; const getDeclarations = () => { const declarations = []; const addModifiedValue = ( typeNum, varNameWrapper, colorModifier ) => { if (!this.isVarType(varName, typeNum)) { return; } const property = varNameWrapper(varName); let modifiedValue; if (isVarDependant(sourceValue)) { if (isConstructedColorVar(sourceValue)) { let value = insertVarValues( sourceValue, this.unstableVarValues ); if (!value) { value = typeNum === VAR_TYPE_BGCOLOR ? "#ffffff" : "#000000"; } modifiedValue = colorModifier(value, theme); } else { modifiedValue = replaceCSSVariablesNames( sourceValue, (v) => varNameWrapper(v), (fallback) => colorModifier(fallback, theme) ); } } else { modifiedValue = colorModifier(sourceValue, theme); } declarations.push({ property, value: modifiedValue }); }; addModifiedValue( VAR_TYPE_BGCOLOR, wrapBgColorVariableName, tryModifyBgColor ); addModifiedValue( VAR_TYPE_TEXTCOLOR, wrapTextColorVariableName, tryModifyTextColor ); addModifiedValue( VAR_TYPE_BORDERCOLOR, wrapBorderColorVariableName, tryModifyBorderColor ); if (this.isVarType(varName, VAR_TYPE_BGIMG)) { const property = wrapBgImgVariableName(varName); let modifiedValue = sourceValue; if (isVarDependant(sourceValue)) { modifiedValue = replaceCSSVariablesNames( sourceValue, (v) => wrapBgColorVariableName(v), (fallback) => tryModifyBgColor(fallback, theme) ); } const bgModifier = getBgImageModifier( modifiedValue, rule, ignoredImgSelectors, isCancelled ); modifiedValue = typeof bgModifier === "function" ? bgModifier(theme) : bgModifier; declarations.push({ property, value: modifiedValue }); } return declarations; }; const callbacks = new Set(); const addListener = (onTypeChange) => { const callback = () => { const decs = getDeclarations(); onTypeChange(decs); }; callbacks.add(callback); this.subscribeForVarTypeChange(varName, callback); }; const removeListeners = () => { callbacks.forEach((callback) => { this.unsubscribeFromVariableTypeChanges( varName, callback ); }); }; return { declarations: getDeclarations(), onTypeChange: {addListener, removeListeners} }; }; } getModifierForVarDependant(property, sourceValue) { const isConstructedColor = sourceValue.match(/^\s*(rgb|hsl)a?\(/); const isSimpleConstructedColor = sourceValue.match( /^rgba?\(var\(--[\-_A-Za-z0-9]+\)(\s*,?\/?\s*0?\.\d+)?\)$/ ); if (isConstructedColor && !isSimpleConstructedColor) { const isBg = property.startsWith("background"); const isText = isTextColorProperty(property); return (theme) => { let value = insertVarValues( sourceValue, this.unstableVarValues ); if (!value) { value = isBg ? "#ffffff" : "#000000"; } const modifier = isBg ? tryModifyBgColor : isText ? tryModifyTextColor : tryModifyBorderColor; return modifier(value, theme); }; } if ( property === "background-color" || (isSimpleConstructedColor && property === "background") ) { return (theme) => { const defaultFallback = tryModifyBgColor( isConstructedColor ? "255, 255, 255" : "#ffffff", theme ); return replaceCSSVariablesNames( sourceValue, (v) => wrapBgColorVariableName(v), (fallback) => tryModifyBgColor(fallback, theme), defaultFallback ); }; } if (isTextColorProperty(property)) { return (theme) => { const defaultFallback = tryModifyTextColor( isConstructedColor ? "0, 0, 0" : "#000000", theme ); return replaceCSSVariablesNames( sourceValue, (v) => wrapTextColorVariableName(v), (fallback) => tryModifyTextColor(fallback, theme), defaultFallback ); }; } if ( property === "background" || property === "background-image" || property === "box-shadow" ) { return (theme) => { const unknownVars = new Set(); const modify = () => { const variableReplaced = replaceCSSVariablesNames( sourceValue, (v) => { if (this.isVarType(v, VAR_TYPE_BGCOLOR)) { return wrapBgColorVariableName(v); } if (this.isVarType(v, VAR_TYPE_BGIMG)) { return wrapBgImgVariableName(v); } unknownVars.add(v); return v; }, (fallback) => tryModifyBgColor(fallback, theme) ); if (property === "box-shadow") { const shadowModifier = getShadowModifierWithInfo(variableReplaced); const modifiedShadow = shadowModifier(theme); if ( modifiedShadow.unparseableMatchesLength !== modifiedShadow.matchesLength ) { return modifiedShadow.result; } } return variableReplaced; }; const modified = modify(); if (unknownVars.size > 0) { const isFallbackResolved = modified.match( /^var\(.*?, (var\(--darkreader-bg--.*\))|(#[0-9A-Fa-f]+)|([a-z]+)|(rgba?\(.+\))|(hsla?\(.+\))\)$/ ); if (isFallbackResolved) { return modified; } return new Promise((resolve) => { for (const unknownVar of unknownVars.values()) { const callback = () => { this.unsubscribeFromVariableTypeChanges( unknownVar, callback ); const newValue = modify(); resolve(newValue); }; this.subscribeForVarTypeChange( unknownVar, callback ); } }); } return modified; }; } if ( property.startsWith("border") || property.startsWith("outline") ) { return (theme) => { return replaceCSSVariablesNames( sourceValue, (v) => wrapBorderColorVariableName(v), (fallback) => tryModifyBorderColor(fallback, theme) ); }; } return null; } subscribeForVarTypeChange(varName, callback) { if (!this.typeChangeSubscriptions.has(varName)) { this.typeChangeSubscriptions.set(varName, new Set()); } const rootStore = this.typeChangeSubscriptions.get(varName); if (!rootStore.has(callback)) { rootStore.add(callback); } } unsubscribeFromVariableTypeChanges(varName, callback) { if (this.typeChangeSubscriptions.has(varName)) { this.typeChangeSubscriptions.get(varName).delete(callback); } } collectVariablesAndVarDep() { this.rulesQueue.forEach((rules) => { iterateCSSRules(rules, (rule) => { if (rule.style) { this.collectVarsFromCSSDeclarations(rule.style); } }); }); this.inlineStyleQueue.forEach((style) => { this.collectVarsFromCSSDeclarations(style); }); this.rulesQueue.clear(); this.inlineStyleQueue.splice(0); } collectVarsFromCSSDeclarations(style) { iterateCSSDeclarations(style, (property, value) => { if (isVariable(property)) { this.inspectVariable(property, value); } if (isVarDependant(value)) { this.inspectVarDependant(property, value); } }); } shouldProcessRootVariables() { return ( this.rulesQueue.size > 0 && document.documentElement.getAttribute("style")?.includes("--") ); } collectRootVariables() { if (!this.shouldProcessRootVariables()) { return; } iterateCSSDeclarations( document.documentElement.style, (property, value) => { if (isVariable(property)) { this.inspectVariable(property, value); } } ); } inspectVariable(varName, value) { this.unstableVarValues.set(varName, value); if (isVarDependant(value) && isConstructedColorVar(value)) { this.unknownColorVars.add(varName); this.definedVars.add(varName); } if (this.definedVars.has(varName)) { return; } this.definedVars.add(varName); const isColor = Boolean( value.match(rawRGBSpaceRegex) || value.match(rawRGBCommaRegex) || parseColorWithCache(value) ); if (isColor) { this.unknownColorVars.add(varName); } else if ( value.includes("url(") || value.includes("linear-gradient(") || value.includes("radial-gradient(") ) { this.resolveVariableType(varName, VAR_TYPE_BGIMG); } } resolveVariableType(varName, typeNum) { const initialType = this.initialVarTypes.get(varName) || 0; const currentType = this.varTypes.get(varName) || 0; const newType = currentType | typeNum; this.varTypes.set(varName, newType); if (newType !== initialType || this.undefinedVars.has(varName)) { this.changedTypeVars.add(varName); this.undefinedVars.delete(varName); } this.unknownColorVars.delete(varName); this.unknownBgVars.delete(varName); } collectRootVarDependents() { if (!this.shouldProcessRootVariables()) { return; } iterateCSSDeclarations( document.documentElement.style, (property, value) => { if (isVarDependant(value)) { this.inspectVarDependant(property, value); } } ); } inspectVarDependant(property, value) { if (isVariable(property)) { this.iterateVarDeps(value, (ref) => { if (!this.varRefs.has(property)) { this.varRefs.set(property, new Set()); } this.varRefs.get(property).add(ref); }); } else if ( property === "background-color" || property === "box-shadow" ) { this.iterateVarDeps(value, (v) => this.resolveVariableType(v, VAR_TYPE_BGCOLOR) ); } else if (isTextColorProperty(property)) { this.iterateVarDeps(value, (v) => this.resolveVariableType(v, VAR_TYPE_TEXTCOLOR) ); } else if ( property.startsWith("border") || property.startsWith("outline") ) { this.iterateVarDeps(value, (v) => this.resolveVariableType(v, VAR_TYPE_BORDERCOLOR) ); } else if ( property === "background" || property === "background-image" ) { this.iterateVarDeps(value, (v) => { if (this.isVarType(v, VAR_TYPE_BGCOLOR | VAR_TYPE_BGIMG)) { return; } const isBgColor = this.findVarRef(v, (ref) => { return ( this.unknownColorVars.has(ref) || this.isVarType( ref, VAR_TYPE_BGCOLOR | VAR_TYPE_TEXTCOLOR | VAR_TYPE_BORDERCOLOR ) ); }) != null; this.iterateVarRefs(v, (ref) => { if (isBgColor) { this.resolveVariableType(ref, VAR_TYPE_BGCOLOR); } else { this.unknownBgVars.add(ref); } }); }); } } iterateVarDeps(value, iterator) { const varDeps = new Set(); iterateVarDependencies(value, (v) => varDeps.add(v)); varDeps.forEach((v) => iterator(v)); } findVarRef(varName, iterator, stack = new Set()) { if (stack.has(varName)) { return null; } stack.add(varName); const result = iterator(varName); if (result) { return varName; } const refs = this.varRefs.get(varName); if (!refs || refs.size === 0) { return null; } for (const ref of refs) { const found = this.findVarRef(ref, iterator, stack); if (found) { return found; } } return null; } iterateVarRefs(varName, iterator) { this.findVarRef(varName, (ref) => { iterator(ref); return false; }); } setOnRootVariableChange(callback) { this.onRootVariableDefined = callback; } putRootVars(styleElement, theme) { const sheet = styleElement.sheet; if (sheet.cssRules.length > 0) { sheet.deleteRule(0); } const declarations = new Map(); iterateCSSDeclarations( document.documentElement.style, (property, value) => { if (isVariable(property)) { if (this.isVarType(property, VAR_TYPE_BGCOLOR)) { declarations.set( wrapBgColorVariableName(property), tryModifyBgColor(value, theme) ); } if (this.isVarType(property, VAR_TYPE_TEXTCOLOR)) { declarations.set( wrapTextColorVariableName(property), tryModifyTextColor(value, theme) ); } if (this.isVarType(property, VAR_TYPE_BORDERCOLOR)) { declarations.set( wrapBorderColorVariableName(property), tryModifyBorderColor(value, theme) ); } this.subscribeForVarTypeChange( property, this.onRootVariableDefined ); } } ); const cssLines = []; cssLines.push(":root {"); for (const [property, value] of declarations) { cssLines.push(` ${property}: ${value};`); } cssLines.push("}"); const cssText = cssLines.join("\n"); sheet.insertRule(cssText); } } const variablesStore = new VariablesStore(); function getVariableRange(input, searchStart = 0) { const start = input.indexOf("var(", searchStart); if (start >= 0) { const range = getParenthesesRange(input, start + 3); if (range) { return {start, end: range.end}; } } return null; } function getVariablesMatches(input) { const ranges = []; let i = 0; let range; while ((range = getVariableRange(input, i))) { const {start, end} = range; ranges.push({start, end, value: input.substring(start, end)}); i = range.end + 1; } return ranges; } function replaceVariablesMatches(input, replacer) { const matches = getVariablesMatches(input); const matchesCount = matches.length; if (matchesCount === 0) { return input; } const inputLength = input.length; const replacements = matches.map((m) => replacer(m.value, matches.length) ); const parts = []; parts.push(input.substring(0, matches[0].start)); for (let i = 0; i < matchesCount; i++) { parts.push(replacements[i]); const start = matches[i].end; const end = i < matchesCount - 1 ? matches[i + 1].start : inputLength; parts.push(input.substring(start, end)); } return parts.join(""); } function getVariableNameAndFallback(match) { const commaIndex = match.indexOf(","); let name; let fallback; if (commaIndex >= 0) { name = match.substring(4, commaIndex).trim(); fallback = match.substring(commaIndex + 1, match.length - 1).trim(); } else { name = match.substring(4, match.length - 1).trim(); fallback = ""; } return {name, fallback}; } function replaceCSSVariablesNames( value, nameReplacer, fallbackReplacer, finalFallback ) { const matchReplacer = (match) => { const {name, fallback} = getVariableNameAndFallback(match); const newName = nameReplacer(name); if (!fallback) { if (finalFallback) { return `var(${newName}, ${finalFallback})`; } return `var(${newName})`; } let newFallback; if (isVarDependant(fallback)) { newFallback = replaceCSSVariablesNames( fallback, nameReplacer, fallbackReplacer ); } else if (fallbackReplacer) { newFallback = fallbackReplacer(fallback); } else { newFallback = fallback; } return `var(${newName}, ${newFallback})`; }; return replaceVariablesMatches(value, matchReplacer); } function iterateVarDependencies(value, iterator) { replaceCSSVariablesNames(value, (varName) => { iterator(varName); return varName; }); } function wrapBgColorVariableName(name) { return `--darkreader-bg${name}`; } function wrapTextColorVariableName(name) { return `--darkreader-text${name}`; } function wrapBorderColorVariableName(name) { return `--darkreader-border${name}`; } function wrapBgImgVariableName(name) { return `--darkreader-bgimg${name}`; } function isVariable(property) { return property.startsWith("--"); } function isVarDependant(value) { return value.includes("var("); } function isConstructedColorVar(value) { return ( value.match(/^\s*(rgb|hsl)a?\(/) || value.match(/^(((\d{1,3})|(var\([\-_A-Za-z0-9]+\))),?\s*?){3}$/) ); } function isTextColorProperty(property) { return ( property === "color" || property === "caret-color" || property === "-webkit-text-fill-color" ); } const rawRGBSpaceRegex = /^(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})$/; const rawRGBCommaRegex = /^(\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})$/; function parseRawColorValue(input) { const match = input.match(rawRGBSpaceRegex) ?? input.match(rawRGBCommaRegex); if (match) { const color = `rgb(${match[1]}, ${match[2]}, ${match[3]})`; return {isRaw: true, color}; } return {isRaw: false, color: input}; } function handleRawColorValue(input, theme, modifyFunction) { const {isRaw, color} = parseRawColorValue(input); const rgb = parseColorWithCache(color); if (rgb) { const outputColor = modifyFunction(rgb, theme); if (isRaw) { const outputInRGB = parseColorWithCache(outputColor); return outputInRGB ? `${outputInRGB.r}, ${outputInRGB.g}, ${outputInRGB.b}` : outputColor; } return outputColor; } return color; } function tryModifyBgColor(color, theme) { return handleRawColorValue(color, theme, modifyBackgroundColor); } function tryModifyTextColor(color, theme) { return handleRawColorValue(color, theme, modifyForegroundColor); } function tryModifyBorderColor(color, theme) { return handleRawColorValue(color, theme, modifyBorderColor); } function insertVarValues(source, varValues, fullStack = new Set()) { let containsUnresolvedVar = false; const matchReplacer = (match, count) => { const {name, fallback} = getVariableNameAndFallback(match); const stack = count > 1 ? new Set(fullStack) : fullStack; if (stack.has(name)) { containsUnresolvedVar = true; return null; } stack.add(name); const varValue = varValues.get(name) || fallback; let inserted = null; if (varValue) { if (isVarDependant(varValue)) { inserted = insertVarValues(varValue, varValues, stack); } else { inserted = varValue; } } if (!inserted) { containsUnresolvedVar = true; return null; } return inserted; }; const replaced = replaceVariablesMatches(source, matchReplacer); if (containsUnresolvedVar) { return null; } return replaced; } const overrides$1 = { "background-color": { customProp: "--darkreader-inline-bgcolor", cssProp: "background-color", dataAttr: "data-darkreader-inline-bgcolor" }, "background-image": { customProp: "--darkreader-inline-bgimage", cssProp: "background-image", dataAttr: "data-darkreader-inline-bgimage" }, "border-color": { customProp: "--darkreader-inline-border", cssProp: "border-color", dataAttr: "data-darkreader-inline-border" }, "border-bottom-color": { customProp: "--darkreader-inline-border-bottom", cssProp: "border-bottom-color", dataAttr: "data-darkreader-inline-border-bottom" }, "border-left-color": { customProp: "--darkreader-inline-border-left", cssProp: "border-left-color", dataAttr: "data-darkreader-inline-border-left" }, "border-right-color": { customProp: "--darkreader-inline-border-right", cssProp: "border-right-color", dataAttr: "data-darkreader-inline-border-right" }, "border-top-color": { customProp: "--darkreader-inline-border-top", cssProp: "border-top-color", dataAttr: "data-darkreader-inline-border-top" }, "box-shadow": { customProp: "--darkreader-inline-boxshadow", cssProp: "box-shadow", dataAttr: "data-darkreader-inline-boxshadow" }, "color": { customProp: "--darkreader-inline-color", cssProp: "color", dataAttr: "data-darkreader-inline-color" }, "fill": { customProp: "--darkreader-inline-fill", cssProp: "fill", dataAttr: "data-darkreader-inline-fill" }, "stroke": { customProp: "--darkreader-inline-stroke", cssProp: "stroke", dataAttr: "data-darkreader-inline-stroke" }, "outline-color": { customProp: "--darkreader-inline-outline", cssProp: "outline-color", dataAttr: "data-darkreader-inline-outline" }, "stop-color": { customProp: "--darkreader-inline-stopcolor", cssProp: "stop-color", dataAttr: "data-darkreader-inline-stopcolor" } }; const shorthandOverrides = { background: { customProp: "--darkreader-inline-bg", cssProp: "background", dataAttr: "data-darkreader-inline-bg" } }; const overridesList = Object.values(overrides$1); const normalizedPropList = {}; overridesList.forEach( ({cssProp, customProp}) => (normalizedPropList[customProp] = cssProp) ); const INLINE_STYLE_ATTRS = [ "style", "fill", "stop-color", "stroke", "bgcolor", "color", "background" ]; const INLINE_STYLE_SELECTOR = INLINE_STYLE_ATTRS.map( (attr) => `[${attr}]` ).join(", "); function getInlineOverrideStyle() { const allOverrides = overridesList.concat( Object.values(shorthandOverrides) ); return allOverrides .map(({dataAttr, customProp, cssProp}) => { return [ `[${dataAttr}] {`, ` ${cssProp}: var(${customProp}) !important;`, "}" ].join("\n"); }) .concat([ "[data-darkreader-inline-invert] {", " filter: invert(100%) hue-rotate(180deg);", "}" ]) .join("\n"); } function getInlineStyleElements(root) { const results = []; if (root instanceof Element && root.matches(INLINE_STYLE_SELECTOR)) { results.push(root); } if ( root instanceof Element || (isShadowDomSupported && root instanceof ShadowRoot) || root instanceof Document ) { push(results, root.querySelectorAll(INLINE_STYLE_SELECTOR)); } return results; } const treeObservers = new Map(); const attrObservers = new Map(); function watchForInlineStyles(elementStyleDidChange, shadowRootDiscovered) { deepWatchForInlineStyles( document, elementStyleDidChange, shadowRootDiscovered ); iterateShadowHosts(document.documentElement, (host) => { deepWatchForInlineStyles( host.shadowRoot, elementStyleDidChange, shadowRootDiscovered ); }); } function deepWatchForInlineStyles( root, elementStyleDidChange, shadowRootDiscovered ) { if (treeObservers.has(root)) { treeObservers.get(root).disconnect(); attrObservers.get(root).disconnect(); } const discoveredNodes = new WeakSet(); function discoverNodes(node) { getInlineStyleElements(node).forEach((el) => { if (discoveredNodes.has(el)) { return; } discoveredNodes.add(el); elementStyleDidChange(el); }); iterateShadowHosts(node, (n) => { if (discoveredNodes.has(node)) { return; } discoveredNodes.add(node); shadowRootDiscovered(n.shadowRoot); deepWatchForInlineStyles( n.shadowRoot, elementStyleDidChange, shadowRootDiscovered ); }); variablesStore.matchVariablesAndDependents(); } const treeObserver = createOptimizedTreeObserver(root, { onMinorMutations: (_root, {additions}) => { additions.forEach((added) => discoverNodes(added)); }, onHugeMutations: () => { discoverNodes(root); } }); treeObservers.set(root, treeObserver); let attemptCount = 0; let start = null; const ATTEMPTS_INTERVAL = getDuration({seconds: 10}); const RETRY_TIMEOUT = getDuration({seconds: 2}); const MAX_ATTEMPTS_COUNT = 50; let cache = []; let timeoutId = null; const handleAttributeMutations = throttle((mutations) => { const handledTargets = new Set(); mutations.forEach((m) => { const target = m.target; if (handledTargets.has(target)) { return; } if (INLINE_STYLE_ATTRS.includes(m.attributeName)) { handledTargets.add(target); elementStyleDidChange(target); } }); variablesStore.matchVariablesAndDependents(); }); const attrObserver = new MutationObserver((mutations) => { if (timeoutId) { cache.push(...mutations); return; } attemptCount++; const now = Date.now(); if (start == null) { start = now; } else if (attemptCount >= MAX_ATTEMPTS_COUNT) { if (now - start < ATTEMPTS_INTERVAL) { timeoutId = setTimeout(() => { start = null; attemptCount = 0; timeoutId = null; const attributeCache = cache; cache = []; handleAttributeMutations(attributeCache); }, RETRY_TIMEOUT); cache.push(...mutations); return; } start = now; attemptCount = 1; } handleAttributeMutations(mutations); }); attrObserver.observe(root, { attributes: true, attributeFilter: INLINE_STYLE_ATTRS.concat( overridesList.map(({dataAttr}) => dataAttr) ), subtree: true }); attrObservers.set(root, attrObserver); } function stopWatchingForInlineStyles() { treeObservers.forEach((o) => o.disconnect()); attrObservers.forEach((o) => o.disconnect()); treeObservers.clear(); attrObservers.clear(); } const inlineStyleCache = new WeakMap(); const svgInversionCache = new WeakSet(); const svgAnalysisConditionCache = new WeakMap(); const themeProps = ["brightness", "contrast", "grayscale", "sepia", "mode"]; function shouldAnalyzeSVGAsImage(svg) { if (svgAnalysisConditionCache.has(svg)) { return svgAnalysisConditionCache.get(svg); } const shouldAnalyze = Boolean( svg && (svg.getAttribute("class")?.includes("logo") || svg.parentElement?.getAttribute("class")?.includes("logo")) ); svgAnalysisConditionCache.set(svg, shouldAnalyze); return shouldAnalyze; } function getInlineStyleCacheKey(el, theme) { return INLINE_STYLE_ATTRS.map( (attr) => `${attr}="${el.getAttribute(attr)}"` ) .concat(themeProps.map((prop) => `${prop}="${theme[prop]}"`)) .join(" "); } function shouldIgnoreInlineStyle(element, selectors) { for (let i = 0, len = selectors.length; i < len; i++) { const ingnoredSelector = selectors[i]; if (element.matches(ingnoredSelector)) { return true; } } return false; } function overrideInlineStyle( element, theme, ignoreInlineSelectors, ignoreImageSelectors ) { const cacheKey = getInlineStyleCacheKey(element, theme); if (cacheKey === inlineStyleCache.get(element)) { return; } const unsetProps = new Set(Object.keys(overrides$1)); function setCustomProp(targetCSSProp, modifierCSSProp, cssVal) { const mod = getModifiableCSSDeclaration( modifierCSSProp, cssVal, {style: element.style}, variablesStore, ignoreImageSelectors, null ); if (!mod) { return; } function setStaticValue(value) { const {customProp, dataAttr} = overrides$1[targetCSSProp] ?? shorthandOverrides[targetCSSProp]; element.style.setProperty(customProp, value); if (!element.hasAttribute(dataAttr)) { element.setAttribute(dataAttr, ""); } unsetProps.delete(targetCSSProp); } function setVarDeclaration(mod) { let prevDeclarations = []; function setProps(declarations) { prevDeclarations.forEach(({property}) => { element.style.removeProperty(property); }); declarations.forEach(({property, value}) => { if (!(value instanceof Promise)) { element.style.setProperty(property, value); } }); prevDeclarations = declarations; } setProps(mod.declarations); mod.onTypeChange.addListener(setProps); } function setAsyncValue(promise, sourceValue) { promise.then((value) => { if ( value && targetCSSProp === "background" && value.startsWith("var(--darkreader-bg--") ) { setStaticValue(value); } if (value && targetCSSProp === "background-image") { if ( (element === document.documentElement || element === document.body) && value === sourceValue ) { value = "none"; } setStaticValue(value); } inlineStyleCache.set( element, getInlineStyleCacheKey(element, theme) ); }); } const value = typeof mod.value === "function" ? mod.value(theme) : mod.value; if (typeof value === "string") { setStaticValue(value); } else if (value instanceof Promise) { setAsyncValue(value, cssVal); } else if (typeof value === "object") { setVarDeclaration(value); } } if (ignoreInlineSelectors.length > 0) { if (shouldIgnoreInlineStyle(element, ignoreInlineSelectors)) { unsetProps.forEach((cssProp) => { element.removeAttribute(overrides$1[cssProp].dataAttr); }); return; } } const isSVGElement = element instanceof SVGElement; const svg = isSVGElement ? (element.ownerSVGElement ?? (element instanceof SVGSVGElement ? element : null)) : null; if (isSVGElement && theme.mode === 1 && svg) { if (svgInversionCache.has(svg)) { return; } if (shouldAnalyzeSVGAsImage(svg)) { svgInversionCache.add(svg); const analyzeSVGAsImage = () => { let svgString = svg.outerHTML; svgString = svgString.replaceAll( '', "" ); const dataURL = `data:image/svg+xml;base64,${btoa(svgString)}`; getImageDetails(dataURL).then((details) => { if ( (details.isDark && details.isTransparent) || (details.isLarge && details.isLight && !details.isTransparent) ) { svg.setAttribute( "data-darkreader-inline-invert", "" ); } else { svg.removeAttribute( "data-darkreader-inline-invert" ); } }); }; analyzeSVGAsImage(); if (!isDOMReady()) { addDOMReadyListener(analyzeSVGAsImage); } return; } } if (element.hasAttribute("bgcolor")) { let value = element.getAttribute("bgcolor"); if ( value.match(/^[0-9a-f]{3}$/i) || value.match(/^[0-9a-f]{6}$/i) ) { value = `#${value}`; } setCustomProp("background-color", "background-color", value); } if ( (element === document.documentElement || element === document.body) && element.hasAttribute("background") ) { const url = getAbsoluteURL( location.href, element.getAttribute("background") ?? "" ); const value = `url("${url}")`; setCustomProp("background-image", "background-image", value); } if (element.hasAttribute("color") && element.rel !== "mask-icon") { let value = element.getAttribute("color"); if ( value.match(/^[0-9a-f]{3}$/i) || value.match(/^[0-9a-f]{6}$/i) ) { value = `#${value}`; } setCustomProp("color", "color", value); } if (isSVGElement) { if (element.hasAttribute("fill")) { const SMALL_SVG_LIMIT = 32; const value = element.getAttribute("fill"); if (value !== "none") { if (!(element instanceof SVGTextElement)) { const handleSVGElement = () => { const {width, height} = element.getBoundingClientRect(); const isBg = width > SMALL_SVG_LIMIT || height > SMALL_SVG_LIMIT; setCustomProp( "fill", isBg ? "background-color" : "color", value ); }; if (isReadyStateComplete()) { handleSVGElement(); } else { addReadyStateCompleteListener(handleSVGElement); } } else { setCustomProp("fill", "color", value); } } } if (element.hasAttribute("stop-color")) { setCustomProp( "stop-color", "background-color", element.getAttribute("stop-color") ); } } if (element.hasAttribute("stroke")) { const value = element.getAttribute("stroke"); setCustomProp( "stroke", element instanceof SVGLineElement || element instanceof SVGTextElement ? "border-color" : "color", value ); } element.style && iterateCSSDeclarations(element.style, (property, value) => { if (property === "background-image" && value.includes("url")) { if ( element === document.documentElement || element === document.body ) { setCustomProp(property, property, value); } return; } if ( overrides$1.hasOwnProperty(property) || (property.startsWith("--") && !normalizedPropList[property]) ) { setCustomProp(property, property, value); } else if ( property === "background" && value.includes("var(") ) { setCustomProp("background", "background", value); } else { const overriddenProp = normalizedPropList[property]; if ( overriddenProp && !element.style.getPropertyValue(overriddenProp) && !element.hasAttribute(overriddenProp) ) { if ( overriddenProp === "background-color" && element.hasAttribute("bgcolor") ) { return; } element.style.setProperty(property, ""); } } }); if ( element.style && element instanceof SVGTextElement && element.style.fill ) { setCustomProp( "fill", "color", element.style.getPropertyValue("fill") ); } if (element.getAttribute("style")?.includes("--")) { variablesStore.addInlineStyleForMatching(element.style); } forEach(unsetProps, (cssProp) => { element.removeAttribute(overrides$1[cssProp].dataAttr); }); inlineStyleCache.set(element, getInlineStyleCacheKey(element, theme)); } const metaThemeColorName = "theme-color"; const metaThemeColorSelector = `meta[name="${metaThemeColorName}"]`; let srcMetaThemeColor = null; let observer = null; function changeMetaThemeColor(meta, theme) { srcMetaThemeColor = srcMetaThemeColor || meta.content; const color = parseColorWithCache(srcMetaThemeColor); if (!color) { return; } meta.content = modifyBackgroundColor(color, theme); } function changeMetaThemeColorWhenAvailable(theme) { const meta = document.querySelector(metaThemeColorSelector); if (meta) { changeMetaThemeColor(meta, theme); } else { if (observer) { observer.disconnect(); } observer = new MutationObserver((mutations) => { loop: for (let i = 0; i < mutations.length; i++) { const {addedNodes} = mutations[i]; for (let j = 0; j < addedNodes.length; j++) { const node = addedNodes[j]; if ( node instanceof HTMLMetaElement && node.name === metaThemeColorName ) { observer.disconnect(); observer = null; changeMetaThemeColor(node, theme); break loop; } } } }); observer.observe(document.head, {childList: true}); } } function restoreMetaThemeColor() { if (observer) { observer.disconnect(); observer = null; } const meta = document.querySelector(metaThemeColorSelector); if (meta && srcMetaThemeColor) { meta.content = srcMetaThemeColor; } } const cssCommentsRegex = /\/\*[\s\S]*?\*\//g; function removeCSSComments(cssText) { return cssText.replace(cssCommentsRegex, ""); } const themeCacheKeys = [ "mode", "brightness", "contrast", "grayscale", "sepia", "darkSchemeBackgroundColor", "darkSchemeTextColor", "lightSchemeBackgroundColor", "lightSchemeTextColor" ]; function getThemeKey(theme) { let resultKey = ""; themeCacheKeys.forEach((key) => { resultKey += `${key}:${theme[key]};`; }); return resultKey; } const asyncQueue = createAsyncTasksQueue(); function createStyleSheetModifier() { let renderId = 0; function getStyleRuleHash(rule) { let cssText = rule.cssText; if (isMediaRule(rule.parentRule)) { cssText = `${rule.parentRule.media.mediaText} { ${cssText} }`; } return getHashCode(cssText); } const rulesTextCache = new Set(); const rulesModCache = new Map(); const varTypeChangeCleaners = new Set(); let prevFilterKey = null; let hasNonLoadedLink = false; let wasRebuilt = false; function shouldRebuildStyle() { return hasNonLoadedLink && !wasRebuilt; } function modifySheet(options) { const rules = options.sourceCSSRules; const { theme, ignoreImageAnalysis, force, prepareSheet, isAsyncCancelled } = options; let rulesChanged = rulesModCache.size === 0; const notFoundCacheKeys = new Set(rulesModCache.keys()); const themeKey = getThemeKey(theme); const themeChanged = themeKey !== prevFilterKey; if (hasNonLoadedLink) { wasRebuilt = true; } const modRules = []; iterateCSSRules( rules, (rule) => { const hash = getStyleRuleHash(rule); let textDiffersFromPrev = false; notFoundCacheKeys.delete(hash); if (!rulesTextCache.has(hash)) { rulesTextCache.add(hash); textDiffersFromPrev = true; } if (textDiffersFromPrev) { rulesChanged = true; } else { modRules.push(rulesModCache.get(hash)); return; } if (rule.style.all === "revert") { return; } const modDecs = []; rule.style && iterateCSSDeclarations( rule.style, (property, value) => { const mod = getModifiableCSSDeclaration( property, value, rule, variablesStore, ignoreImageAnalysis, isAsyncCancelled ); if (mod) { modDecs.push(mod); } } ); let modRule = null; if (modDecs.length > 0) { const parentRule = rule.parentRule; modRule = { selector: rule.selectorText, declarations: modDecs, parentRule }; modRules.push(modRule); } rulesModCache.set(hash, modRule); }, () => { hasNonLoadedLink = true; } ); notFoundCacheKeys.forEach((key) => { rulesTextCache.delete(key); rulesModCache.delete(key); }); prevFilterKey = themeKey; if (!force && !rulesChanged && !themeChanged) { return; } renderId++; function setRule(target, index, rule) { const {selector, declarations} = rule; let selectorText = selector; const emptyIsWhereSelector = selector.startsWith(":is(") && (selector.includes(":is()") || selector.includes(":where()") || (selector.includes(":where(") && selector.includes(":-moz"))); const viewTransitionSelector = selector.includes("::view-transition-"); if (emptyIsWhereSelector || viewTransitionSelector) { selectorText = ".darkreader-unsupported-selector"; } let ruleText = `${selectorText} {`; for (const dec of declarations) { const {property, value, important} = dec; if (value) { ruleText += ` ${property}: ${value}${important ? " !important" : ""};`; } } ruleText += " }"; target.insertRule(ruleText, index); } const asyncDeclarations = new Map(); const varDeclarations = new Map(); let asyncDeclarationCounter = 0; let varDeclarationCounter = 0; const rootReadyGroup = {rule: null, rules: [], isGroup: true}; const groupRefs = new WeakMap(); function getGroup(rule) { if (rule == null) { return rootReadyGroup; } if (groupRefs.has(rule)) { return groupRefs.get(rule); } const group = {rule, rules: [], isGroup: true}; groupRefs.set(rule, group); const parentGroup = getGroup(rule.parentRule); parentGroup.rules.push(group); return group; } varTypeChangeCleaners.forEach((clear) => clear()); varTypeChangeCleaners.clear(); modRules .filter((r) => r) .forEach(({selector, declarations, parentRule}) => { const group = getGroup(parentRule); const readyStyleRule = { selector, declarations: [], isGroup: false }; const readyDeclarations = readyStyleRule.declarations; group.rules.push(readyStyleRule); function handleAsyncDeclaration( property, modified, important, sourceValue ) { const asyncKey = ++asyncDeclarationCounter; const asyncDeclaration = { property, value: null, important, asyncKey, sourceValue }; readyDeclarations.push(asyncDeclaration); const currentRenderId = renderId; modified.then((asyncValue) => { if ( !asyncValue || isAsyncCancelled() || currentRenderId !== renderId ) { return; } asyncDeclaration.value = asyncValue; asyncQueue.add(() => { if ( isAsyncCancelled() || currentRenderId !== renderId ) { return; } rebuildAsyncRule(asyncKey); }); }); } function handleVarDeclarations( property, modified, important, sourceValue ) { const {declarations: varDecs, onTypeChange} = modified; const varKey = ++varDeclarationCounter; const currentRenderId = renderId; const initialIndex = readyDeclarations.length; let oldDecs = []; if (varDecs.length === 0) { const tempDec = { property, value: sourceValue, important, sourceValue, varKey }; readyDeclarations.push(tempDec); oldDecs = [tempDec]; } varDecs.forEach((mod) => { if (mod.value instanceof Promise) { handleAsyncDeclaration( mod.property, mod.value, important, sourceValue ); } else { const readyDec = { property: mod.property, value: mod.value, important, sourceValue, varKey }; readyDeclarations.push(readyDec); oldDecs.push(readyDec); } }); onTypeChange.addListener((newDecs) => { if ( isAsyncCancelled() || currentRenderId !== renderId ) { return; } const readyVarDecs = newDecs.map((mod) => { return { property: mod.property, value: mod.value, important, sourceValue, varKey }; }); const index = readyDeclarations.indexOf( oldDecs[0], initialIndex ); readyDeclarations.splice( index, oldDecs.length, ...readyVarDecs ); oldDecs = readyVarDecs; rebuildVarRule(varKey); }); varTypeChangeCleaners.add(() => onTypeChange.removeListeners() ); } declarations.forEach( ({property, value, important, sourceValue}) => { if (typeof value === "function") { const modified = value(theme); if (modified instanceof Promise) { handleAsyncDeclaration( property, modified, important, sourceValue ); } else if (property.startsWith("--")) { handleVarDeclarations( property, modified, important, sourceValue ); } else { readyDeclarations.push({ property, value: modified, important, sourceValue }); } } else { readyDeclarations.push({ property, value, important, sourceValue }); } } ); }); const sheet = prepareSheet(); function buildStyleSheet() { function createTarget(group, parent) { const {rule} = group; if (isMediaRule(rule)) { const {media} = rule; const index = parent.cssRules.length; parent.insertRule( `@media ${media.mediaText} {}`, index ); return parent.cssRules[index]; } if (isLayerRule(rule)) { const {name} = rule; const index = parent.cssRules.length; parent.insertRule(`@layer ${name} {}`, index); return parent.cssRules[index]; } return parent; } function iterateReadyRules(group, target, styleIterator) { group.rules.forEach((r) => { if (r.isGroup) { const t = createTarget(r, target); iterateReadyRules(r, t, styleIterator); } else { styleIterator(r, target); } }); } iterateReadyRules(rootReadyGroup, sheet, (rule, target) => { const index = target.cssRules.length; rule.declarations.forEach(({asyncKey, varKey}) => { if (asyncKey != null) { asyncDeclarations.set(asyncKey, { rule, target, index }); } if (varKey != null) { varDeclarations.set(varKey, {rule, target, index}); } }); setRule(target, index, rule); }); } function rebuildAsyncRule(key) { const {rule, target, index} = asyncDeclarations.get(key); target.deleteRule(index); setRule(target, index, rule); asyncDeclarations.delete(key); } function rebuildVarRule(key) { const {rule, target, index} = varDeclarations.get(key); target.deleteRule(index); setRule(target, index, rule); } buildStyleSheet(); } return {modifySheet, shouldRebuildStyle}; } let canUseSheetProxy$1 = false; document.addEventListener( "__darkreader__inlineScriptsAllowed", () => (canUseSheetProxy$1 = true), {once: true} ); function createSheetWatcher( element, safeGetSheetRules, callback, isCancelled ) { let rafSheetWatcher = null; function watchForSheetChanges() { watchForSheetChangesUsingProxy(); if (!(canUseSheetProxy$1 && element.sheet)) { rafSheetWatcher = createRAFSheetWatcher( element, safeGetSheetRules, callback, isCancelled ); rafSheetWatcher.start(); } } let areSheetChangesPending = false; function onSheetChange() { canUseSheetProxy$1 = true; rafSheetWatcher?.stop(); if (areSheetChangesPending) { return; } function handleSheetChanges() { areSheetChangesPending = false; if (isCancelled()) { return; } callback(); } areSheetChangesPending = true; queueMicrotask(handleSheetChanges); } function watchForSheetChangesUsingProxy() { element.addEventListener( "__darkreader__updateSheet", onSheetChange ); } function stopWatchingForSheetChangesUsingProxy() { element.removeEventListener( "__darkreader__updateSheet", onSheetChange ); } function stopWatchingForSheetChanges() { stopWatchingForSheetChangesUsingProxy(); rafSheetWatcher?.stop(); } return { start: watchForSheetChanges, stop: stopWatchingForSheetChanges }; } function createRAFSheetWatcher( element, safeGetSheetRules, callback, isCancelled ) { let rulesChangeKey = null; let rulesCheckFrameId = null; function getRulesChangeKey() { const rules = safeGetSheetRules(); return rules ? rules.length : null; } function didRulesKeyChange() { return getRulesChangeKey() !== rulesChangeKey; } function watchForSheetChangesUsingRAF() { rulesChangeKey = getRulesChangeKey(); stopWatchingForSheetChangesUsingRAF(); const checkForUpdate = () => { const cancelled = isCancelled(); if (!cancelled && didRulesKeyChange()) { rulesChangeKey = getRulesChangeKey(); callback(); } if (cancelled || (canUseSheetProxy$1 && element.sheet)) { stopWatchingForSheetChangesUsingRAF(); return; } rulesCheckFrameId = requestAnimationFrame(checkForUpdate); }; checkForUpdate(); } function stopWatchingForSheetChangesUsingRAF() { rulesCheckFrameId && cancelAnimationFrame(rulesCheckFrameId); } return { start: watchForSheetChangesUsingRAF, stop: stopWatchingForSheetChangesUsingRAF }; } const STYLE_SELECTOR = 'style, link[rel*="stylesheet" i]:not([disabled])'; function isFontsGoogleApiStyle(element) { if (!element.href) { return false; } try { const elementURL = new URL(element.href); return elementURL.hostname === "fonts.googleapis.com"; } catch (err) { logInfo(`Couldn't construct ${element.href} as URL`); return false; } } const hostsBreakingOnSVGStyleOverride = ["www.onet.pl"]; function shouldManageStyle(element) { return ( (element instanceof HTMLStyleElement || (element instanceof SVGStyleElement && !hostsBreakingOnSVGStyleOverride.includes( location.hostname )) || (element instanceof HTMLLinkElement && Boolean(element.rel) && element.rel.toLowerCase().includes("stylesheet") && Boolean(element.href) && !element.disabled && true && !isFontsGoogleApiStyle(element))) && !element.classList.contains("darkreader") && element.media.toLowerCase() !== "print" && !element.classList.contains("stylus") ); } function getManageableStyles(node, results = [], deep = true) { if (shouldManageStyle(node)) { results.push(node); } else if ( node instanceof Element || (isShadowDomSupported && node instanceof ShadowRoot) || node === document ) { forEach(node.querySelectorAll(STYLE_SELECTOR), (style) => getManageableStyles(style, results, false) ); if (deep) { iterateShadowHosts(node, (host) => getManageableStyles(host.shadowRoot, results, false) ); } } return results; } const syncStyleSet = new WeakSet(); const corsStyleSet = new WeakSet(); let loadingLinkCounter = 0; const rejectorsForLoadingLinks = new Map(); function cleanLoadingLinks() { rejectorsForLoadingLinks.clear(); } function manageStyle(element, {update, loadingStart, loadingEnd}) { const prevStyles = []; let next = element; while ( (next = next.nextElementSibling) && next.matches(".darkreader") ) { prevStyles.push(next); } let corsCopy = prevStyles.find( (el) => el.matches(".darkreader--cors") && !corsStyleSet.has(el) ) || null; let syncStyle = prevStyles.find( (el) => el.matches(".darkreader--sync") && !syncStyleSet.has(el) ) || null; let corsCopyPositionWatcher = null; let syncStylePositionWatcher = null; let cancelAsyncOperations = false; let isOverrideEmpty = true; const isAsyncCancelled = () => cancelAsyncOperations; const sheetModifier = createStyleSheetModifier(); const observer = new MutationObserver((mutations) => { if ( mutations.some((m) => m.type === "characterData") && containsCSSImport() ) { const cssText = (element.textContent ?? "").trim(); createOrUpdateCORSCopy(cssText, location.href).then(update); } else { update(); } }); const observerOptions = { attributes: true, childList: true, subtree: true, characterData: true }; function containsCSSImport() { if (!(element instanceof HTMLStyleElement)) { return false; } const cssText = removeCSSComments(element.textContent ?? "").trim(); return cssText.match(cssImportRegex); } function hasImports(cssRules, checkCrossOrigin) { let result = false; if (cssRules) { let rule; cssRulesLoop: for ( let i = 0, len = cssRules.length; i < len; i++ ) { rule = cssRules[i]; if (rule.href) { if (checkCrossOrigin) { if ( !rule.href.startsWith( "https://fonts.googleapis.com/" ) && rule.href.startsWith("http") && !rule.href.startsWith(location.origin) ) { result = true; break cssRulesLoop; } } else { result = true; break cssRulesLoop; } } } } return result; } function getRulesSync() { if (corsCopy) { return corsCopy.sheet.cssRules; } if (containsCSSImport()) { return null; } const cssRules = safeGetSheetRules(); if ( element instanceof HTMLLinkElement && !isRelativeHrefOnAbsolutePath(element.href) && hasImports(cssRules, false) ) { return null; } if (hasImports(cssRules, true)) { return null; } return cssRules; } function insertStyle() { if (corsCopy) { if (element.nextSibling !== corsCopy) { element.parentNode.insertBefore( corsCopy, element.nextSibling ); } if (corsCopy.nextSibling !== syncStyle) { element.parentNode.insertBefore( syncStyle, corsCopy.nextSibling ); } } else if (element.nextSibling !== syncStyle) { element.parentNode.insertBefore(syncStyle, element.nextSibling); } } function createSyncStyle() { syncStyle = element instanceof SVGStyleElement ? document.createElementNS( "http://www.w3.org/2000/svg", "style" ) : document.createElement("style"); syncStyle.classList.add("darkreader"); syncStyle.classList.add("darkreader--sync"); syncStyle.media = "screen"; if (element.title) { syncStyle.title = element.title; } syncStyleSet.add(syncStyle); } let isLoadingRules = false; let wasLoadingError = false; const loadingLinkId = ++loadingLinkCounter; async function getRulesAsync() { let cssText; let cssBasePath; if (element instanceof HTMLLinkElement) { let [cssRules, accessError] = getRulesOrError(); if ( (!cssRules && !accessError) || isStillLoadingError(accessError) ) { try { logInfo( `Linkelement ${loadingLinkId} is not loaded yet and thus will be await for`, element ); await linkLoading(element, loadingLinkId); } catch (err) { wasLoadingError = true; } if (cancelAsyncOperations) { return null; } [cssRules, accessError] = getRulesOrError(); } if (cssRules) { if (!hasImports(cssRules, false)) { return cssRules; } } cssText = await loadText(element.href); cssBasePath = getCSSBaseBath(element.href); if (cancelAsyncOperations) { return null; } } else if (containsCSSImport()) { cssText = element.textContent.trim(); cssBasePath = getCSSBaseBath(location.href); } else { return null; } await createOrUpdateCORSCopy(cssText, cssBasePath); if (corsCopy) { return corsCopy.sheet.cssRules; } return null; } async function createOrUpdateCORSCopy(cssText, cssBasePath) { if (cssText) { try { const fullCSSText = await replaceCSSImports( cssText, cssBasePath ); if (corsCopy) { if ( (corsCopy.textContent?.length ?? 0) < fullCSSText.length ) { corsCopy.textContent = fullCSSText; } } else { corsCopy = createCORSCopy(element, fullCSSText); } } catch (err) {} if (corsCopy) { corsCopyPositionWatcher = watchForNodePosition( corsCopy, "prev-sibling" ); } } } function details(options) { const rules = getRulesSync(); if (!rules) { if (options.secondRound) { return null; } if (isLoadingRules || wasLoadingError) { return null; } isLoadingRules = true; loadingStart(); getRulesAsync() .then((results) => { isLoadingRules = false; loadingEnd(); if (results) { update(); } }) .catch((err) => { isLoadingRules = false; loadingEnd(); }); return null; } return {rules}; } let forceRenderStyle = false; function render(theme, ignoreImageAnalysis) { const rules = getRulesSync(); if (!rules) { return; } cancelAsyncOperations = false; function removeCSSRulesFromSheet(sheet) { if (!sheet) { return; } for (let i = sheet.cssRules.length - 1; i >= 0; i--) { sheet.deleteRule(i); } } function prepareOverridesSheet() { if (!syncStyle) { createSyncStyle(); } syncStylePositionWatcher && syncStylePositionWatcher.stop(); insertStyle(); if (syncStyle.sheet == null) { syncStyle.textContent = ""; } const sheet = syncStyle.sheet; removeCSSRulesFromSheet(sheet); if (syncStylePositionWatcher) { syncStylePositionWatcher.run(); } else { syncStylePositionWatcher = watchForNodePosition( syncStyle, "prev-sibling", () => { forceRenderStyle = true; buildOverrides(); } ); } return syncStyle.sheet; } function buildOverrides() { const force = forceRenderStyle; forceRenderStyle = false; sheetModifier.modifySheet({ prepareSheet: prepareOverridesSheet, sourceCSSRules: rules, theme, ignoreImageAnalysis, force, isAsyncCancelled }); isOverrideEmpty = syncStyle.sheet.cssRules.length === 0; if (sheetModifier.shouldRebuildStyle()) { addReadyStateCompleteListener(() => update()); } } buildOverrides(); } function getRulesOrError() { try { if (element.sheet == null) { return [null, null]; } return [element.sheet.cssRules, null]; } catch (err) { return [null, err]; } } function isStillLoadingError(error) { return error && error.message && error.message.includes("loading"); } function safeGetSheetRules() { const [cssRules, err] = getRulesOrError(); if (err) { return null; } return cssRules; } const sheetChangeWatcher = createSheetWatcher( element, safeGetSheetRules, update, isAsyncCancelled ); function pause() { observer.disconnect(); cancelAsyncOperations = true; corsCopyPositionWatcher && corsCopyPositionWatcher.stop(); syncStylePositionWatcher && syncStylePositionWatcher.stop(); sheetChangeWatcher.stop(); } function destroy() { pause(); removeNode(corsCopy); removeNode(syncStyle); loadingEnd(); if (rejectorsForLoadingLinks.has(loadingLinkId)) { const reject = rejectorsForLoadingLinks.get(loadingLinkId); rejectorsForLoadingLinks.delete(loadingLinkId); reject && reject(); } } function watch() { observer.observe(element, observerOptions); if (element instanceof HTMLStyleElement) { sheetChangeWatcher.start(); } } const maxMoveCount = 10; let moveCount = 0; function restore() { if (!syncStyle) { return; } moveCount++; if (moveCount > maxMoveCount) { return; } insertStyle(); corsCopyPositionWatcher && corsCopyPositionWatcher.skip(); syncStylePositionWatcher && syncStylePositionWatcher.skip(); if (!isOverrideEmpty) { forceRenderStyle = true; update(); } } return { details, render, pause, destroy, watch, restore }; } async function linkLoading(link, loadingId) { return new Promise((resolve, reject) => { const cleanUp = () => { link.removeEventListener("load", onLoad); link.removeEventListener("error", onError); rejectorsForLoadingLinks.delete(loadingId); }; const onLoad = () => { cleanUp(); resolve(); }; const onError = () => { cleanUp(); reject( `Linkelement ${loadingId} couldn't be loaded. ${link.href}` ); }; rejectorsForLoadingLinks.set(loadingId, () => { cleanUp(); reject(); }); link.addEventListener("load", onLoad, {passive: true}); link.addEventListener("error", onError, {passive: true}); if (!link.href) { onError(); } }); } function getCSSImportURL(importDeclaration) { return getCSSURLValue( importDeclaration .substring(7) .trim() .replace(/;$/, "") .replace(/screen$/, "") ); } async function loadText(url) { if (url.startsWith("data:")) { return await (await fetch(url)).text(); } const parsedURL = new URL(url); if (parsedURL.origin === location.origin) { return await loadAsText(url, "text/css", location.origin); } return await bgFetch({ url, responseType: "text", mimeType: "text/css", origin: location.origin }); } async function replaceCSSImports(cssText, basePath, cache = new Map()) { cssText = removeCSSComments(cssText); cssText = replaceCSSFontFace(cssText); cssText = replaceCSSRelativeURLsWithAbsolute(cssText, basePath); const importMatches = getMatches(cssImportRegex, cssText); for (const match of importMatches) { const importURL = getCSSImportURL(match); const absoluteURL = getAbsoluteURL(basePath, importURL); let importedCSS; if (cache.has(absoluteURL)) { importedCSS = cache.get(absoluteURL); } else { try { importedCSS = await loadText(absoluteURL); cache.set(absoluteURL, importedCSS); importedCSS = await replaceCSSImports( importedCSS, getCSSBaseBath(absoluteURL), cache ); } catch (err) { importedCSS = ""; } } cssText = cssText.split(match).join(importedCSS); } cssText = cssText.trim(); return cssText; } function createCORSCopy(srcElement, cssText) { if (!cssText) { return null; } const cors = document.createElement("style"); cors.classList.add("darkreader"); cors.classList.add("darkreader--cors"); cors.media = "screen"; cors.textContent = cssText; srcElement.parentNode.insertBefore(cors, srcElement.nextSibling); cors.sheet.disabled = true; corsStyleSet.add(cors); return cors; } const definedCustomElements = new Set(); const undefinedGroups = new Map(); let elementsDefinitionCallback; function isCustomElement(element) { if (element.tagName.includes("-") || element.getAttribute("is")) { return true; } return false; } function recordUndefinedElement(element) { let tag = element.tagName.toLowerCase(); if (!tag.includes("-")) { const extendedTag = element.getAttribute("is"); if (extendedTag) { tag = extendedTag; } else { return; } } if (!undefinedGroups.has(tag)) { undefinedGroups.set(tag, new Set()); customElementsWhenDefined(tag).then(() => { if (elementsDefinitionCallback) { const elements = undefinedGroups.get(tag); undefinedGroups.delete(tag); elementsDefinitionCallback(Array.from(elements)); } }); } undefinedGroups.get(tag).add(element); } function collectUndefinedElements(root) { if (!isDefinedSelectorSupported) { return; } forEach( root.querySelectorAll(":not(:defined)"), recordUndefinedElement ); } let canOptimizeUsingProxy = false; document.addEventListener( "__darkreader__inlineScriptsAllowed", () => { canOptimizeUsingProxy = true; }, {once: true, passive: true} ); const resolvers = new Map(); function handleIsDefined(e) { canOptimizeUsingProxy = true; const tag = e.detail.tag; definedCustomElements.add(tag); if (resolvers.has(tag)) { const r = resolvers.get(tag); resolvers.delete(tag); r.forEach((r) => r()); } } async function customElementsWhenDefined(tag) { if (definedCustomElements.has(tag)) { return; } return new Promise((resolve) => { if ( window.customElements && typeof customElements.whenDefined === "function" ) { customElements.whenDefined(tag).then(() => resolve()); } else if (canOptimizeUsingProxy) { if (resolvers.has(tag)) { resolvers.get(tag).push(resolve); } else { resolvers.set(tag, [resolve]); } document.dispatchEvent( new CustomEvent("__darkreader__addUndefinedResolver", { detail: {tag} }) ); } else { const checkIfDefined = () => { const elements = undefinedGroups.get(tag); if (elements && elements.size > 0) { if ( elements.values().next().value.matches(":defined") ) { resolve(); } else { requestAnimationFrame(checkIfDefined); } } }; requestAnimationFrame(checkIfDefined); } }); } function watchWhenCustomElementsDefined(callback) { elementsDefinitionCallback = callback; } function unsubscribeFromDefineCustomElements() { elementsDefinitionCallback = null; undefinedGroups.clear(); document.removeEventListener( "__darkreader__isDefined", handleIsDefined ); } const observers = []; let observedRoots; function watchForStylePositions( currentStyles, update, shadowRootDiscovered ) { stopWatchingForStylePositions(); const prevStylesByRoot = new WeakMap(); const getPrevStyles = (root) => { if (!prevStylesByRoot.has(root)) { prevStylesByRoot.set(root, new Set()); } return prevStylesByRoot.get(root); }; currentStyles.forEach((node) => { let root = node; while ((root = root.parentNode)) { if ( root === document || root.nodeType === Node.DOCUMENT_FRAGMENT_NODE ) { const prevStyles = getPrevStyles(root); prevStyles.add(node); break; } } }); const prevStyleSiblings = new WeakMap(); const nextStyleSiblings = new WeakMap(); function saveStylePosition(style) { prevStyleSiblings.set(style, style.previousElementSibling); nextStyleSiblings.set(style, style.nextElementSibling); } function forgetStylePosition(style) { prevStyleSiblings.delete(style); nextStyleSiblings.delete(style); } function didStylePositionChange(style) { return ( style.previousElementSibling !== prevStyleSiblings.get(style) || style.nextElementSibling !== nextStyleSiblings.get(style) ); } currentStyles.forEach(saveStylePosition); function handleStyleOperations(root, operations) { const {createdStyles, removedStyles, movedStyles} = operations; createdStyles.forEach((s) => saveStylePosition(s)); movedStyles.forEach((s) => saveStylePosition(s)); removedStyles.forEach((s) => forgetStylePosition(s)); const prevStyles = getPrevStyles(root); createdStyles.forEach((s) => prevStyles.add(s)); removedStyles.forEach((s) => prevStyles.delete(s)); if ( createdStyles.size + removedStyles.size + movedStyles.size > 0 ) { update({ created: Array.from(createdStyles), removed: Array.from(removedStyles), moved: Array.from(movedStyles), updated: [] }); } } function handleMinorTreeMutations(root, {additions, moves, deletions}) { const createdStyles = new Set(); const removedStyles = new Set(); const movedStyles = new Set(); additions.forEach((node) => getManageableStyles(node).forEach((style) => createdStyles.add(style) ) ); deletions.forEach((node) => getManageableStyles(node).forEach((style) => removedStyles.add(style) ) ); moves.forEach((node) => getManageableStyles(node).forEach((style) => movedStyles.add(style) ) ); handleStyleOperations(root, { createdStyles, removedStyles, movedStyles }); additions.forEach((n) => { deepObserve(n); collectUndefinedElements(n); }); additions.forEach( (node) => isCustomElement(node) && recordUndefinedElement(node) ); } function handleHugeTreeMutations(root) { const styles = new Set(getManageableStyles(root)); const createdStyles = new Set(); const removedStyles = new Set(); const movedStyles = new Set(); const prevStyles = getPrevStyles(root); styles.forEach((s) => { if (!prevStyles.has(s)) { createdStyles.add(s); } }); prevStyles.forEach((s) => { if (!styles.has(s)) { removedStyles.add(s); } }); styles.forEach((s) => { if ( !createdStyles.has(s) && !removedStyles.has(s) && didStylePositionChange(s) ) { movedStyles.add(s); } }); handleStyleOperations(root, { createdStyles, removedStyles, movedStyles }); deepObserve(root); collectUndefinedElements(root); } function handleAttributeMutations(mutations) { const updatedStyles = new Set(); const removedStyles = new Set(); mutations.forEach((m) => { const {target} = m; if (target.isConnected) { if (shouldManageStyle(target)) { updatedStyles.add(target); } else if ( target instanceof HTMLLinkElement && target.disabled ) { removedStyles.add(target); } } }); if (updatedStyles.size + removedStyles.size > 0) { update({ updated: Array.from(updatedStyles), created: [], removed: Array.from(removedStyles), moved: [] }); } } function observe(root) { if (observedRoots.has(root)) { return; } const treeObserver = createOptimizedTreeObserver(root, { onMinorMutations: handleMinorTreeMutations, onHugeMutations: handleHugeTreeMutations }); const attrObserver = new MutationObserver(handleAttributeMutations); attrObserver.observe(root, { attributeFilter: ["rel", "disabled", "media", "href"], subtree: true }); observers.push(treeObserver, attrObserver); observedRoots.add(root); } function subscribeForShadowRootChanges(node) { const {shadowRoot} = node; if (shadowRoot == null || observedRoots.has(shadowRoot)) { return; } observe(shadowRoot); shadowRootDiscovered(shadowRoot); } function deepObserve(node) { iterateShadowHosts(node, subscribeForShadowRootChanges); } observe(document); deepObserve(document.documentElement); watchWhenCustomElementsDefined((hosts) => { const newStyles = []; hosts.forEach((host) => push(newStyles, getManageableStyles(host.shadowRoot)) ); update({created: newStyles, updated: [], removed: [], moved: []}); hosts.forEach((host) => { const {shadowRoot} = host; if (shadowRoot == null) { return; } subscribeForShadowRootChanges(host); deepObserve(shadowRoot); collectUndefinedElements(shadowRoot); }); }); document.addEventListener("__darkreader__isDefined", handleIsDefined); collectUndefinedElements(document); } function resetObservers() { observers.forEach((o) => o.disconnect()); observers.splice(0, observers.length); observedRoots = new WeakSet(); } function stopWatchingForStylePositions() { resetObservers(); unsubscribeFromDefineCustomElements(); } function watchForStyleChanges(currentStyles, update, shadowRootDiscovered) { watchForStylePositions(currentStyles, update, shadowRootDiscovered); } function stopWatchingForStyleChanges() { stopWatchingForStylePositions(); } let canUseSheetProxy = false; document.addEventListener( "__darkreader__inlineScriptsAllowed", () => (canUseSheetProxy = true), {once: true} ); const overrides = new WeakSet(); const overridesBySource = new WeakMap(); function canHaveAdoptedStyleSheets(node) { return Array.isArray(node.adoptedStyleSheets); } function createAdoptedStyleSheetOverride(node) { let cancelAsyncOperations = false; function iterateSourceSheets(iterator) { node.adoptedStyleSheets.forEach((sheet) => { if (!overrides.has(sheet)) { iterator(sheet); } }); } function injectSheet(sheet, override) { const newSheets = [...node.adoptedStyleSheets]; const sheetIndex = newSheets.indexOf(sheet); const overrideIndex = newSheets.indexOf(override); if (overrideIndex >= 0) { newSheets.splice(overrideIndex, 1); } newSheets.splice(sheetIndex + 1, 0, override); node.adoptedStyleSheets = newSheets; } function clear() { const newSheets = [...node.adoptedStyleSheets]; for (let i = newSheets.length - 1; i >= 0; i--) { const sheet = newSheets[i]; if (overrides.has(sheet)) { newSheets.splice(i, 1); } } if (node.adoptedStyleSheets.length !== newSheets.length) { node.adoptedStyleSheets = newSheets; } sourceSheets = new WeakSet(); sourceDeclarations = new WeakSet(); } const cleaners = []; function destroy() { cleaners.forEach((c) => c()); cleaners.splice(0); cancelAsyncOperations = true; clear(); if (frameId) { cancelAnimationFrame(frameId); frameId = null; } } let rulesChangeKey = 0; function getRulesChangeKey() { let count = 0; iterateSourceSheets((sheet) => { count += sheet.cssRules.length; }); if (count === 1) { const rule = node.adoptedStyleSheets[0].cssRules[0]; return rule instanceof CSSStyleRule ? rule.style.length : count; } return count; } let sourceSheets = new WeakSet(); let sourceDeclarations = new WeakSet(); function render(theme, ignoreImageAnalysis) { clear(); for (let i = node.adoptedStyleSheets.length - 1; i >= 0; i--) { const sheet = node.adoptedStyleSheets[i]; if (overrides.has(sheet)) { continue; } sourceSheets.add(sheet); const readyOverride = overridesBySource.get(sheet); if (readyOverride) { rulesChangeKey = getRulesChangeKey(); injectSheet(sheet, readyOverride); continue; } const rules = sheet.cssRules; const override = new CSSStyleSheet(); overridesBySource.set(sheet, override); iterateCSSRules(rules, (rule) => sourceDeclarations.add(rule.style) ); const prepareSheet = () => { for (let i = override.cssRules.length - 1; i >= 0; i--) { override.deleteRule(i); } override.insertRule("#__darkreader__adoptedOverride {}"); injectSheet(sheet, override); overrides.add(override); return override; }; const sheetModifier = createStyleSheetModifier(); sheetModifier.modifySheet({ prepareSheet, sourceCSSRules: rules, theme, ignoreImageAnalysis, force: false, isAsyncCancelled: () => cancelAsyncOperations }); } rulesChangeKey = getRulesChangeKey(); } let callbackRequested = false; function handleArrayChange(callback) { if (callbackRequested) { return; } callbackRequested = true; queueMicrotask(() => { callbackRequested = false; const sheets = node.adoptedStyleSheets.filter( (s) => !overrides.has(s) ); sheets.forEach((sheet) => overridesBySource.delete(sheet)); callback(sheets); }); } function checkForUpdates() { return getRulesChangeKey() !== rulesChangeKey; } let frameId = null; function watchUsingRAF(callback) { frameId = requestAnimationFrame(() => { if (canUseSheetProxy) { return; } if (checkForUpdates()) { handleArrayChange(callback); } watchUsingRAF(callback); }); } function addSheetChangeEventListener(type, listener) { node.addEventListener(type, listener); cleaners.push(() => node.removeEventListener(type, listener)); } function watch(callback) { const onAdoptedSheetsChange = () => { canUseSheetProxy = true; handleArrayChange(callback); }; addSheetChangeEventListener( "__darkreader__adoptedStyleSheetsChange", onAdoptedSheetsChange ); addSheetChangeEventListener( "__darkreader__adoptedStyleSheetChange", onAdoptedSheetsChange ); addSheetChangeEventListener( "__darkreader__adoptedStyleDeclarationChange", onAdoptedSheetsChange ); if (canUseSheetProxy) { return; } watchUsingRAF(callback); } return { render, destroy, watch }; } let documentVisibilityListener = null; let documentIsVisible_ = !document.hidden; const listenerOptions = { capture: true, passive: true }; function watchForDocumentVisibility() { document.addEventListener( "visibilitychange", documentVisibilityListener, listenerOptions ); window.addEventListener( "pageshow", documentVisibilityListener, listenerOptions ); window.addEventListener( "focus", documentVisibilityListener, listenerOptions ); } function stopWatchingForDocumentVisibility() { document.removeEventListener( "visibilitychange", documentVisibilityListener, listenerOptions ); window.removeEventListener( "pageshow", documentVisibilityListener, listenerOptions ); window.removeEventListener( "focus", documentVisibilityListener, listenerOptions ); } function setDocumentVisibilityListener(callback) { const alreadyWatching = Boolean(documentVisibilityListener); documentVisibilityListener = () => { if (!document.hidden) { removeDocumentVisibilityListener(); callback(); documentIsVisible_ = true; } }; if (!alreadyWatching) { watchForDocumentVisibility(); } } function removeDocumentVisibilityListener() { stopWatchingForDocumentVisibility(); documentVisibilityListener = null; } function documentIsVisible() { return documentIsVisible_; } function findRelevantFix(documentURL, fixes) { if ( !Array.isArray(fixes) || fixes.length === 0 || fixes[0].url[0] !== "*" ) { return null; } let maxSpecificity = 0; let maxSpecificityIndex = null; for (let i = 1; i < fixes.length; i++) { if (isURLInList(documentURL, fixes[i].url)) { const specificity = fixes[i].url[0].length; if ( maxSpecificityIndex === null || maxSpecificity < specificity ) { maxSpecificity = specificity; maxSpecificityIndex = i; } } } return maxSpecificityIndex; } function combineFixes(fixes) { if (fixes.length === 0 || fixes[0].url[0] !== "*") { return null; } function combineArrays(arrays) { return arrays.filter(Boolean).flat(); } return { url: [], invert: combineArrays(fixes.map((fix) => fix.invert)), css: fixes .map((fix) => fix.css) .filter(Boolean) .join("\n"), ignoreInlineStyle: combineArrays( fixes.map((fix) => fix.ignoreInlineStyle) ), ignoreImageAnalysis: combineArrays( fixes.map((fix) => fix.ignoreImageAnalysis) ), disableStyleSheetsProxy: fixes.some( (fix) => fix.disableStyleSheetsProxy ), disableCustomElementRegistryProxy: fixes.some( (fix) => fix.disableCustomElementRegistryProxy ) }; } const INSTANCE_ID = generateUID(); const styleManagers = new Map(); const adoptedStyleManagers = []; const adoptedStyleFallbacks = new Map(); let theme = null; let fixes = null; let isIFrame = null; let ignoredImageAnalysisSelectors = []; let ignoredInlineSelectors = []; function createOrUpdateStyle(className, root = document.head || document) { let element = root.querySelector(`.${className}`); if (!element) { element = document.createElement("style"); element.classList.add("darkreader"); element.classList.add(className); element.media = "screen"; element.textContent = ""; } return element; } const nodePositionWatchers = new Map(); function setupNodePositionWatcher(node, alias) { nodePositionWatchers.has(alias) && nodePositionWatchers.get(alias).stop(); nodePositionWatchers.set(alias, watchForNodePosition(node, "head")); } function stopStylePositionWatchers() { forEach(nodePositionWatchers.values(), (watcher) => watcher.stop()); nodePositionWatchers.clear(); } function createStaticStyleOverrides() { const fallbackStyle = createOrUpdateStyle( "darkreader--fallback", document ); fallbackStyle.textContent = getModifiedFallbackStyle(theme, { strict: true }); document.head.insertBefore(fallbackStyle, document.head.firstChild); setupNodePositionWatcher(fallbackStyle, "fallback"); const userAgentStyle = createOrUpdateStyle("darkreader--user-agent"); userAgentStyle.textContent = getModifiedUserAgentStyle( theme, isIFrame, theme.styleSystemControls ); document.head.insertBefore(userAgentStyle, fallbackStyle.nextSibling); setupNodePositionWatcher(userAgentStyle, "user-agent"); const textStyle = createOrUpdateStyle("darkreader--text"); if (theme.useFont || theme.textStroke > 0) { textStyle.textContent = createTextStyle(theme); } else { textStyle.textContent = ""; } document.head.insertBefore(textStyle, fallbackStyle.nextSibling); setupNodePositionWatcher(textStyle, "text"); const invertStyle = createOrUpdateStyle("darkreader--invert"); if (fixes && Array.isArray(fixes.invert) && fixes.invert.length > 0) { invertStyle.textContent = [ `${fixes.invert.join(", ")} {`, ` filter: ${getCSSFilterValue({ ...theme, contrast: theme.mode === 0 ? theme.contrast : clamp(theme.contrast - 10, 0, 100) })} !important;`, "}" ].join("\n"); } else { invertStyle.textContent = ""; } document.head.insertBefore(invertStyle, textStyle.nextSibling); setupNodePositionWatcher(invertStyle, "invert"); const inlineStyle = createOrUpdateStyle("darkreader--inline"); inlineStyle.textContent = getInlineOverrideStyle(); document.head.insertBefore(inlineStyle, invertStyle.nextSibling); setupNodePositionWatcher(inlineStyle, "inline"); const overrideStyle = createOrUpdateStyle("darkreader--override"); overrideStyle.textContent = fixes && fixes.css ? replaceCSSTemplates(fixes.css) : ""; document.head.appendChild(overrideStyle); setupNodePositionWatcher(overrideStyle, "override"); const variableStyle = createOrUpdateStyle("darkreader--variables"); const selectionColors = theme?.selectionColor ? getSelectionColor(theme) : null; const neutralBackgroundColor = modifyBackgroundColor( parseColorWithCache("#ffffff"), theme ); const neutralTextColor = modifyForegroundColor( parseColorWithCache("#000000"), theme ); variableStyle.textContent = [ `:root {`, ` --darkreader-neutral-background: ${neutralBackgroundColor};`, ` --darkreader-neutral-text: ${neutralTextColor};`, ` --darkreader-selection-background: ${selectionColors?.backgroundColorSelection ?? "initial"};`, ` --darkreader-selection-text: ${selectionColors?.foregroundColorSelection ?? "initial"};`, `}` ].join("\n"); document.head.insertBefore(variableStyle, inlineStyle.nextSibling); setupNodePositionWatcher(variableStyle, "variables"); const rootVarsStyle = createOrUpdateStyle("darkreader--root-vars"); document.head.insertBefore(rootVarsStyle, variableStyle.nextSibling); const enableStyleSheetsProxy = !( fixes && fixes.disableStyleSheetsProxy ); const enableCustomElementRegistryProxy = !( fixes && fixes.disableCustomElementRegistryProxy ); document.dispatchEvent(new CustomEvent("__darkreader__cleanUp")); { document.dispatchEvent( new CustomEvent("__darkreader__stylesheetProxy__arg", { detail: { enableStyleSheetsProxy, enableCustomElementRegistryProxy } }) ); } } const shadowRootsWithOverrides = new Set(); function createShadowStaticStyleOverridesInner(root) { const inlineStyle = createOrUpdateStyle("darkreader--inline", root); inlineStyle.textContent = getInlineOverrideStyle(); root.insertBefore(inlineStyle, root.firstChild); const overrideStyle = createOrUpdateStyle("darkreader--override", root); overrideStyle.textContent = fixes && fixes.css ? replaceCSSTemplates(fixes.css) : ""; root.insertBefore(overrideStyle, inlineStyle.nextSibling); const invertStyle = createOrUpdateStyle("darkreader--invert", root); if (fixes && Array.isArray(fixes.invert) && fixes.invert.length > 0) { invertStyle.textContent = [ `${fixes.invert.join(", ")} {`, ` filter: ${getCSSFilterValue({ ...theme, contrast: theme.mode === 0 ? theme.contrast : clamp(theme.contrast - 10, 0, 100) })} !important;`, "}" ].join("\n"); } else { invertStyle.textContent = ""; } root.insertBefore(invertStyle, overrideStyle.nextSibling); shadowRootsWithOverrides.add(root); } function delayedCreateShadowStaticStyleOverrides(root) { const observer = new MutationObserver((mutations, observer) => { observer.disconnect(); for (const {type, removedNodes} of mutations) { if (type === "childList") { for (const {nodeName, className} of removedNodes) { if ( nodeName === "STYLE" && [ "darkreader darkreader--inline", "darkreader darkreader--override", "darkreader darkreader--invert" ].includes(className) ) { createShadowStaticStyleOverridesInner(root); return; } } } } }); observer.observe(root, {childList: true}); } function createShadowStaticStyleOverrides(root) { const delayed = root.firstChild === null; createShadowStaticStyleOverridesInner(root); if (delayed) { delayedCreateShadowStaticStyleOverrides(root); } } function replaceCSSTemplates($cssText) { return $cssText.replace(/\${(.+?)}/g, (_, $color) => { const color = parseColorWithCache($color); if (color) { const lightness = getSRGBLightness(color.r, color.g, color.b); if (lightness > 0.5) { return modifyBackgroundColor(color, theme); } return modifyForegroundColor(color, theme); } return $color; }); } function cleanFallbackStyle() { const fallback = document.querySelector(".darkreader--fallback"); if (fallback) { fallback.textContent = ""; } } function createDynamicStyleOverrides() { cancelRendering(); const allStyles = getManageableStyles(document); const newManagers = allStyles .filter((style) => !styleManagers.has(style)) .map((style) => createManager(style)); newManagers .map((manager) => manager.details({secondRound: false})) .filter((detail) => detail && detail.rules.length > 0) .forEach((detail) => { variablesStore.addRulesForMatching(detail.rules); }); variablesStore.matchVariablesAndDependents(); variablesStore.setOnRootVariableChange(() => { const rootVarsStyle = createOrUpdateStyle("darkreader--root-vars"); variablesStore.putRootVars(rootVarsStyle, theme); }); const rootVarsStyle = createOrUpdateStyle("darkreader--root-vars"); variablesStore.putRootVars(rootVarsStyle, theme); styleManagers.forEach((manager) => manager.render(theme, ignoredImageAnalysisSelectors) ); if (loadingStyles.size === 0) { cleanFallbackStyle(); } newManagers.forEach((manager) => manager.watch()); const inlineStyleElements = toArray( document.querySelectorAll(INLINE_STYLE_SELECTOR) ); iterateShadowHosts(document.documentElement, (host) => { createShadowStaticStyleOverrides(host.shadowRoot); const elements = host.shadowRoot.querySelectorAll( INLINE_STYLE_SELECTOR ); if (elements.length > 0) { push(inlineStyleElements, elements); } }); inlineStyleElements.forEach((el) => overrideInlineStyle( el, theme, ignoredInlineSelectors, ignoredImageAnalysisSelectors ) ); handleAdoptedStyleSheets(document); variablesStore.matchVariablesAndDependents(); } let loadingStylesCounter = 0; const loadingStyles = new Set(); function createManager(element) { const loadingStyleId = ++loadingStylesCounter; function loadingStart() { if (!isDOMReady() || !documentIsVisible()) { loadingStyles.add(loadingStyleId); logInfo( `Current amount of styles loading: ${loadingStyles.size}` ); const fallbackStyle = document.querySelector( ".darkreader--fallback" ); if (!fallbackStyle.textContent) { fallbackStyle.textContent = getModifiedFallbackStyle( theme, {strict: false} ); } } } function loadingEnd() { loadingStyles.delete(loadingStyleId); logInfo( `Removed loadingStyle ${loadingStyleId}, now awaiting: ${loadingStyles.size}` ); if (loadingStyles.size === 0 && isDOMReady()) { cleanFallbackStyle(); } } function update() { const details = manager.details({secondRound: true}); if (!details) { return; } variablesStore.addRulesForMatching(details.rules); variablesStore.matchVariablesAndDependents(); manager.render(theme, ignoredImageAnalysisSelectors); } const manager = manageStyle(element, { update, loadingStart, loadingEnd }); styleManagers.set(element, manager); return manager; } function removeManager(element) { const manager = styleManagers.get(element); if (manager) { manager.destroy(); styleManagers.delete(element); } } const throttledRenderAllStyles = throttle((callback) => { styleManagers.forEach((manager) => manager.render(theme, ignoredImageAnalysisSelectors) ); adoptedStyleManagers.forEach((manager) => manager.render(theme, ignoredImageAnalysisSelectors) ); callback && callback(); }); const cancelRendering = function () { throttledRenderAllStyles.cancel(); }; function onDOMReady() { if (loadingStyles.size === 0) { cleanFallbackStyle(); return; } } function runDynamicStyle() { createDynamicStyleOverrides(); watchForUpdates(); } function createThemeAndWatchForUpdates() { createStaticStyleOverrides(); if (!documentIsVisible() && !theme.immediateModify) { setDocumentVisibilityListener(runDynamicStyle); } else { runDynamicStyle(); } changeMetaThemeColorWhenAvailable(theme); } function handleAdoptedStyleSheets(node) { if (canHaveAdoptedStyleSheets(node)) { node.adoptedStyleSheets.forEach((s) => { variablesStore.addRulesForMatching(s.cssRules); }); const newManger = createAdoptedStyleSheetOverride(node); adoptedStyleManagers.push(newManger); newManger.render(theme, ignoredImageAnalysisSelectors); newManger.watch((sheets) => { sheets.forEach((s) => { variablesStore.addRulesForMatching(s.cssRules); }); variablesStore.matchVariablesAndDependents(); newManger.render(theme, ignoredImageAnalysisSelectors); }); } } function watchForUpdates() { const managedStyles = Array.from(styleManagers.keys()); watchForStyleChanges( managedStyles, ({created, updated, removed, moved}) => { const stylesToRemove = removed; const stylesToManage = created .concat(updated) .concat(moved) .filter((style) => !styleManagers.has(style)); const stylesToRestore = moved.filter((style) => styleManagers.has(style) ); stylesToRemove.forEach((style) => removeManager(style)); const newManagers = stylesToManage.map((style) => createManager(style) ); newManagers .map((manager) => manager.details({secondRound: false})) .filter((detail) => detail && detail.rules.length > 0) .forEach((detail) => { variablesStore.addRulesForMatching(detail.rules); }); variablesStore.matchVariablesAndDependents(); newManagers.forEach((manager) => manager.render(theme, ignoredImageAnalysisSelectors) ); newManagers.forEach((manager) => manager.watch()); stylesToRestore.forEach((style) => styleManagers.get(style).restore() ); }, (shadowRoot) => { createShadowStaticStyleOverrides(shadowRoot); handleAdoptedStyleSheets(shadowRoot); } ); watchForInlineStyles( (element) => { overrideInlineStyle( element, theme, ignoredInlineSelectors, ignoredImageAnalysisSelectors ); if (element === document.documentElement) { const styleAttr = element.getAttribute("style") || ""; if (styleAttr.includes("--")) { variablesStore.matchVariablesAndDependents(); const rootVarsStyle = createOrUpdateStyle( "darkreader--root-vars" ); variablesStore.putRootVars(rootVarsStyle, theme); } } }, (root) => { createShadowStaticStyleOverrides(root); const inlineStyleElements = root.querySelectorAll( INLINE_STYLE_SELECTOR ); if (inlineStyleElements.length > 0) { forEach(inlineStyleElements, (el) => overrideInlineStyle( el, theme, ignoredInlineSelectors, ignoredImageAnalysisSelectors ) ); } } ); addDOMReadyListener(onDOMReady); } function stopWatchingForUpdates() { styleManagers.forEach((manager) => manager.pause()); stopStylePositionWatchers(); stopWatchingForStyleChanges(); stopWatchingForInlineStyles(); removeDOMReadyListener(onDOMReady); cleanReadyStateCompleteListeners(); } let metaObserver; function addMetaListener() { metaObserver = new MutationObserver(() => { if (document.querySelector('meta[name="darkreader-lock"]')) { metaObserver.disconnect(); removeDynamicTheme(); } }); metaObserver.observe(document.head, {childList: true, subtree: true}); } function createDarkReaderInstanceMarker() { const metaElement = document.createElement("meta"); metaElement.name = "darkreader"; metaElement.content = INSTANCE_ID; document.head.appendChild(metaElement); } function isDRLocked() { return document.querySelector('meta[name="darkreader-lock"]') != null; } function isAnotherDarkReaderInstanceActive() { const meta = document.querySelector('meta[name="darkreader"]'); if (meta) { if (meta.content !== INSTANCE_ID) { return true; } return false; } createDarkReaderInstanceMarker(); addMetaListener(); return false; } let interceptorAttempts = 2; function interceptOldScript({success, failure}) { if (--interceptorAttempts <= 0) { failure(); return; } const oldMeta = document.head.querySelector('meta[name="darkreader"]'); if (!oldMeta || oldMeta.content === INSTANCE_ID) { return; } const lock = document.createElement("meta"); lock.name = "darkreader-lock"; document.head.append(lock); queueMicrotask(() => { lock.remove(); success(); }); } function disableConflictingPlugins() { if (document.documentElement.hasAttribute("data-wp-dark-mode-preset")) { const disableWPDarkMode = () => { document.dispatchEvent( new CustomEvent("__darkreader__disableConflictingPlugins") ); document.documentElement.classList.remove( "wp-dark-mode-active" ); document.documentElement.removeAttribute( "data-wp-dark-mode-active" ); }; disableWPDarkMode(); const observer = new MutationObserver(() => { if ( document.documentElement.classList.contains( "wp-dark-mode-active" ) || document.documentElement.hasAttribute( "data-wp-dark-mode-active" ) ) { disableWPDarkMode(); } }); observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class", "data-wp-dark-mode-active"] }); } } function selectRelevantFix(documentURL, fixes) { if (!fixes) { return null; } if (fixes.length === 0 || fixes[0].url[0] !== "*") { return null; } const relevantFixIndex = findRelevantFix(documentURL, fixes); return relevantFixIndex ? combineFixes([fixes[0], fixes[relevantFixIndex]]) : fixes[0]; } function createOrUpdateDynamicTheme(theme, dynamicThemeFixes, iframe) { const dynamicThemeFix = selectRelevantFix( document.location.href, dynamicThemeFixes ); createOrUpdateDynamicThemeInternal(theme, dynamicThemeFix, iframe); } function createOrUpdateDynamicThemeInternal( themeConfig, dynamicThemeFixes, iframe ) { theme = themeConfig; fixes = dynamicThemeFixes; if (fixes) { ignoredImageAnalysisSelectors = Array.isArray( fixes.ignoreImageAnalysis ) ? fixes.ignoreImageAnalysis : []; ignoredInlineSelectors = Array.isArray(fixes.ignoreInlineStyle) ? fixes.ignoreInlineStyle : []; } else { ignoredImageAnalysisSelectors = []; ignoredInlineSelectors = []; } if (theme.immediateModify) { setIsDOMReady(() => { return true; }); } isIFrame = iframe; const ready = () => { const success = () => { disableConflictingPlugins(); document.documentElement.setAttribute( "data-darkreader-mode", "dynamic" ); document.documentElement.setAttribute( "data-darkreader-scheme", theme.mode ? "dark" : "dimmed" ); createThemeAndWatchForUpdates(); }; const failure = () => { removeDynamicTheme(); }; if (isDRLocked()) { removeNode(document.querySelector(".darkreader--fallback")); } else if (isAnotherDarkReaderInstanceActive()) { interceptOldScript({ success, failure }); } else { success(); } }; if (document.head) { ready(); } else { { const fallbackStyle = createOrUpdateStyle( "darkreader--fallback" ); document.documentElement.appendChild(fallbackStyle); fallbackStyle.textContent = getModifiedFallbackStyle(theme, { strict: true }); } const headObserver = new MutationObserver(() => { if (document.head) { headObserver.disconnect(); ready(); } }); headObserver.observe(document, {childList: true, subtree: true}); } } function removeProxy() { document.dispatchEvent(new CustomEvent("__darkreader__cleanUp")); removeNode(document.head.querySelector(".darkreader--proxy")); } const cleaners = []; function removeDynamicTheme() { document.documentElement.removeAttribute(`data-darkreader-mode`); document.documentElement.removeAttribute(`data-darkreader-scheme`); cleanDynamicThemeCache(); removeNode(document.querySelector(".darkreader--fallback")); if (document.head) { restoreMetaThemeColor(); removeNode(document.head.querySelector(".darkreader--user-agent")); removeNode(document.head.querySelector(".darkreader--text")); removeNode(document.head.querySelector(".darkreader--invert")); removeNode(document.head.querySelector(".darkreader--inline")); removeNode(document.head.querySelector(".darkreader--override")); removeNode(document.head.querySelector(".darkreader--variables")); removeNode(document.head.querySelector(".darkreader--root-vars")); removeNode(document.head.querySelector('meta[name="darkreader"]')); removeProxy(); } shadowRootsWithOverrides.forEach((root) => { removeNode(root.querySelector(".darkreader--inline")); removeNode(root.querySelector(".darkreader--override")); }); shadowRootsWithOverrides.clear(); forEach(styleManagers.keys(), (el) => removeManager(el)); loadingStyles.clear(); cleanLoadingLinks(); forEach(document.querySelectorAll(".darkreader"), removeNode); adoptedStyleManagers.forEach((manager) => manager.destroy()); adoptedStyleManagers.splice(0); adoptedStyleFallbacks.forEach((fallback) => fallback.destroy()); adoptedStyleFallbacks.clear(); metaObserver && metaObserver.disconnect(); cleaners.forEach((clean) => clean()); cleaners.splice(0); } function cleanDynamicThemeCache() { variablesStore.clear(); parsedURLCache.clear(); removeDocumentVisibilityListener(); cancelRendering(); stopWatchingForUpdates(); cleanModificationCache(); clearColorCache(); } function parseCSS(cssText) { cssText = removeCSSComments(cssText); cssText = cssText.trim(); if (!cssText) { return []; } const rules = []; const excludeRanges = getTokenExclusionRanges(cssText); const bracketRanges = getAllOpenCloseRanges( cssText, "{", "}", excludeRanges ); let ruleStart = 0; bracketRanges.forEach((brackets) => { const key = cssText.substring(ruleStart, brackets.start).trim(); const content = cssText.substring( brackets.start + 1, brackets.end - 1 ); if (key.startsWith("@")) { const typeEndIndex = key.search(/[\s\(]/); const rule = { type: typeEndIndex < 0 ? key : key.substring(0, typeEndIndex), query: typeEndIndex < 0 ? "" : key.substring(typeEndIndex).trim(), rules: parseCSS(content) }; rules.push(rule); } else { const rule = { selectors: parseSelectors(key), declarations: parseDeclarations(content) }; rules.push(rule); } ruleStart = brackets.end; }); return rules; } function getAllOpenCloseRanges( input, openToken, closeToken, excludeRanges = [] ) { const ranges = []; let i = 0; let range; while ( (range = getOpenCloseRange( input, i, openToken, closeToken, excludeRanges )) ) { ranges.push(range); i = range.end; } return ranges; } function getTokenExclusionRanges(cssText) { const singleQuoteGoesFirst = cssText.indexOf("'") < cssText.indexOf('"'); const firstQuote = singleQuoteGoesFirst ? "'" : '"'; const secondQuote = singleQuoteGoesFirst ? '"' : "'"; const excludeRanges = getAllOpenCloseRanges( cssText, firstQuote, firstQuote ); excludeRanges.push( ...getAllOpenCloseRanges( cssText, secondQuote, secondQuote, excludeRanges ) ); excludeRanges.push( ...getAllOpenCloseRanges(cssText, "[", "]", excludeRanges) ); excludeRanges.push( ...getAllOpenCloseRanges(cssText, "(", ")", excludeRanges) ); return excludeRanges; } function parseSelectors(selectorText) { const excludeRanges = getTokenExclusionRanges(selectorText); return splitExcluding(selectorText, ",", excludeRanges); } function parseDeclarations(cssDeclarationsText) { const declarations = []; const excludeRanges = getTokenExclusionRanges(cssDeclarationsText); splitExcluding(cssDeclarationsText, ";", excludeRanges).forEach( (part) => { const colonIndex = part.indexOf(":"); if (colonIndex > 0) { const importantIndex = part.indexOf("!important"); declarations.push({ property: part.substring(0, colonIndex).trim(), value: part .substring( colonIndex + 1, importantIndex > 0 ? importantIndex : part.length ) .trim(), important: importantIndex > 0 }); } } ); return declarations; } function isParsedStyleRule(rule) { return "selectors" in rule; } function formatCSS(cssText) { const parsed = parseCSS(cssText); return formatParsedCSS(parsed); } function formatParsedCSS(parsed) { const lines = []; const tab = " "; function formatRule(rule, indent) { if (isParsedStyleRule(rule)) { formatStyleRule(rule, indent); } else { formatAtRule(rule, indent); } } function formatAtRule({type, query, rules}, indent) { lines.push(`${indent}${type} ${query} {`); rules.forEach((child) => formatRule(child, `${indent}${tab}`)); lines.push(`${indent}}`); } function formatStyleRule({selectors, declarations}, indent) { const lastSelectorIndex = selectors.length - 1; selectors.forEach((selector, i) => { lines.push( `${indent}${selector}${i < lastSelectorIndex ? "," : " {"}` ); }); const sorted = sortDeclarations(declarations); sorted.forEach(({property, value, important}) => { lines.push( `${indent}${tab}${property}: ${value}${important ? " !important" : ""};` ); }); lines.push(`${indent}}`); } clearEmptyRules(parsed); parsed.forEach((rule) => formatRule(rule, "")); return lines.join("\n"); } function sortDeclarations(declarations) { const prefixRegex = /^-[a-z]-/; return [...declarations].sort((a, b) => { const aProp = a.property; const bProp = b.property; const aPrefix = aProp.match(prefixRegex)?.[0] ?? ""; const bPrefix = bProp.match(prefixRegex)?.[0] ?? ""; const aNorm = aPrefix ? aProp.replace(prefixRegex, "") : aProp; const bNorm = bPrefix ? bProp.replace(prefixRegex, "") : bProp; if (aNorm === bNorm) { return aPrefix.localeCompare(bPrefix); } return aNorm.localeCompare(bNorm); }); } function clearEmptyRules(rules) { for (let i = rules.length - 1; i >= 0; i--) { const rule = rules[i]; if (isParsedStyleRule(rule)) { if (rule.declarations.length === 0) { rules.splice(i, 1); } } else { clearEmptyRules(rule.rules); if (rule.rules.length === 0) { rules.splice(i, 1); } } } } const blobRegex = /url\(\"(blob\:.*?)\"\)/g; async function replaceBlobs(text) { const promises = []; getMatches(blobRegex, text, 1).forEach((url) => { const promise = loadAsDataURL(url); promises.push(promise); }); const data = await Promise.all(promises); return text.replace(blobRegex, () => `url("${data.shift()}")`); } const banner = `/* _______ / \\ .==. .==. (( ))==(( )) / "==" "=="\\ /____|| || ||___\\ ________ ____ ________ ___ ___ | ___ \\ / \\ | ___ \\ | | / / | | \\ \\ / /\\ \\ | | \\ \\| |_/ / | | ) / /__\\ \\ | |__/ /| ___ \\ | |__/ / ______ \\| ____ \\| | \\ \\ _______|_______/__/ ____ \\__\\__|___\\__\\__|___\\__\\____ | ___ \\ | ____/ / \\ | ___ \\ | ____| ___ \\ | | \\ \\| |___ / /\\ \\ | | \\ \\| |___| | \\ \\ | |__/ /| ____/ /__\\ \\ | | ) | ____| |__/ / | ____ \\| |__/ ______ \\| |__/ /| |___| ____ \\ |__| \\__\\____/__/ \\__\\_______/ |______|__| \\__\\ https://darkreader.org */ /*! Dark reader generated CSS | Licensed under MIT https://github.com/darkreader/darkreader/blob/main/LICENSE */ `; async function collectCSS() { const css = [banner]; function addStaticCSS(selector, comment) { const staticStyle = document.querySelector(selector); if (staticStyle && staticStyle.textContent) { css.push(`/* ${comment} */`); css.push(staticStyle.textContent); css.push(""); } } addStaticCSS(".darkreader--fallback", "Fallback Style"); addStaticCSS(".darkreader--user-agent", "User-Agent Style"); addStaticCSS(".darkreader--text", "Text Style"); addStaticCSS(".darkreader--invert", "Invert Style"); addStaticCSS(".darkreader--variables", "Variables Style"); const modifiedCSS = []; document.querySelectorAll(".darkreader--sync").forEach((element) => { forEach(element.sheet.cssRules, (rule) => { rule && rule.cssText && modifiedCSS.push(rule.cssText); }); }); if (modifiedCSS.length) { const formattedCSS = formatCSS(modifiedCSS.join("\n")); css.push("/* Modified CSS */"); css.push(await replaceBlobs(formattedCSS)); css.push(""); } addStaticCSS(".darkreader--override", "Override Style"); return css.join("\n"); } let unloaded = false; const scriptId = generateUID(); function cleanup() { unloaded = true; removeEventListener("pagehide", onPageHide); removeEventListener("freeze", onFreeze); removeEventListener("resume", onResume); cleanDynamicThemeCache(); stopDarkThemeDetector(); stopColorSchemeChangeDetector(); } function sendMessage(message) { if (unloaded) { return; } const responseHandler = (response) => { if ( response === "unsupportedSender" || response?.type === MessageTypeBGtoCS.UNSUPPORTED_SENDER ) { removeStyle(); removeSVGFilter(); removeDynamicTheme(); cleanup(); } }; try { if (true) { const promise = chrome.runtime.sendMessage(message); promise.then(responseHandler).catch(cleanup); } } catch (error) { if (error.message === "Extension context invalidated.") { console.log( "Dark Reader: instance of old CS detected, cleaning up." ); cleanup(); } else { console.log( "Dark Reader: unexpected error during message passing." ); } } } function onMessage(message) { if ( message.scriptId !== scriptId && message.type !== MessageTypeUItoCS.EXPORT_CSS ) { return; } logInfoCollapsed(`onMessage[${message.type}]`, message); switch (message.type) { case MessageTypeBGtoCS.ADD_CSS_FILTER: case MessageTypeBGtoCS.ADD_STATIC_THEME: { const {css, detectDarkTheme, detectorHints} = message.data; removeDynamicTheme(); createOrUpdateStyle$1( css, message.type === MessageTypeBGtoCS.ADD_STATIC_THEME ? "static" : "filter" ); if (detectDarkTheme) { runDarkThemeDetector((hasDarkTheme) => { if (hasDarkTheme) { removeStyle(); onDarkThemeDetected(); } }, detectorHints); } break; } case MessageTypeBGtoCS.ADD_SVG_FILTER: { const { css, svgMatrix, svgReverseMatrix, detectDarkTheme, detectorHints } = message.data; removeDynamicTheme(); createOrUpdateSVGFilter(svgMatrix, svgReverseMatrix); createOrUpdateStyle$1(css, "filter"); if (detectDarkTheme) { runDarkThemeDetector((hasDarkTheme) => { if (hasDarkTheme) { removeStyle(); removeSVGFilter(); onDarkThemeDetected(); } }, detectorHints); } break; } case MessageTypeBGtoCS.ADD_DYNAMIC_THEME: { const {theme, fixes, isIFrame, detectDarkTheme, detectorHints} = message.data; removeStyle(); createOrUpdateDynamicTheme(theme, fixes, isIFrame); if (detectDarkTheme) { runDarkThemeDetector((hasDarkTheme) => { if (hasDarkTheme) { removeDynamicTheme(); onDarkThemeDetected(); } }, detectorHints); } break; } case MessageTypeUItoCS.EXPORT_CSS: collectCSS().then((collectedCSS) => sendMessage({ type: MessageTypeCStoUI.EXPORT_CSS_RESPONSE, data: collectedCSS }) ); break; case MessageTypeBGtoCS.UNSUPPORTED_SENDER: case MessageTypeBGtoCS.CLEAN_UP: removeStyle(); removeSVGFilter(); removeDynamicTheme(); stopDarkThemeDetector(); break; } } function sendConnectionOrResumeMessage(type) { sendMessage({ type, scriptId, data: { isDark: isSystemDarkModeEnabled(), isTopFrame: window === window.top } }); } runColorSchemeChangeDetector((isDark) => sendMessage({ type: MessageTypeCStoBG.COLOR_SCHEME_CHANGE, data: {isDark} }) ); chrome.runtime.onMessage.addListener(onMessage); sendConnectionOrResumeMessage(MessageTypeCStoBG.DOCUMENT_CONNECT); function onPageHide(e) { if (e.persisted === false) { sendMessage({type: MessageTypeCStoBG.DOCUMENT_FORGET, scriptId}); } } function onFreeze() { sendMessage({type: MessageTypeCStoBG.DOCUMENT_FREEZE}); } function onResume() { sendConnectionOrResumeMessage(MessageTypeCStoBG.DOCUMENT_RESUME); } function onDarkThemeDetected() { sendMessage({type: MessageTypeCStoBG.DARK_THEME_DETECTED}); } { addEventListener("pagehide", onPageHide, {passive: true}); addEventListener("freeze", onFreeze, {passive: true}); addEventListener("resume", onResume, {passive: true}); } })();