7716 lines
284 KiB
JavaScript
7716 lines
284 KiB
JavaScript
|
(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 = [
|
||
|
`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}">`,
|
||
|
"<defs>",
|
||
|
'<filter id="darkreader-image-filter">',
|
||
|
`<feColorMatrix type="matrix" values="${matrix}" />`,
|
||
|
"</filter>",
|
||
|
"</defs>",
|
||
|
`<image width="${width}" height="${height}" filter="url(#darkreader-image-filter)" xlink:href="${dataURL}" />`,
|
||
|
"</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(
|
||
|
'<style class="darkreader darkreader--sync" media="screen"></style>',
|
||
|
""
|
||
|
);
|
||
|
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});
|
||
|
}
|
||
|
})();
|