darkreader 4.9.105 → 4.9.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +8 -8
  2. package/darkreader.js +924 -844
  3. package/darkreader.mjs +881 -800
  4. package/package.json +27 -28
package/darkreader.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Dark Reader v4.9.105
2
+ * Dark Reader v4.9.109
3
3
  * https://darkreader.org/
4
4
  */
5
5
 
@@ -29,6 +29,8 @@ var MessageTypeUItoBG;
29
29
  "ui-bg-apply-dev-static-themes";
30
30
  MessageTypeUItoBG["RESET_DEV_STATIC_THEMES"] =
31
31
  "ui-bg-reset-dev-static-themes";
32
+ MessageTypeUItoBG["START_ACTIVATION"] = "ui-bg-start-activation";
33
+ MessageTypeUItoBG["RESET_ACTIVATION"] = "ui-bg-reset-activation";
32
34
  MessageTypeUItoBG["COLOR_SCHEME_CHANGE"] = "ui-bg-color-scheme-change";
33
35
  MessageTypeUItoBG["HIDE_HIGHLIGHTS"] = "ui-bg-hide-highlights";
34
36
  })(MessageTypeUItoBG || (MessageTypeUItoBG = {}));
@@ -355,9 +357,7 @@ const filterModeSites = [
355
357
  ];
356
358
  ({
357
359
  customThemes: filterModeSites.map((url) => {
358
- const engine = isChromium
359
- ? ThemeEngine.svgFilter
360
- : ThemeEngine.cssFilter;
360
+ const engine = ThemeEngine.cssFilter;
361
361
  return {
362
362
  url: [url],
363
363
  theme: {...DEFAULT_THEME, engine},
@@ -956,10 +956,16 @@ const supportedColorFuncs = [
956
956
  function parse($color) {
957
957
  const c = $color.trim().toLowerCase();
958
958
  if (c.includes("(from ")) {
959
+ if (c.indexOf("(from") !== c.lastIndexOf("(from")) {
960
+ return null;
961
+ }
959
962
  return domParseColor(c);
960
963
  }
961
964
  if (c.match(rgbMatch)) {
962
965
  if (c.startsWith("rgb(#") || c.startsWith("rgba(#")) {
966
+ if (c.lastIndexOf("rgb") > 0) {
967
+ return null;
968
+ }
963
969
  return domParseColor(c);
964
970
  }
965
971
  return parseRGB(c);
@@ -982,7 +988,10 @@ function parse($color) {
982
988
  if (
983
989
  c.endsWith(")") &&
984
990
  supportedColorFuncs.some(
985
- (fn) => c.startsWith(fn) && c[fn.length] === "("
991
+ (fn) =>
992
+ c.startsWith(fn) &&
993
+ c[fn.length] === "(" &&
994
+ c.lastIndexOf(fn) === 0
986
995
  )
987
996
  ) {
988
997
  return domParseColor(c);
@@ -1798,6 +1807,9 @@ function iterateCSSRules(rules, iterate, onImportError) {
1798
1807
  forEach(rules, (rule) => {
1799
1808
  if (isStyleRule(rule)) {
1800
1809
  iterate(rule);
1810
+ if (rule.cssRules?.length > 0) {
1811
+ iterateCSSRules(rule.cssRules, iterate);
1812
+ }
1801
1813
  } else if (isImportRule(rule)) {
1802
1814
  try {
1803
1815
  iterateCSSRules(
@@ -2055,817 +2067,812 @@ function getSheetScope(sheet) {
2055
2067
  return null;
2056
2068
  }
2057
2069
 
2058
- const gradientLength = "gradient".length;
2059
- const conicGradient = "conic-";
2060
- const conicGradientLength = conicGradient.length;
2061
- const radialGradient = "radial-";
2062
- const linearGradient = "linear-";
2063
- function parseGradient(value) {
2064
- const result = [];
2065
- let index = 0;
2066
- let startIndex = conicGradient.length;
2067
- while ((index = value.indexOf("gradient", startIndex)) !== -1) {
2068
- let typeGradient;
2069
- [linearGradient, radialGradient, conicGradient].find((possibleType) => {
2070
- if (index - possibleType.length >= 0) {
2071
- const possibleGradient = value.substring(
2072
- index - possibleType.length,
2073
- index
2074
- );
2075
- if (possibleGradient === possibleType) {
2076
- if (
2077
- value.slice(
2078
- index - possibleType.length - 10,
2079
- index - possibleType.length - 1
2080
- ) === "repeating"
2081
- ) {
2082
- typeGradient = `repeating-${possibleType}gradient`;
2083
- return true;
2084
- }
2085
- if (
2086
- value.slice(
2087
- index - possibleType.length - 8,
2088
- index - possibleType.length - 1
2089
- ) === "-webkit"
2090
- ) {
2091
- typeGradient = `-webkit-${possibleType}gradient`;
2092
- return true;
2093
- }
2094
- typeGradient = `${possibleType}gradient`;
2095
- return true;
2096
- }
2070
+ let variablesSheet;
2071
+ const registeredColors = new Map();
2072
+ function registerVariablesSheet(sheet) {
2073
+ variablesSheet = sheet;
2074
+ const types = ["background", "text", "border"];
2075
+ registeredColors.forEach((registered) => {
2076
+ types.forEach((type) => {
2077
+ if (registered[type]) {
2078
+ const {variable, value} = registered[type];
2079
+ variablesSheet?.cssRules[0].style.setProperty(variable, value);
2097
2080
  }
2098
2081
  });
2099
- if (!typeGradient) {
2100
- break;
2101
- }
2102
- const {start, end} = getParenthesesRange(value, index + gradientLength);
2103
- const match = value.substring(start + 1, end - 1);
2104
- startIndex = end + 1 + conicGradientLength;
2105
- result.push({
2106
- typeGradient,
2107
- match,
2108
- offset: typeGradient.length + 2,
2109
- index: index - typeGradient.length + gradientLength,
2110
- hasComma: true
2111
- });
2112
- }
2113
- if (result.length) {
2114
- result[result.length - 1].hasComma = false;
2115
- }
2116
- return result;
2117
- }
2118
-
2119
- const STORAGE_KEY_IMAGE_DETAILS_LIST = "__darkreader__imageDetails_v2_list";
2120
- const STORAGE_KEY_IMAGE_DETAILS_PREFIX = "__darkreader__imageDetails_v2_";
2121
- const STORAGE_KEY_CSS_FETCH_PREFIX = "__darkreader__cssFetch_";
2122
- let imageCacheTimeout = 0;
2123
- const imageDetailsCacheQueue = new Map();
2124
- const cachedImageUrls = [];
2125
- function writeImageDetailsQueue() {
2126
- imageDetailsCacheQueue.forEach((details, url) => {
2127
- if (url && url.startsWith("https://")) {
2128
- try {
2129
- const json = JSON.stringify(details);
2130
- sessionStorage.setItem(
2131
- `${STORAGE_KEY_IMAGE_DETAILS_PREFIX}${url}`,
2132
- json
2133
- );
2134
- cachedImageUrls.push(url);
2135
- } catch (err) {}
2136
- }
2137
2082
  });
2138
- imageDetailsCacheQueue.clear();
2139
- sessionStorage.setItem(
2140
- STORAGE_KEY_IMAGE_DETAILS_LIST,
2141
- JSON.stringify(cachedImageUrls)
2142
- );
2143
- }
2144
- function writeImageDetailsCache(url, imageDetails) {
2145
- if (!url || !url.startsWith("https://")) {
2146
- return;
2147
- }
2148
- imageDetailsCacheQueue.set(url, imageDetails);
2149
- clearTimeout(imageCacheTimeout);
2150
- imageCacheTimeout = setTimeout(writeImageDetailsQueue, 1000);
2151
2083
  }
2152
- function readImageDetailsCache(targetMap) {
2153
- try {
2154
- const jsonList = sessionStorage.getItem(STORAGE_KEY_IMAGE_DETAILS_LIST);
2155
- if (!jsonList) {
2156
- return;
2157
- }
2158
- const list = JSON.parse(jsonList);
2159
- list.forEach((url) => {
2160
- const json = sessionStorage.getItem(
2161
- `${STORAGE_KEY_IMAGE_DETAILS_PREFIX}${url}`
2162
- );
2163
- if (json) {
2164
- const details = JSON.parse(json);
2165
- targetMap.set(url, details);
2166
- }
2167
- });
2168
- } catch (err) {}
2084
+ function releaseVariablesSheet() {
2085
+ variablesSheet = null;
2086
+ clearColorPalette();
2169
2087
  }
2170
- function writeCSSFetchCache(url, cssText) {
2171
- const key = `${STORAGE_KEY_CSS_FETCH_PREFIX}${url}`;
2172
- try {
2173
- sessionStorage.setItem(key, cssText);
2174
- } catch (err) {}
2088
+ function getRegisteredVariableValue(type, registered) {
2089
+ return `var(${registered[type].variable}, ${registered[type].value})`;
2175
2090
  }
2176
- function readCSSFetchCache(url) {
2177
- const key = `${STORAGE_KEY_CSS_FETCH_PREFIX}${url}`;
2178
- try {
2179
- return sessionStorage.getItem(key) ?? null;
2180
- } catch (err) {}
2091
+ function getRegisteredColor(type, parsed) {
2092
+ const hex = rgbToHexString(parsed);
2093
+ const registered = registeredColors.get(hex);
2094
+ if (registered?.[type]) {
2095
+ return getRegisteredVariableValue(type, registered);
2096
+ }
2181
2097
  return null;
2182
2098
  }
2183
-
2184
- function toSVGMatrix(matrix) {
2185
- return matrix
2186
- .slice(0, 4)
2187
- .map((m) => m.map((m) => m.toFixed(3)).join(" "))
2188
- .join(" ");
2189
- }
2190
- function getSVGFilterMatrixValue(config) {
2191
- return toSVGMatrix(createFilterMatrix(config));
2192
- }
2193
-
2194
- const MAX_FRAME_DURATION = 1000 / 60;
2195
- class AsyncQueue {
2196
- constructor() {
2197
- this.queue = [];
2198
- this.timerId = null;
2099
+ function registerColor(type, parsed, value) {
2100
+ const hex = rgbToHexString(parsed);
2101
+ let registered;
2102
+ if (registeredColors.has(hex)) {
2103
+ registered = registeredColors.get(hex);
2104
+ } else {
2105
+ const parsed = parseColorWithCache(hex);
2106
+ registered = {parsed};
2107
+ registeredColors.set(hex, registered);
2199
2108
  }
2200
- addTask(task) {
2201
- this.queue.push(task);
2202
- this.scheduleFrame();
2109
+ const variable = `--darkreader-${type}-${hex.replace("#", "")}`;
2110
+ registered[type] = {variable, value};
2111
+ if (variablesSheet?.cssRules[0]?.style) {
2112
+ variablesSheet?.cssRules[0].style.setProperty(variable, value);
2203
2113
  }
2204
- stop() {
2205
- if (this.timerId !== null) {
2206
- cancelAnimationFrame(this.timerId);
2207
- this.timerId = null;
2114
+ return getRegisteredVariableValue(type, registered);
2115
+ }
2116
+ function getColorPalette() {
2117
+ const background = [];
2118
+ const border = [];
2119
+ const text = [];
2120
+ registeredColors.forEach((registered) => {
2121
+ if (registered.background) {
2122
+ background.push(registered.parsed);
2208
2123
  }
2209
- this.queue = [];
2210
- }
2211
- scheduleFrame() {
2212
- if (this.timerId) {
2213
- return;
2124
+ if (registered.border) {
2125
+ border.push(registered.parsed);
2126
+ }
2127
+ if (registered.text) {
2128
+ text.push(registered.parsed);
2214
2129
  }
2215
- this.timerId = requestAnimationFrame(() => {
2216
- this.timerId = null;
2217
- const start = Date.now();
2218
- let cb;
2219
- while ((cb = this.queue.shift())) {
2220
- cb();
2221
- if (Date.now() - start >= MAX_FRAME_DURATION) {
2222
- this.scheduleFrame();
2223
- break;
2224
- }
2225
- }
2226
- });
2227
- }
2228
- }
2229
-
2230
- const resolvers$1 = new Map();
2231
- const rejectors = new Map();
2232
- async function bgFetch(request) {
2233
- if (window.DarkReader?.Plugins?.fetch) {
2234
- return window.DarkReader.Plugins.fetch(request);
2235
- }
2236
- return new Promise((resolve, reject) => {
2237
- const id = generateUID();
2238
- resolvers$1.set(id, resolve);
2239
- rejectors.set(id, reject);
2240
- chrome.runtime.sendMessage({
2241
- type: MessageTypeCStoBG.FETCH,
2242
- data: request,
2243
- id
2244
- });
2245
2130
  });
2131
+ return {background, border, text};
2132
+ }
2133
+ function clearColorPalette() {
2134
+ registeredColors.clear();
2246
2135
  }
2247
- chrome.runtime.onMessage.addListener(({type, data, error, id}) => {
2248
- if (type === MessageTypeBGtoCS.FETCH_RESPONSE) {
2249
- const resolve = resolvers$1.get(id);
2250
- const reject = rejectors.get(id);
2251
- resolvers$1.delete(id);
2252
- rejectors.delete(id);
2253
- if (error) {
2254
- reject && reject(error);
2255
- } else {
2256
- resolve && resolve(data);
2257
- }
2258
- }
2259
- });
2260
2136
 
2261
- const imageManager = new AsyncQueue();
2262
- async function getImageDetails(url) {
2263
- return new Promise(async (resolve, reject) => {
2264
- try {
2265
- const dataURL = url.startsWith("data:")
2266
- ? url
2267
- : await getDataURL(url);
2268
- const blob =
2269
- tryConvertDataURLToBlobSync(dataURL) ?? (await loadAsBlob(url));
2270
- let image;
2271
- if (dataURL.startsWith("data:image/svg+xml")) {
2272
- image = await loadImage(dataURL);
2273
- } else {
2274
- image =
2275
- (await tryCreateImageBitmap(blob)) ??
2276
- (await loadImage(dataURL));
2277
- }
2278
- imageManager.addTask(() => {
2279
- const analysis = analyzeImage(image);
2280
- resolve({
2281
- src: url,
2282
- dataURL: analysis.isLarge ? "" : dataURL,
2283
- width: image.width,
2284
- height: image.height,
2285
- ...analysis
2286
- });
2287
- });
2288
- } catch (error) {
2289
- reject(error);
2290
- }
2137
+ function getBgPole(theme) {
2138
+ const isDarkScheme = theme.mode === 1;
2139
+ const prop = isDarkScheme
2140
+ ? "darkSchemeBackgroundColor"
2141
+ : "lightSchemeBackgroundColor";
2142
+ return theme[prop];
2143
+ }
2144
+ function getFgPole(theme) {
2145
+ const isDarkScheme = theme.mode === 1;
2146
+ const prop = isDarkScheme ? "darkSchemeTextColor" : "lightSchemeTextColor";
2147
+ return theme[prop];
2148
+ }
2149
+ const colorModificationCache = new Map();
2150
+ function clearColorModificationCache() {
2151
+ colorModificationCache.clear();
2152
+ }
2153
+ const rgbCacheKeys = ["r", "g", "b", "a"];
2154
+ const themeCacheKeys = [
2155
+ "mode",
2156
+ "brightness",
2157
+ "contrast",
2158
+ "grayscale",
2159
+ "sepia",
2160
+ "darkSchemeBackgroundColor",
2161
+ "darkSchemeTextColor",
2162
+ "lightSchemeBackgroundColor",
2163
+ "lightSchemeTextColor"
2164
+ ];
2165
+ function getCacheId(rgb, theme) {
2166
+ let resultId = "";
2167
+ rgbCacheKeys.forEach((key) => {
2168
+ resultId += `${rgb[key]};`;
2169
+ });
2170
+ themeCacheKeys.forEach((key) => {
2171
+ resultId += `${theme[key]};`;
2291
2172
  });
2173
+ return resultId;
2292
2174
  }
2293
- async function getDataURL(url) {
2294
- const parsedURL = new URL(url);
2295
- if (parsedURL.origin === location.origin) {
2296
- return await loadAsDataURL(url);
2175
+ function modifyColorWithCache(
2176
+ rgb,
2177
+ theme,
2178
+ modifyHSL,
2179
+ poleColor,
2180
+ anotherPoleColor
2181
+ ) {
2182
+ let fnCache;
2183
+ if (colorModificationCache.has(modifyHSL)) {
2184
+ fnCache = colorModificationCache.get(modifyHSL);
2185
+ } else {
2186
+ fnCache = new Map();
2187
+ colorModificationCache.set(modifyHSL, fnCache);
2297
2188
  }
2298
- return await bgFetch({url, responseType: "data-url"});
2189
+ const id = getCacheId(rgb, theme);
2190
+ if (fnCache.has(id)) {
2191
+ return fnCache.get(id);
2192
+ }
2193
+ const hsl = rgbToHSL(rgb);
2194
+ const pole = poleColor == null ? null : parseToHSLWithCache(poleColor);
2195
+ const anotherPole =
2196
+ anotherPoleColor == null ? null : parseToHSLWithCache(anotherPoleColor);
2197
+ const modified = modifyHSL(hsl, pole, anotherPole);
2198
+ const {r, g, b, a} = hslToRGB(modified);
2199
+ const matrix = createFilterMatrix({...theme, mode: 0});
2200
+ const [rf, gf, bf] = applyColorMatrix([r, g, b], matrix);
2201
+ const color =
2202
+ a === 1
2203
+ ? rgbToHexString({r: rf, g: gf, b: bf})
2204
+ : rgbToString({r: rf, g: gf, b: bf, a});
2205
+ fnCache.set(id, color);
2206
+ return color;
2299
2207
  }
2300
- async function tryCreateImageBitmap(blob) {
2301
- try {
2302
- return await createImageBitmap(blob);
2303
- } catch (err) {
2304
- logWarn(
2305
- `Unable to create image bitmap for type ${blob.type}: ${String(err)}`
2306
- );
2307
- return null;
2208
+ function modifyAndRegisterColor(type, rgb, theme, modifier) {
2209
+ const registered = getRegisteredColor(type, rgb);
2210
+ if (registered) {
2211
+ return registered;
2308
2212
  }
2213
+ const value = modifier(rgb, theme);
2214
+ return registerColor(type, rgb, value);
2309
2215
  }
2310
- const INCOMPLETE_DOC_LOADING_IMAGE_LIMIT = 256;
2311
- let loadingImagesCount = 0;
2312
- async function loadImage(url) {
2313
- return new Promise((resolve, reject) => {
2314
- const image = new Image();
2315
- image.onload = () => resolve(image);
2316
- image.onerror = () => reject(`Unable to load image ${url}`);
2317
- if (
2318
- ++loadingImagesCount <= INCOMPLETE_DOC_LOADING_IMAGE_LIMIT ||
2319
- isReadyStateComplete()
2320
- ) {
2321
- image.src = url;
2216
+ function modifyLightSchemeColor(rgb, theme) {
2217
+ const poleBg = getBgPole(theme);
2218
+ const poleFg = getFgPole(theme);
2219
+ return modifyColorWithCache(rgb, theme, modifyLightModeHSL, poleFg, poleBg);
2220
+ }
2221
+ function modifyLightModeHSL({h, s, l, a}, poleFg, poleBg) {
2222
+ const isDark = l < 0.5;
2223
+ let isNeutral;
2224
+ if (isDark) {
2225
+ isNeutral = l < 0.2 || s < 0.12;
2226
+ } else {
2227
+ const isBlue = h > 200 && h < 280;
2228
+ isNeutral = s < 0.24 || (l > 0.8 && isBlue);
2229
+ }
2230
+ let hx = h;
2231
+ let sx = s;
2232
+ if (isNeutral) {
2233
+ if (isDark) {
2234
+ hx = poleFg.h;
2235
+ sx = poleFg.s;
2322
2236
  } else {
2323
- addReadyStateCompleteListener(() => (image.src = url));
2237
+ hx = poleBg.h;
2238
+ sx = poleBg.s;
2324
2239
  }
2325
- });
2326
- }
2327
- const MAX_ANALYSIS_PIXELS_COUNT = 32 * 32;
2328
- let canvas;
2329
- let context;
2330
- function createCanvas() {
2331
- const maxWidth = MAX_ANALYSIS_PIXELS_COUNT;
2332
- const maxHeight = MAX_ANALYSIS_PIXELS_COUNT;
2333
- canvas = document.createElement("canvas");
2334
- canvas.width = maxWidth;
2335
- canvas.height = maxHeight;
2336
- context = canvas.getContext("2d", {willReadFrequently: true});
2337
- context.imageSmoothingEnabled = false;
2338
- }
2339
- function removeCanvas() {
2340
- canvas = null;
2341
- context = null;
2240
+ }
2241
+ const lx = scale(l, 0, 1, poleFg.l, poleBg.l);
2242
+ return {h: hx, s: sx, l: lx, a};
2342
2243
  }
2343
- const LARGE_IMAGE_PIXELS_COUNT = 512 * 512;
2344
- function analyzeImage(image) {
2345
- if (!canvas) {
2346
- createCanvas();
2244
+ const MAX_BG_LIGHTNESS = 0.4;
2245
+ function modifyBgHSL({h, s, l, a}, pole) {
2246
+ const isDark = l < 0.5;
2247
+ const isBlue = h > 200 && h < 280;
2248
+ const isNeutral = s < 0.12 || (l > 0.8 && isBlue);
2249
+ if (isDark) {
2250
+ const lx = scale(l, 0, 0.5, 0, MAX_BG_LIGHTNESS);
2251
+ if (isNeutral) {
2252
+ const hx = pole.h;
2253
+ const sx = pole.s;
2254
+ return {h: hx, s: sx, l: lx, a};
2255
+ }
2256
+ return {h, s, l: lx, a};
2347
2257
  }
2348
- let sw;
2349
- let sh;
2350
- if (image instanceof HTMLImageElement) {
2351
- sw = image.naturalWidth;
2352
- sh = image.naturalHeight;
2353
- } else {
2354
- sw = image.width;
2355
- sh = image.height;
2258
+ let lx = scale(l, 0.5, 1, MAX_BG_LIGHTNESS, pole.l);
2259
+ if (isNeutral) {
2260
+ const hx = pole.h;
2261
+ const sx = pole.s;
2262
+ return {h: hx, s: sx, l: lx, a};
2356
2263
  }
2357
- if (sw === 0 || sh === 0) {
2358
- logWarn("Image is empty");
2359
- return {
2360
- isDark: false,
2361
- isLight: false,
2362
- isTransparent: false,
2363
- isLarge: false
2364
- };
2264
+ let hx = h;
2265
+ const isYellow = h > 60 && h < 180;
2266
+ if (isYellow) {
2267
+ const isCloserToGreen = h > 120;
2268
+ if (isCloserToGreen) {
2269
+ hx = scale(h, 120, 180, 135, 180);
2270
+ } else {
2271
+ hx = scale(h, 60, 120, 60, 105);
2272
+ }
2365
2273
  }
2366
- const isLarge = sw * sh > LARGE_IMAGE_PIXELS_COUNT;
2367
- const sourcePixelsCount = sw * sh;
2368
- const k = Math.min(
2369
- 1,
2370
- Math.sqrt(MAX_ANALYSIS_PIXELS_COUNT / sourcePixelsCount)
2274
+ if (hx > 40 && hx < 80) {
2275
+ lx *= 0.75;
2276
+ }
2277
+ return {h: hx, s, l: lx, a};
2278
+ }
2279
+ function _modifyBackgroundColor(rgb, theme) {
2280
+ if (theme.mode === 0) {
2281
+ return modifyLightSchemeColor(rgb, theme);
2282
+ }
2283
+ const pole = getBgPole(theme);
2284
+ return modifyColorWithCache(rgb, theme, modifyBgHSL, pole);
2285
+ }
2286
+ function modifyBackgroundColor(rgb, theme, shouldRegisterColorVariable = true) {
2287
+ if (!shouldRegisterColorVariable) {
2288
+ return _modifyBackgroundColor(rgb, theme);
2289
+ }
2290
+ return modifyAndRegisterColor(
2291
+ "background",
2292
+ rgb,
2293
+ theme,
2294
+ _modifyBackgroundColor
2371
2295
  );
2372
- const width = Math.ceil(sw * k);
2373
- const height = Math.ceil(sh * k);
2374
- context.clearRect(0, 0, width, height);
2375
- context.drawImage(image, 0, 0, sw, sh, 0, 0, width, height);
2376
- const imageData = context.getImageData(0, 0, width, height);
2377
- const d = imageData.data;
2378
- const TRANSPARENT_ALPHA_THRESHOLD = 0.05;
2379
- const DARK_LIGHTNESS_THRESHOLD = 0.4;
2380
- const LIGHT_LIGHTNESS_THRESHOLD = 0.7;
2381
- let transparentPixelsCount = 0;
2382
- let darkPixelsCount = 0;
2383
- let lightPixelsCount = 0;
2384
- let i, x, y;
2385
- let r, g, b, a;
2386
- let l;
2387
- for (y = 0; y < height; y++) {
2388
- for (x = 0; x < width; x++) {
2389
- i = 4 * (y * width + x);
2390
- r = d[i + 0];
2391
- g = d[i + 1];
2392
- b = d[i + 2];
2393
- a = d[i + 3];
2394
- if (a / 255 < TRANSPARENT_ALPHA_THRESHOLD) {
2395
- transparentPixelsCount++;
2396
- } else {
2397
- l = getSRGBLightness(r, g, b);
2398
- if (l < DARK_LIGHTNESS_THRESHOLD) {
2399
- darkPixelsCount++;
2400
- }
2401
- if (l > LIGHT_LIGHTNESS_THRESHOLD) {
2402
- lightPixelsCount++;
2403
- }
2404
- }
2296
+ }
2297
+ const MIN_FG_LIGHTNESS = 0.55;
2298
+ function modifyBlueFgHue(hue) {
2299
+ return scale(hue, 205, 245, 205, 220);
2300
+ }
2301
+ function modifyFgHSL({h, s, l, a}, pole) {
2302
+ const isLight = l > 0.5;
2303
+ const isNeutral = l < 0.2 || s < 0.24;
2304
+ const isBlue = !isNeutral && h > 205 && h < 245;
2305
+ if (isLight) {
2306
+ const lx = scale(l, 0.5, 1, MIN_FG_LIGHTNESS, pole.l);
2307
+ if (isNeutral) {
2308
+ const hx = pole.h;
2309
+ const sx = pole.s;
2310
+ return {h: hx, s: sx, l: lx, a};
2311
+ }
2312
+ let hx = h;
2313
+ if (isBlue) {
2314
+ hx = modifyBlueFgHue(h);
2405
2315
  }
2316
+ return {h: hx, s, l: lx, a};
2406
2317
  }
2407
- const totalPixelsCount = width * height;
2408
- const opaquePixelsCount = totalPixelsCount - transparentPixelsCount;
2409
- const DARK_IMAGE_THRESHOLD = 0.7;
2410
- const LIGHT_IMAGE_THRESHOLD = 0.7;
2411
- const TRANSPARENT_IMAGE_THRESHOLD = 0.1;
2412
- return {
2413
- isDark: darkPixelsCount / opaquePixelsCount >= DARK_IMAGE_THRESHOLD,
2414
- isLight: lightPixelsCount / opaquePixelsCount >= LIGHT_IMAGE_THRESHOLD,
2415
- isTransparent:
2416
- transparentPixelsCount / totalPixelsCount >=
2417
- TRANSPARENT_IMAGE_THRESHOLD,
2418
- isLarge
2419
- };
2318
+ if (isNeutral) {
2319
+ const hx = pole.h;
2320
+ const sx = pole.s;
2321
+ const lx = scale(l, 0, 0.5, pole.l, MIN_FG_LIGHTNESS);
2322
+ return {h: hx, s: sx, l: lx, a};
2323
+ }
2324
+ let hx = h;
2325
+ let lx;
2326
+ if (isBlue) {
2327
+ hx = modifyBlueFgHue(h);
2328
+ lx = scale(l, 0, 0.5, pole.l, Math.min(1, MIN_FG_LIGHTNESS + 0.05));
2329
+ } else {
2330
+ lx = scale(l, 0, 0.5, pole.l, MIN_FG_LIGHTNESS);
2331
+ }
2332
+ return {h: hx, s, l: lx, a};
2420
2333
  }
2421
- let isBlobURLSupported = null;
2422
- let canUseProxy = false;
2423
- let blobURLCheckRequested = false;
2424
- const blobURLCheckAwaiters = [];
2425
- document.addEventListener(
2426
- "__darkreader__inlineScriptsAllowed",
2427
- () => (canUseProxy = true),
2428
- {once: true}
2429
- );
2430
- async function requestBlobURLCheck() {
2431
- if (!canUseProxy) {
2432
- return;
2334
+ function _modifyForegroundColor(rgb, theme) {
2335
+ if (theme.mode === 0) {
2336
+ return modifyLightSchemeColor(rgb, theme);
2433
2337
  }
2434
- if (blobURLCheckRequested) {
2435
- return await new Promise((resolve) =>
2436
- blobURLCheckAwaiters.push(resolve)
2437
- );
2338
+ const pole = getFgPole(theme);
2339
+ return modifyColorWithCache(rgb, theme, modifyFgHSL, pole);
2340
+ }
2341
+ function modifyForegroundColor(rgb, theme, shouldRegisterColorVariable = true) {
2342
+ if (!shouldRegisterColorVariable) {
2343
+ return _modifyForegroundColor(rgb, theme);
2438
2344
  }
2439
- blobURLCheckRequested = true;
2440
- await new Promise((resolve) => {
2441
- document.addEventListener(
2442
- "__darkreader__blobURLCheckResponse",
2443
- (e) => {
2444
- isBlobURLSupported = e.detail.blobURLAllowed;
2445
- resolve();
2446
- blobURLCheckAwaiters.forEach((r) => r());
2447
- blobURLCheckAwaiters.splice(0);
2448
- },
2449
- {once: true}
2450
- );
2451
- document.dispatchEvent(
2452
- new CustomEvent("__darkreader__blobURLCheckRequest")
2453
- );
2454
- });
2345
+ return modifyAndRegisterColor("text", rgb, theme, _modifyForegroundColor);
2455
2346
  }
2456
- function isBlobURLCheckResultReady() {
2457
- return isBlobURLSupported != null || !canUseProxy;
2347
+ function modifyBorderHSL({h, s, l, a}, poleFg, poleBg) {
2348
+ const isDark = l < 0.5;
2349
+ const isNeutral = l < 0.2 || s < 0.24;
2350
+ let hx = h;
2351
+ let sx = s;
2352
+ if (isNeutral) {
2353
+ if (isDark) {
2354
+ hx = poleFg.h;
2355
+ sx = poleFg.s;
2356
+ } else {
2357
+ hx = poleBg.h;
2358
+ sx = poleBg.s;
2359
+ }
2360
+ }
2361
+ const lx = scale(l, 0, 1, 0.5, 0.2);
2362
+ return {h: hx, s: sx, l: lx, a};
2458
2363
  }
2459
- function onCSPError(err) {
2460
- if (err.blockedURI === "blob") {
2461
- isBlobURLSupported = false;
2462
- document.removeEventListener("securitypolicyviolation", onCSPError);
2364
+ function _modifyBorderColor(rgb, theme) {
2365
+ if (theme.mode === 0) {
2366
+ return modifyLightSchemeColor(rgb, theme);
2463
2367
  }
2368
+ const poleFg = getFgPole(theme);
2369
+ const poleBg = getBgPole(theme);
2370
+ return modifyColorWithCache(rgb, theme, modifyBorderHSL, poleFg, poleBg);
2464
2371
  }
2465
- document.addEventListener("securitypolicyviolation", onCSPError);
2466
- const objectURLs = new Set();
2467
- function getFilteredImageURL({dataURL, width, height}, theme) {
2468
- if (dataURL.startsWith("data:image/svg+xml")) {
2469
- dataURL = escapeXML(dataURL);
2372
+ function modifyBorderColor(rgb, theme, shouldRegisterColorVariable = true) {
2373
+ if (!shouldRegisterColorVariable) {
2374
+ return _modifyBorderColor(rgb, theme);
2470
2375
  }
2471
- const matrix = getSVGFilterMatrixValue(theme);
2472
- const svg = [
2473
- `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}">`,
2474
- "<defs>",
2475
- '<filter id="darkreader-image-filter">',
2476
- `<feColorMatrix type="matrix" values="${matrix}" />`,
2477
- "</filter>",
2478
- "</defs>",
2479
- `<image width="${width}" height="${height}" filter="url(#darkreader-image-filter)" xlink:href="${dataURL}" />`,
2480
- "</svg>"
2481
- ].join("");
2482
- if (!isBlobURLSupported) {
2483
- return `data:image/svg+xml;base64,${btoa(svg)}`;
2376
+ return modifyAndRegisterColor("border", rgb, theme, _modifyBorderColor);
2377
+ }
2378
+ function modifyShadowColor(rgb, theme) {
2379
+ return modifyBackgroundColor(rgb, theme);
2380
+ }
2381
+ function modifyGradientColor(rgb, theme) {
2382
+ return modifyBackgroundColor(rgb, theme);
2383
+ }
2384
+
2385
+ const gradientLength = "gradient".length;
2386
+ const conicGradient = "conic-";
2387
+ const conicGradientLength = conicGradient.length;
2388
+ const radialGradient = "radial-";
2389
+ const linearGradient = "linear-";
2390
+ function parseGradient(value) {
2391
+ const result = [];
2392
+ let index = 0;
2393
+ let startIndex = conicGradient.length;
2394
+ while ((index = value.indexOf("gradient", startIndex)) !== -1) {
2395
+ let typeGradient;
2396
+ [linearGradient, radialGradient, conicGradient].find((possibleType) => {
2397
+ if (index - possibleType.length >= 0) {
2398
+ const possibleGradient = value.substring(
2399
+ index - possibleType.length,
2400
+ index
2401
+ );
2402
+ if (possibleGradient === possibleType) {
2403
+ if (
2404
+ value.slice(
2405
+ index - possibleType.length - 10,
2406
+ index - possibleType.length - 1
2407
+ ) === "repeating"
2408
+ ) {
2409
+ typeGradient = `repeating-${possibleType}gradient`;
2410
+ return true;
2411
+ }
2412
+ if (
2413
+ value.slice(
2414
+ index - possibleType.length - 8,
2415
+ index - possibleType.length - 1
2416
+ ) === "-webkit"
2417
+ ) {
2418
+ typeGradient = `-webkit-${possibleType}gradient`;
2419
+ return true;
2420
+ }
2421
+ typeGradient = `${possibleType}gradient`;
2422
+ return true;
2423
+ }
2424
+ }
2425
+ });
2426
+ if (!typeGradient) {
2427
+ break;
2428
+ }
2429
+ const {start, end} = getParenthesesRange(value, index + gradientLength);
2430
+ const match = value.substring(start + 1, end - 1);
2431
+ startIndex = end + 1 + conicGradientLength;
2432
+ result.push({
2433
+ typeGradient,
2434
+ match,
2435
+ offset: typeGradient.length + 2,
2436
+ index: index - typeGradient.length + gradientLength,
2437
+ hasComma: true
2438
+ });
2484
2439
  }
2485
- const bytes = new Uint8Array(svg.length);
2486
- for (let i = 0; i < svg.length; i++) {
2487
- bytes[i] = svg.charCodeAt(i);
2440
+ if (result.length) {
2441
+ result[result.length - 1].hasComma = false;
2488
2442
  }
2489
- const blob = new Blob([bytes], {type: "image/svg+xml"});
2490
- const objectURL = URL.createObjectURL(blob);
2491
- objectURLs.add(objectURL);
2492
- return objectURL;
2493
- }
2494
- const xmlEscapeChars = {
2495
- "<": "&lt;",
2496
- ">": "&gt;",
2497
- "&": "&amp;",
2498
- "'": "&apos;",
2499
- '"': "&quot;"
2500
- };
2501
- function escapeXML(str) {
2502
- return str.replace(/[<>&'"]/g, (c) => xmlEscapeChars[c] ?? c);
2443
+ return result;
2503
2444
  }
2504
- const dataURLBlobURLs = new Map();
2505
- function tryConvertDataURLToBlobSync(dataURL) {
2506
- const colonIndex = dataURL.indexOf(":");
2507
- const semicolonIndex = dataURL.indexOf(";", colonIndex + 1);
2508
- const commaIndex = dataURL.indexOf(",", semicolonIndex + 1);
2509
- const encoding = dataURL
2510
- .substring(semicolonIndex + 1, commaIndex)
2511
- .toLocaleLowerCase();
2512
- const mediaType = dataURL.substring(colonIndex + 1, semicolonIndex);
2513
- if (encoding !== "base64" || !mediaType) {
2514
- return null;
2515
- }
2516
- const characters = atob(dataURL.substring(commaIndex + 1));
2517
- const bytes = new Uint8Array(characters.length);
2518
- for (let i = 0; i < characters.length; i++) {
2519
- bytes[i] = characters.charCodeAt(i);
2520
- }
2521
- return new Blob([bytes], {type: mediaType});
2445
+
2446
+ const STORAGE_KEY_IMAGE_DETAILS_LIST = "__darkreader__imageDetails_v2_list";
2447
+ const STORAGE_KEY_IMAGE_DETAILS_PREFIX = "__darkreader__imageDetails_v2_";
2448
+ const STORAGE_KEY_CSS_FETCH_PREFIX = "__darkreader__cssFetch_";
2449
+ let imageCacheTimeout = 0;
2450
+ const imageDetailsCacheQueue = new Map();
2451
+ const cachedImageUrls = [];
2452
+ function writeImageDetailsQueue() {
2453
+ imageDetailsCacheQueue.forEach((details, url) => {
2454
+ if (url && url.startsWith("https://")) {
2455
+ try {
2456
+ const json = JSON.stringify(details);
2457
+ sessionStorage.setItem(
2458
+ `${STORAGE_KEY_IMAGE_DETAILS_PREFIX}${url}`,
2459
+ json
2460
+ );
2461
+ cachedImageUrls.push(url);
2462
+ } catch (err) {}
2463
+ }
2464
+ });
2465
+ imageDetailsCacheQueue.clear();
2466
+ sessionStorage.setItem(
2467
+ STORAGE_KEY_IMAGE_DETAILS_LIST,
2468
+ JSON.stringify(cachedImageUrls)
2469
+ );
2522
2470
  }
2523
- async function tryConvertDataURLToBlobURL(dataURL) {
2524
- if (!isBlobURLSupported) {
2525
- return null;
2526
- }
2527
- const hash = getHashCode(dataURL);
2528
- let blobURL = dataURLBlobURLs.get(hash);
2529
- if (blobURL) {
2530
- return blobURL;
2531
- }
2532
- let blob = tryConvertDataURLToBlobSync(dataURL);
2533
- if (!blob) {
2534
- const response = await fetch(dataURL);
2535
- blob = await response.blob();
2471
+ function writeImageDetailsCache(url, imageDetails) {
2472
+ if (!url || !url.startsWith("https://")) {
2473
+ return;
2536
2474
  }
2537
- blobURL = URL.createObjectURL(blob);
2538
- dataURLBlobURLs.set(hash, blobURL);
2539
- return blobURL;
2540
- }
2541
- function cleanImageProcessingCache() {
2542
- imageManager && imageManager.stop();
2543
- removeCanvas();
2544
- objectURLs.forEach((u) => URL.revokeObjectURL(u));
2545
- objectURLs.clear();
2546
- dataURLBlobURLs.forEach((u) => URL.revokeObjectURL(u));
2547
- dataURLBlobURLs.clear();
2475
+ imageDetailsCacheQueue.set(url, imageDetails);
2476
+ clearTimeout(imageCacheTimeout);
2477
+ imageCacheTimeout = setTimeout(writeImageDetailsQueue, 1000);
2548
2478
  }
2549
-
2550
- let variablesSheet;
2551
- const registeredColors = new Map();
2552
- function registerVariablesSheet(sheet) {
2553
- variablesSheet = sheet;
2554
- const types = ["background", "text", "border"];
2555
- registeredColors.forEach((registered) => {
2556
- types.forEach((type) => {
2557
- if (registered[type]) {
2558
- const {variable, value} = registered[type];
2559
- variablesSheet?.cssRules[0].style.setProperty(variable, value);
2479
+ function readImageDetailsCache(targetMap) {
2480
+ try {
2481
+ const jsonList = sessionStorage.getItem(STORAGE_KEY_IMAGE_DETAILS_LIST);
2482
+ if (!jsonList) {
2483
+ return;
2484
+ }
2485
+ const list = JSON.parse(jsonList);
2486
+ list.forEach((url) => {
2487
+ const json = sessionStorage.getItem(
2488
+ `${STORAGE_KEY_IMAGE_DETAILS_PREFIX}${url}`
2489
+ );
2490
+ if (json) {
2491
+ const details = JSON.parse(json);
2492
+ targetMap.set(url, details);
2560
2493
  }
2561
2494
  });
2562
- });
2563
- }
2564
- function releaseVariablesSheet() {
2565
- variablesSheet = null;
2566
- clearColorPalette();
2495
+ } catch (err) {}
2567
2496
  }
2568
- function getRegisteredVariableValue(type, registered) {
2569
- return `var(${registered[type].variable}, ${registered[type].value})`;
2497
+ function writeCSSFetchCache(url, cssText) {
2498
+ const key = `${STORAGE_KEY_CSS_FETCH_PREFIX}${url}`;
2499
+ try {
2500
+ sessionStorage.setItem(key, cssText);
2501
+ } catch (err) {}
2570
2502
  }
2571
- function getRegisteredColor(type, parsed) {
2572
- const hex = rgbToHexString(parsed);
2573
- const registered = registeredColors.get(hex);
2574
- if (registered?.[type]) {
2575
- return getRegisteredVariableValue(type, registered);
2576
- }
2503
+ function readCSSFetchCache(url) {
2504
+ const key = `${STORAGE_KEY_CSS_FETCH_PREFIX}${url}`;
2505
+ try {
2506
+ return sessionStorage.getItem(key) ?? null;
2507
+ } catch (err) {}
2577
2508
  return null;
2578
2509
  }
2579
- function registerColor(type, parsed, value) {
2580
- const hex = rgbToHexString(parsed);
2581
- let registered;
2582
- if (registeredColors.has(hex)) {
2583
- registered = registeredColors.get(hex);
2584
- } else {
2585
- const parsed = parseColorWithCache(hex);
2586
- registered = {parsed};
2587
- registeredColors.set(hex, registered);
2510
+
2511
+ function toSVGMatrix(matrix) {
2512
+ return matrix
2513
+ .slice(0, 4)
2514
+ .map((m) => m.map((m) => m.toFixed(3)).join(" "))
2515
+ .join(" ");
2516
+ }
2517
+ function getSVGFilterMatrixValue(config) {
2518
+ return toSVGMatrix(createFilterMatrix(config));
2519
+ }
2520
+
2521
+ const MAX_FRAME_DURATION = 1000 / 60;
2522
+ class AsyncQueue {
2523
+ constructor() {
2524
+ this.queue = [];
2525
+ this.timerId = null;
2588
2526
  }
2589
- const variable = `--darkreader-${type}-${hex.replace("#", "")}`;
2590
- registered[type] = {variable, value};
2591
- if (variablesSheet?.cssRules[0]?.style) {
2592
- variablesSheet?.cssRules[0].style.setProperty(variable, value);
2527
+ addTask(task) {
2528
+ this.queue.push(task);
2529
+ this.scheduleFrame();
2593
2530
  }
2594
- return getRegisteredVariableValue(type, registered);
2595
- }
2596
- function getColorPalette() {
2597
- const background = [];
2598
- const border = [];
2599
- const text = [];
2600
- registeredColors.forEach((registered) => {
2601
- if (registered.background) {
2602
- background.push(registered.parsed);
2531
+ stop() {
2532
+ if (this.timerId !== null) {
2533
+ cancelAnimationFrame(this.timerId);
2534
+ this.timerId = null;
2603
2535
  }
2604
- if (registered.border) {
2605
- border.push(registered.parsed);
2536
+ this.queue = [];
2537
+ }
2538
+ scheduleFrame() {
2539
+ if (this.timerId) {
2540
+ return;
2606
2541
  }
2607
- if (registered.text) {
2608
- text.push(registered.parsed);
2542
+ this.timerId = requestAnimationFrame(() => {
2543
+ this.timerId = null;
2544
+ const start = Date.now();
2545
+ let cb;
2546
+ while ((cb = this.queue.shift())) {
2547
+ cb();
2548
+ if (Date.now() - start >= MAX_FRAME_DURATION) {
2549
+ this.scheduleFrame();
2550
+ break;
2551
+ }
2552
+ }
2553
+ });
2554
+ }
2555
+ }
2556
+
2557
+ const resolvers$1 = new Map();
2558
+ const rejectors = new Map();
2559
+ async function bgFetch(request) {
2560
+ if (window.DarkReader?.Plugins?.fetch) {
2561
+ return window.DarkReader.Plugins.fetch(request);
2562
+ }
2563
+ return new Promise((resolve, reject) => {
2564
+ const id = generateUID();
2565
+ resolvers$1.set(id, resolve);
2566
+ rejectors.set(id, reject);
2567
+ chrome.runtime.sendMessage({
2568
+ type: MessageTypeCStoBG.FETCH,
2569
+ data: request,
2570
+ id
2571
+ });
2572
+ });
2573
+ }
2574
+ chrome.runtime.onMessage.addListener(({type, data, error, id}) => {
2575
+ if (type === MessageTypeBGtoCS.FETCH_RESPONSE) {
2576
+ const resolve = resolvers$1.get(id);
2577
+ const reject = rejectors.get(id);
2578
+ resolvers$1.delete(id);
2579
+ rejectors.delete(id);
2580
+ if (error) {
2581
+ reject &&
2582
+ reject(typeof error === "string" ? new Error(error) : error);
2583
+ } else {
2584
+ resolve && resolve(data);
2585
+ }
2586
+ }
2587
+ });
2588
+
2589
+ const imageManager = new AsyncQueue();
2590
+ async function getImageDetails(url) {
2591
+ return new Promise(async (resolve, reject) => {
2592
+ try {
2593
+ const dataURL = url.startsWith("data:")
2594
+ ? url
2595
+ : await getDataURL(url);
2596
+ const blob =
2597
+ tryConvertDataURLToBlobSync(dataURL) ?? (await loadAsBlob(url));
2598
+ let image;
2599
+ if (dataURL.startsWith("data:image/svg+xml")) {
2600
+ image = await loadImage(dataURL);
2601
+ } else {
2602
+ image =
2603
+ (await tryCreateImageBitmap(blob)) ??
2604
+ (await loadImage(dataURL));
2605
+ }
2606
+ imageManager.addTask(() => {
2607
+ const analysis = analyzeImage(image);
2608
+ resolve({
2609
+ src: url,
2610
+ dataURL: analysis.isLarge ? "" : dataURL,
2611
+ width: image.width,
2612
+ height: image.height,
2613
+ ...analysis
2614
+ });
2615
+ });
2616
+ } catch (error) {
2617
+ reject(error);
2609
2618
  }
2610
2619
  });
2611
- return {background, border, text};
2612
2620
  }
2613
- function clearColorPalette() {
2614
- registeredColors.clear();
2615
- }
2616
-
2617
- function getBgPole(theme) {
2618
- const isDarkScheme = theme.mode === 1;
2619
- const prop = isDarkScheme
2620
- ? "darkSchemeBackgroundColor"
2621
- : "lightSchemeBackgroundColor";
2622
- return theme[prop];
2623
- }
2624
- function getFgPole(theme) {
2625
- const isDarkScheme = theme.mode === 1;
2626
- const prop = isDarkScheme ? "darkSchemeTextColor" : "lightSchemeTextColor";
2627
- return theme[prop];
2621
+ async function getDataURL(url) {
2622
+ const parsedURL = new URL(url);
2623
+ if (parsedURL.origin === location.origin) {
2624
+ return await loadAsDataURL(url);
2625
+ }
2626
+ return await bgFetch({url, responseType: "data-url"});
2628
2627
  }
2629
- const colorModificationCache = new Map();
2630
- function clearColorModificationCache() {
2631
- colorModificationCache.clear();
2628
+ async function tryCreateImageBitmap(blob) {
2629
+ try {
2630
+ return await createImageBitmap(blob);
2631
+ } catch (err) {
2632
+ logWarn(
2633
+ `Unable to create image bitmap for type ${blob.type}: ${String(err)}`
2634
+ );
2635
+ return null;
2636
+ }
2632
2637
  }
2633
- const rgbCacheKeys = ["r", "g", "b", "a"];
2634
- const themeCacheKeys$1 = [
2635
- "mode",
2636
- "brightness",
2637
- "contrast",
2638
- "grayscale",
2639
- "sepia",
2640
- "darkSchemeBackgroundColor",
2641
- "darkSchemeTextColor",
2642
- "lightSchemeBackgroundColor",
2643
- "lightSchemeTextColor"
2644
- ];
2645
- function getCacheId(rgb, theme) {
2646
- let resultId = "";
2647
- rgbCacheKeys.forEach((key) => {
2648
- resultId += `${rgb[key]};`;
2649
- });
2650
- themeCacheKeys$1.forEach((key) => {
2651
- resultId += `${theme[key]};`;
2638
+ const INCOMPLETE_DOC_LOADING_IMAGE_LIMIT = 256;
2639
+ let loadingImagesCount = 0;
2640
+ async function loadImage(url) {
2641
+ return new Promise((resolve, reject) => {
2642
+ const image = new Image();
2643
+ image.onload = () => resolve(image);
2644
+ image.onerror = () => reject(`Unable to load image ${url}`);
2645
+ if (
2646
+ ++loadingImagesCount <= INCOMPLETE_DOC_LOADING_IMAGE_LIMIT ||
2647
+ isReadyStateComplete()
2648
+ ) {
2649
+ image.src = url;
2650
+ } else {
2651
+ addReadyStateCompleteListener(() => (image.src = url));
2652
+ }
2652
2653
  });
2653
- return resultId;
2654
- }
2655
- function modifyColorWithCache(
2656
- rgb,
2657
- theme,
2658
- modifyHSL,
2659
- poleColor,
2660
- anotherPoleColor
2661
- ) {
2662
- let fnCache;
2663
- if (colorModificationCache.has(modifyHSL)) {
2664
- fnCache = colorModificationCache.get(modifyHSL);
2665
- } else {
2666
- fnCache = new Map();
2667
- colorModificationCache.set(modifyHSL, fnCache);
2668
- }
2669
- const id = getCacheId(rgb, theme);
2670
- if (fnCache.has(id)) {
2671
- return fnCache.get(id);
2672
- }
2673
- const hsl = rgbToHSL(rgb);
2674
- const pole = poleColor == null ? null : parseToHSLWithCache(poleColor);
2675
- const anotherPole =
2676
- anotherPoleColor == null ? null : parseToHSLWithCache(anotherPoleColor);
2677
- const modified = modifyHSL(hsl, pole, anotherPole);
2678
- const {r, g, b, a} = hslToRGB(modified);
2679
- const matrix = createFilterMatrix(theme);
2680
- const [rf, gf, bf] = applyColorMatrix([r, g, b], matrix);
2681
- const color =
2682
- a === 1
2683
- ? rgbToHexString({r: rf, g: gf, b: bf})
2684
- : rgbToString({r: rf, g: gf, b: bf, a});
2685
- fnCache.set(id, color);
2686
- return color;
2687
2654
  }
2688
- function modifyAndRegisterColor(type, rgb, theme, modifier) {
2689
- const registered = getRegisteredColor(type, rgb);
2690
- if (registered) {
2691
- return registered;
2692
- }
2693
- const value = modifier(rgb, theme);
2694
- return registerColor(type, rgb, value);
2655
+ const MAX_ANALYSIS_PIXELS_COUNT = 32 * 32;
2656
+ let canvas;
2657
+ let context;
2658
+ function createCanvas() {
2659
+ const maxWidth = MAX_ANALYSIS_PIXELS_COUNT;
2660
+ const maxHeight = MAX_ANALYSIS_PIXELS_COUNT;
2661
+ canvas = document.createElement("canvas");
2662
+ canvas.width = maxWidth;
2663
+ canvas.height = maxHeight;
2664
+ context = canvas.getContext("2d", {willReadFrequently: true});
2665
+ context.imageSmoothingEnabled = false;
2695
2666
  }
2696
- function modifyLightSchemeColor(rgb, theme) {
2697
- const poleBg = getBgPole(theme);
2698
- const poleFg = getFgPole(theme);
2699
- return modifyColorWithCache(rgb, theme, modifyLightModeHSL, poleFg, poleBg);
2667
+ function removeCanvas() {
2668
+ canvas = null;
2669
+ context = null;
2700
2670
  }
2701
- function modifyLightModeHSL({h, s, l, a}, poleFg, poleBg) {
2702
- const isDark = l < 0.5;
2703
- let isNeutral;
2704
- if (isDark) {
2705
- isNeutral = l < 0.2 || s < 0.12;
2671
+ const LARGE_IMAGE_PIXELS_COUNT = 512 * 512;
2672
+ function analyzeImage(image) {
2673
+ if (!canvas) {
2674
+ createCanvas();
2675
+ }
2676
+ let sw;
2677
+ let sh;
2678
+ if (image instanceof HTMLImageElement) {
2679
+ sw = image.naturalWidth;
2680
+ sh = image.naturalHeight;
2706
2681
  } else {
2707
- const isBlue = h > 200 && h < 280;
2708
- isNeutral = s < 0.24 || (l > 0.8 && isBlue);
2682
+ sw = image.width;
2683
+ sh = image.height;
2709
2684
  }
2710
- let hx = h;
2711
- let sx = l;
2712
- if (isNeutral) {
2713
- if (isDark) {
2714
- hx = poleFg.h;
2715
- sx = poleFg.s;
2716
- } else {
2717
- hx = poleBg.h;
2718
- sx = poleBg.s;
2719
- }
2685
+ if (sw === 0 || sh === 0) {
2686
+ logWarn("Image is empty");
2687
+ return {
2688
+ isDark: false,
2689
+ isLight: false,
2690
+ isTransparent: false,
2691
+ isLarge: false
2692
+ };
2720
2693
  }
2721
- const lx = scale(l, 0, 1, poleFg.l, poleBg.l);
2722
- return {h: hx, s: sx, l: lx, a};
2723
- }
2724
- const MAX_BG_LIGHTNESS = 0.4;
2725
- function modifyBgHSL({h, s, l, a}, pole) {
2726
- const isDark = l < 0.5;
2727
- const isBlue = h > 200 && h < 280;
2728
- const isNeutral = s < 0.12 || (l > 0.8 && isBlue);
2729
- if (isDark) {
2730
- const lx = scale(l, 0, 0.5, 0, MAX_BG_LIGHTNESS);
2731
- if (isNeutral) {
2732
- const hx = pole.h;
2733
- const sx = pole.s;
2734
- return {h: hx, s: sx, l: lx, a};
2694
+ const isLarge = sw * sh > LARGE_IMAGE_PIXELS_COUNT;
2695
+ const sourcePixelsCount = sw * sh;
2696
+ const k = Math.min(
2697
+ 1,
2698
+ Math.sqrt(MAX_ANALYSIS_PIXELS_COUNT / sourcePixelsCount)
2699
+ );
2700
+ const width = Math.ceil(sw * k);
2701
+ const height = Math.ceil(sh * k);
2702
+ context.clearRect(0, 0, width, height);
2703
+ context.drawImage(image, 0, 0, sw, sh, 0, 0, width, height);
2704
+ const imageData = context.getImageData(0, 0, width, height);
2705
+ const d = imageData.data;
2706
+ const TRANSPARENT_ALPHA_THRESHOLD = 0.05;
2707
+ const DARK_LIGHTNESS_THRESHOLD = 0.4;
2708
+ const LIGHT_LIGHTNESS_THRESHOLD = 0.7;
2709
+ let transparentPixelsCount = 0;
2710
+ let darkPixelsCount = 0;
2711
+ let lightPixelsCount = 0;
2712
+ let i, x, y;
2713
+ let r, g, b, a;
2714
+ let l;
2715
+ for (y = 0; y < height; y++) {
2716
+ for (x = 0; x < width; x++) {
2717
+ i = 4 * (y * width + x);
2718
+ r = d[i + 0];
2719
+ g = d[i + 1];
2720
+ b = d[i + 2];
2721
+ a = d[i + 3];
2722
+ if (a / 255 < TRANSPARENT_ALPHA_THRESHOLD) {
2723
+ transparentPixelsCount++;
2724
+ } else {
2725
+ l = getSRGBLightness(r, g, b);
2726
+ if (l < DARK_LIGHTNESS_THRESHOLD) {
2727
+ darkPixelsCount++;
2728
+ }
2729
+ if (l > LIGHT_LIGHTNESS_THRESHOLD) {
2730
+ lightPixelsCount++;
2731
+ }
2732
+ }
2735
2733
  }
2736
- return {h, s, l: lx, a};
2737
- }
2738
- let lx = scale(l, 0.5, 1, MAX_BG_LIGHTNESS, pole.l);
2739
- if (isNeutral) {
2740
- const hx = pole.h;
2741
- const sx = pole.s;
2742
- return {h: hx, s: sx, l: lx, a};
2743
2734
  }
2744
- let hx = h;
2745
- const isYellow = h > 60 && h < 180;
2746
- if (isYellow) {
2747
- const isCloserToGreen = h > 120;
2748
- if (isCloserToGreen) {
2749
- hx = scale(h, 120, 180, 135, 180);
2750
- } else {
2751
- hx = scale(h, 60, 120, 60, 105);
2752
- }
2735
+ const totalPixelsCount = width * height;
2736
+ const opaquePixelsCount = totalPixelsCount - transparentPixelsCount;
2737
+ const DARK_IMAGE_THRESHOLD = 0.7;
2738
+ const LIGHT_IMAGE_THRESHOLD = 0.7;
2739
+ const TRANSPARENT_IMAGE_THRESHOLD = 0.1;
2740
+ return {
2741
+ isDark: darkPixelsCount / opaquePixelsCount >= DARK_IMAGE_THRESHOLD,
2742
+ isLight: lightPixelsCount / opaquePixelsCount >= LIGHT_IMAGE_THRESHOLD,
2743
+ isTransparent:
2744
+ transparentPixelsCount / totalPixelsCount >=
2745
+ TRANSPARENT_IMAGE_THRESHOLD,
2746
+ isLarge
2747
+ };
2748
+ }
2749
+ let isBlobURLSupported = null;
2750
+ let canUseProxy = false;
2751
+ let blobURLCheckRequested = false;
2752
+ const blobURLCheckAwaiters = [];
2753
+ document.addEventListener(
2754
+ "__darkreader__inlineScriptsAllowed",
2755
+ () => (canUseProxy = true),
2756
+ {once: true}
2757
+ );
2758
+ async function requestBlobURLCheck() {
2759
+ if (!canUseProxy) {
2760
+ return;
2753
2761
  }
2754
- if (hx > 40 && hx < 80) {
2755
- lx *= 0.75;
2762
+ if (blobURLCheckRequested) {
2763
+ return await new Promise((resolve) =>
2764
+ blobURLCheckAwaiters.push(resolve)
2765
+ );
2756
2766
  }
2757
- return {h: hx, s, l: lx, a};
2767
+ blobURLCheckRequested = true;
2768
+ await new Promise((resolve) => {
2769
+ document.addEventListener(
2770
+ "__darkreader__blobURLCheckResponse",
2771
+ (e) => {
2772
+ isBlobURLSupported = e.detail.blobURLAllowed;
2773
+ resolve();
2774
+ blobURLCheckAwaiters.forEach((r) => r());
2775
+ blobURLCheckAwaiters.splice(0);
2776
+ },
2777
+ {once: true}
2778
+ );
2779
+ document.dispatchEvent(
2780
+ new CustomEvent("__darkreader__blobURLCheckRequest")
2781
+ );
2782
+ });
2758
2783
  }
2759
- function _modifyBackgroundColor(rgb, theme) {
2760
- if (theme.mode === 0) {
2761
- return modifyLightSchemeColor(rgb, theme);
2762
- }
2763
- const pole = getBgPole(theme);
2764
- return modifyColorWithCache(rgb, {...theme, mode: 0}, modifyBgHSL, pole);
2784
+ function isBlobURLCheckResultReady() {
2785
+ return isBlobURLSupported != null || !canUseProxy;
2765
2786
  }
2766
- function modifyBackgroundColor(rgb, theme, shouldRegisterColorVariable = true) {
2767
- if (!shouldRegisterColorVariable) {
2768
- return _modifyBackgroundColor(rgb, theme);
2787
+ function onCSPError(err) {
2788
+ if (err.blockedURI === "blob") {
2789
+ isBlobURLSupported = false;
2790
+ document.removeEventListener("securitypolicyviolation", onCSPError);
2769
2791
  }
2770
- return modifyAndRegisterColor(
2771
- "background",
2772
- rgb,
2773
- theme,
2774
- _modifyBackgroundColor
2775
- );
2776
- }
2777
- const MIN_FG_LIGHTNESS = 0.55;
2778
- function modifyBlueFgHue(hue) {
2779
- return scale(hue, 205, 245, 205, 220);
2780
2792
  }
2781
- function modifyFgHSL({h, s, l, a}, pole) {
2782
- const isLight = l > 0.5;
2783
- const isNeutral = l < 0.2 || s < 0.24;
2784
- const isBlue = !isNeutral && h > 205 && h < 245;
2785
- if (isLight) {
2786
- const lx = scale(l, 0.5, 1, MIN_FG_LIGHTNESS, pole.l);
2787
- if (isNeutral) {
2788
- const hx = pole.h;
2789
- const sx = pole.s;
2790
- return {h: hx, s: sx, l: lx, a};
2791
- }
2792
- let hx = h;
2793
- if (isBlue) {
2794
- hx = modifyBlueFgHue(h);
2795
- }
2796
- return {h: hx, s, l: lx, a};
2793
+ document.addEventListener("securitypolicyviolation", onCSPError);
2794
+ const objectURLs = new Set();
2795
+ function getFilteredImageURL({dataURL, width, height}, theme) {
2796
+ if (dataURL.startsWith("data:image/svg+xml")) {
2797
+ dataURL = escapeXML(dataURL);
2797
2798
  }
2798
- if (isNeutral) {
2799
- const hx = pole.h;
2800
- const sx = pole.s;
2801
- const lx = scale(l, 0, 0.5, pole.l, MIN_FG_LIGHTNESS);
2802
- return {h: hx, s: sx, l: lx, a};
2799
+ const matrix = getSVGFilterMatrixValue(theme);
2800
+ const svg = [
2801
+ `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}">`,
2802
+ "<defs>",
2803
+ '<filter id="darkreader-image-filter">',
2804
+ `<feColorMatrix type="matrix" values="${matrix}" />`,
2805
+ "</filter>",
2806
+ "</defs>",
2807
+ `<image width="${width}" height="${height}" filter="url(#darkreader-image-filter)" xlink:href="${dataURL}" />`,
2808
+ "</svg>"
2809
+ ].join("");
2810
+ if (!isBlobURLSupported) {
2811
+ return `data:image/svg+xml;base64,${btoa(svg)}`;
2803
2812
  }
2804
- let hx = h;
2805
- let lx;
2806
- if (isBlue) {
2807
- hx = modifyBlueFgHue(h);
2808
- lx = scale(l, 0, 0.5, pole.l, Math.min(1, MIN_FG_LIGHTNESS + 0.05));
2809
- } else {
2810
- lx = scale(l, 0, 0.5, pole.l, MIN_FG_LIGHTNESS);
2813
+ const bytes = new Uint8Array(svg.length);
2814
+ for (let i = 0; i < svg.length; i++) {
2815
+ bytes[i] = svg.charCodeAt(i);
2811
2816
  }
2812
- return {h: hx, s, l: lx, a};
2817
+ const blob = new Blob([bytes], {type: "image/svg+xml"});
2818
+ const objectURL = URL.createObjectURL(blob);
2819
+ objectURLs.add(objectURL);
2820
+ return objectURL;
2813
2821
  }
2814
- function _modifyForegroundColor(rgb, theme) {
2815
- if (theme.mode === 0) {
2816
- return modifyLightSchemeColor(rgb, theme);
2817
- }
2818
- const pole = getFgPole(theme);
2819
- return modifyColorWithCache(rgb, {...theme, mode: 0}, modifyFgHSL, pole);
2822
+ const xmlEscapeChars = {
2823
+ "<": "&lt;",
2824
+ ">": "&gt;",
2825
+ "&": "&amp;",
2826
+ "'": "&apos;",
2827
+ '"': "&quot;"
2828
+ };
2829
+ function escapeXML(str) {
2830
+ return str.replace(/[<>&'"]/g, (c) => xmlEscapeChars[c] ?? c);
2820
2831
  }
2821
- function modifyForegroundColor(rgb, theme, shouldRegisterColorVariable = true) {
2822
- if (!shouldRegisterColorVariable) {
2823
- return _modifyForegroundColor(rgb, theme);
2832
+ const dataURLBlobURLs = new Map();
2833
+ function tryConvertDataURLToBlobSync(dataURL) {
2834
+ const colonIndex = dataURL.indexOf(":");
2835
+ const semicolonIndex = dataURL.indexOf(";", colonIndex + 1);
2836
+ const commaIndex = dataURL.indexOf(",", semicolonIndex + 1);
2837
+ const encoding = dataURL
2838
+ .substring(semicolonIndex + 1, commaIndex)
2839
+ .toLocaleLowerCase();
2840
+ const mediaType = dataURL.substring(colonIndex + 1, semicolonIndex);
2841
+ if (encoding !== "base64" || !mediaType) {
2842
+ return null;
2824
2843
  }
2825
- return modifyAndRegisterColor("text", rgb, theme, _modifyForegroundColor);
2826
- }
2827
- function modifyBorderHSL({h, s, l, a}, poleFg, poleBg) {
2828
- const isDark = l < 0.5;
2829
- const isNeutral = l < 0.2 || s < 0.24;
2830
- let hx = h;
2831
- let sx = s;
2832
- if (isNeutral) {
2833
- if (isDark) {
2834
- hx = poleFg.h;
2835
- sx = poleFg.s;
2836
- } else {
2837
- hx = poleBg.h;
2838
- sx = poleBg.s;
2839
- }
2844
+ const characters = atob(dataURL.substring(commaIndex + 1));
2845
+ const bytes = new Uint8Array(characters.length);
2846
+ for (let i = 0; i < characters.length; i++) {
2847
+ bytes[i] = characters.charCodeAt(i);
2840
2848
  }
2841
- const lx = scale(l, 0, 1, 0.5, 0.2);
2842
- return {h: hx, s: sx, l: lx, a};
2849
+ return new Blob([bytes], {type: mediaType});
2843
2850
  }
2844
- function _modifyBorderColor(rgb, theme) {
2845
- if (theme.mode === 0) {
2846
- return modifyLightSchemeColor(rgb, theme);
2851
+ async function tryConvertDataURLToBlobURL(dataURL) {
2852
+ if (!isBlobURLSupported) {
2853
+ return null;
2847
2854
  }
2848
- const poleFg = getFgPole(theme);
2849
- const poleBg = getBgPole(theme);
2850
- return modifyColorWithCache(
2851
- rgb,
2852
- {...theme, mode: 0},
2853
- modifyBorderHSL,
2854
- poleFg,
2855
- poleBg
2856
- );
2857
- }
2858
- function modifyBorderColor(rgb, theme, shouldRegisterColorVariable = true) {
2859
- if (!shouldRegisterColorVariable) {
2860
- return _modifyBorderColor(rgb, theme);
2855
+ const hash = getHashCode(dataURL);
2856
+ let blobURL = dataURLBlobURLs.get(hash);
2857
+ if (blobURL) {
2858
+ return blobURL;
2861
2859
  }
2862
- return modifyAndRegisterColor("border", rgb, theme, _modifyBorderColor);
2863
- }
2864
- function modifyShadowColor(rgb, theme) {
2865
- return modifyBackgroundColor(rgb, theme);
2860
+ let blob = tryConvertDataURLToBlobSync(dataURL);
2861
+ if (!blob) {
2862
+ const response = await fetch(dataURL);
2863
+ blob = await response.blob();
2864
+ }
2865
+ blobURL = URL.createObjectURL(blob);
2866
+ dataURLBlobURLs.set(hash, blobURL);
2867
+ return blobURL;
2866
2868
  }
2867
- function modifyGradientColor(rgb, theme) {
2868
- return modifyBackgroundColor(rgb, theme);
2869
+ function cleanImageProcessingCache() {
2870
+ imageManager && imageManager.stop();
2871
+ removeCanvas();
2872
+ objectURLs.forEach((u) => URL.revokeObjectURL(u));
2873
+ objectURLs.clear();
2874
+ dataURLBlobURLs.forEach((u) => URL.revokeObjectURL(u));
2875
+ dataURLBlobURLs.clear();
2869
2876
  }
2870
2877
 
2871
2878
  function getPriority(ruleStyle, property) {
@@ -3126,10 +3133,18 @@ const unparsableColors = new Set([
3126
3133
  "auto"
3127
3134
  ]);
3128
3135
  function getColorModifier(prop, value, rule) {
3129
- if (unparsableColors.has(value.toLowerCase())) {
3136
+ if (
3137
+ unparsableColors.has(value.toLowerCase()) &&
3138
+ !(prop === "color" && value === "initial")
3139
+ ) {
3130
3140
  return value;
3131
3141
  }
3132
- const rgb = parseColorWithCache(value);
3142
+ let rgb = null;
3143
+ if (prop === "color" && value === "initial") {
3144
+ rgb = {r: 0, g: 0, b: 0, a: 1};
3145
+ } else {
3146
+ rgb = parseColorWithCache(value);
3147
+ }
3133
3148
  if (!rgb) {
3134
3149
  logWarn("Couldn't parse color", value);
3135
3150
  return null;
@@ -3531,7 +3546,7 @@ function getScrollbarColorModifier(value) {
3531
3546
  return null;
3532
3547
  }
3533
3548
  return (theme) =>
3534
- `${modifyForegroundColor(thumb, theme)} ${modifyBackgroundColor(thumb, theme)}`;
3549
+ `${modifyForegroundColor(thumb, theme)} ${modifyBackgroundColor(track, theme)}`;
3535
3550
  }
3536
3551
  function getColorSchemeModifier() {
3537
3552
  return (theme) => (theme.mode === 0 ? "dark light" : "dark");
@@ -3882,7 +3897,7 @@ class VariablesStore {
3882
3897
  const modified = modify();
3883
3898
  if (unknownVars.size > 0) {
3884
3899
  const isFallbackResolved = modified.match(
3885
- /^var\(.*?, (var\(--darkreader-bg--.*\))|(#[0-9A-Fa-f]+)|([a-z]+)|(rgba?\(.+\))|(hsla?\(.+\))\)$/
3900
+ /^var\(.*?, ((var\(--darkreader-bg--.*\))|(#[0-9A-Fa-f]+)|([a-z]+)|(rgba?\(.+\))|(hsla?\(.+\)))\)$/
3886
3901
  );
3887
3902
  if (isFallbackResolved) {
3888
3903
  return modified;
@@ -4352,17 +4367,6 @@ function insertVarValues(source, varValues, fullStack = new Set()) {
4352
4367
  return replaced;
4353
4368
  }
4354
4369
 
4355
- const themeCacheKeys = [
4356
- "mode",
4357
- "brightness",
4358
- "contrast",
4359
- "grayscale",
4360
- "sepia",
4361
- "darkSchemeBackgroundColor",
4362
- "darkSchemeTextColor",
4363
- "lightSchemeBackgroundColor",
4364
- "lightSchemeTextColor"
4365
- ];
4366
4370
  function getThemeKey(theme) {
4367
4371
  let resultKey = "";
4368
4372
  themeCacheKeys.forEach((key) => {
@@ -4673,6 +4677,12 @@ function createStyleSheetModifier() {
4673
4677
  function buildStyleSheet() {
4674
4678
  function createTarget(group, parent) {
4675
4679
  const {rule} = group;
4680
+ if (isStyleRule(rule)) {
4681
+ const {selectorText} = rule;
4682
+ const index = parent.cssRules.length;
4683
+ parent.insertRule(`${selectorText} {}`, index);
4684
+ return parent.cssRules[index];
4685
+ }
4676
4686
  if (isMediaRule(rule)) {
4677
4687
  const {media} = rule;
4678
4688
  const index = parent.cssRules.length;
@@ -4973,6 +4983,25 @@ function createAdoptedStyleSheetFallback() {
4973
4983
  return {render, destroy, commands};
4974
4984
  }
4975
4985
 
4986
+ const hostsBreakingOnStylePosition = ["www.diffusioneshop.com", "zhale.me"];
4987
+ const mode = hostsBreakingOnStylePosition.includes(location.hostname)
4988
+ ? "away"
4989
+ : "next";
4990
+ function getStyleInjectionMode() {
4991
+ return mode;
4992
+ }
4993
+ function injectStyleAway(styleElement) {
4994
+ let container = document.body.querySelector(".darkreader-style-container");
4995
+ if (!container) {
4996
+ container = document.createElement("div");
4997
+ container.classList.add("darkreader");
4998
+ container.classList.add("darkreader-style-container");
4999
+ container.style.display = "none";
5000
+ document.body.append(container);
5001
+ }
5002
+ container.append(styleElement);
5003
+ }
5004
+
4976
5005
  const overrides = {
4977
5006
  "background-color": {
4978
5007
  customProp: "--darkreader-inline-bgcolor",
@@ -5738,19 +5767,27 @@ function cleanLoadingLinks() {
5738
5767
  rejectorsForLoadingLinks.clear();
5739
5768
  }
5740
5769
  function manageStyle(element, {update, loadingStart, loadingEnd}) {
5741
- const prevStyles = [];
5742
- let next = element;
5743
- while ((next = next.nextElementSibling) && next.matches(".darkreader")) {
5744
- prevStyles.push(next);
5745
- }
5746
- let corsCopy =
5747
- prevStyles.find(
5748
- (el) => el.matches(".darkreader--cors") && !corsStyleSet.has(el)
5749
- ) || null;
5750
- let syncStyle =
5751
- prevStyles.find(
5752
- (el) => el.matches(".darkreader--sync") && !syncStyleSet.has(el)
5753
- ) || null;
5770
+ const inMode = getStyleInjectionMode();
5771
+ let corsCopy = null;
5772
+ let syncStyle = null;
5773
+ if (inMode === "next") {
5774
+ const prevStyles = [];
5775
+ let next = element;
5776
+ while (
5777
+ (next = next.nextElementSibling) &&
5778
+ next.matches(".darkreader")
5779
+ ) {
5780
+ prevStyles.push(next);
5781
+ }
5782
+ corsCopy =
5783
+ prevStyles.find(
5784
+ (el) => el.matches(".darkreader--cors") && !corsStyleSet.has(el)
5785
+ ) || null;
5786
+ syncStyle =
5787
+ prevStyles.find(
5788
+ (el) => el.matches(".darkreader--sync") && !syncStyleSet.has(el)
5789
+ ) || null;
5790
+ }
5754
5791
  let corsCopyPositionWatcher = null;
5755
5792
  let syncStylePositionWatcher = null;
5756
5793
  let cancelAsyncOperations = false;
@@ -5830,18 +5867,28 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
5830
5867
  return cssRules;
5831
5868
  }
5832
5869
  function insertStyle() {
5833
- if (corsCopy) {
5834
- if (element.nextSibling !== corsCopy) {
5835
- element.parentNode.insertBefore(corsCopy, element.nextSibling);
5870
+ if (inMode === "next") {
5871
+ if (corsCopy) {
5872
+ if (element.nextSibling !== corsCopy) {
5873
+ element.parentNode.insertBefore(
5874
+ corsCopy,
5875
+ element.nextSibling
5876
+ );
5877
+ }
5878
+ if (corsCopy.nextSibling !== syncStyle) {
5879
+ element.parentNode.insertBefore(
5880
+ syncStyle,
5881
+ corsCopy.nextSibling
5882
+ );
5883
+ }
5884
+ } else if (element.nextSibling !== syncStyle) {
5885
+ element.parentNode.insertBefore(syncStyle, element.nextSibling);
5836
5886
  }
5837
- if (corsCopy.nextSibling !== syncStyle) {
5838
- element.parentNode.insertBefore(
5839
- syncStyle,
5840
- corsCopy.nextSibling
5841
- );
5887
+ } else if (inMode === "away") {
5888
+ if (corsCopy && !corsCopy.parentNode) {
5889
+ injectStyleAway(corsCopy);
5842
5890
  }
5843
- } else if (element.nextSibling !== syncStyle) {
5844
- element.parentNode.insertBefore(syncStyle, element.nextSibling);
5891
+ injectStyleAway(syncStyle);
5845
5892
  }
5846
5893
  }
5847
5894
  function createSyncStyle() {
@@ -5899,7 +5946,12 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
5899
5946
  return cssRules;
5900
5947
  }
5901
5948
  }
5902
- cssText = await loadText(element.href);
5949
+ try {
5950
+ cssText = await loadText(element.href);
5951
+ } catch (err) {
5952
+ logWarn(err);
5953
+ cssText = "";
5954
+ }
5903
5955
  cssBasePath = getCSSBaseBath(element.href);
5904
5956
  if (cancelAsyncOperations) {
5905
5957
  return null;
@@ -5930,12 +5982,31 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
5930
5982
  corsCopy.textContent = fullCSSText;
5931
5983
  }
5932
5984
  } else {
5933
- corsCopy = createCORSCopy(element, fullCSSText);
5985
+ corsCopy = createCORSCopy(
5986
+ fullCSSText,
5987
+ inMode === "next"
5988
+ ? (cc) =>
5989
+ element.parentNode.insertBefore(
5990
+ cc,
5991
+ element.nextSibling
5992
+ )
5993
+ : injectStyleAway
5994
+ );
5995
+ if (corsCopy) {
5996
+ if (inMode === "next") {
5997
+ element.parentNode.insertBefore(
5998
+ corsCopy,
5999
+ element.nextSibling
6000
+ );
6001
+ } else if (inMode === "away") {
6002
+ injectStyleAway(corsCopy);
6003
+ }
6004
+ }
5934
6005
  }
5935
6006
  } catch (err) {
5936
6007
  logWarn(err);
5937
6008
  }
5938
- if (corsCopy) {
6009
+ if (corsCopy && inMode === "next") {
5939
6010
  corsCopyPositionWatcher = watchForNodePosition(
5940
6011
  corsCopy,
5941
6012
  "prev-sibling"
@@ -6002,7 +6073,7 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
6002
6073
  removeCSSRulesFromSheet(sheet);
6003
6074
  if (syncStylePositionWatcher) {
6004
6075
  syncStylePositionWatcher.run();
6005
- } else {
6076
+ } else if (inMode === "next") {
6006
6077
  syncStylePositionWatcher = watchForNodePosition(
6007
6078
  syncStyle,
6008
6079
  "prev-sibling",
@@ -6223,7 +6294,7 @@ async function replaceCSSImports(cssText, basePath, cache = new Map()) {
6223
6294
  cssText = cssText.trim();
6224
6295
  return cssText;
6225
6296
  }
6226
- function createCORSCopy(srcElement, cssText) {
6297
+ function createCORSCopy(cssText, inject) {
6227
6298
  if (!cssText) {
6228
6299
  return null;
6229
6300
  }
@@ -6232,7 +6303,7 @@ function createCORSCopy(srcElement, cssText) {
6232
6303
  cors.classList.add("darkreader--cors");
6233
6304
  cors.media = "screen";
6234
6305
  cors.textContent = cssText;
6235
- srcElement.parentNode.insertBefore(cors, srcElement.nextSibling);
6306
+ inject(cors);
6236
6307
  cors.sheet.disabled = true;
6237
6308
  corsStyleSet.add(cors);
6238
6309
  return cors;
@@ -7007,20 +7078,24 @@ let fixes = null;
7007
7078
  let isIFrame$1 = null;
7008
7079
  let ignoredImageAnalysisSelectors = [];
7009
7080
  let ignoredInlineSelectors = [];
7010
- const staticStyleMap = new Map();
7081
+ let staticStyleMap = new WeakMap();
7011
7082
  function createOrUpdateStyle(className, root = document.head || document) {
7012
7083
  let element = root.querySelector(`.${className}`);
7084
+ if (!staticStyleMap.has(root)) {
7085
+ staticStyleMap.set(root, new Map());
7086
+ }
7087
+ const classMap = staticStyleMap.get(root);
7013
7088
  if (element) {
7014
- staticStyleMap.set(className, element);
7015
- } else if (staticStyleMap.has(className)) {
7016
- element = staticStyleMap.get(className);
7089
+ classMap.set(className, element);
7090
+ } else if (classMap.has(className)) {
7091
+ element = classMap.get(className);
7017
7092
  } else {
7018
7093
  element = document.createElement("style");
7019
7094
  element.classList.add("darkreader");
7020
7095
  element.classList.add(className);
7021
7096
  element.media = "screen";
7022
7097
  element.textContent = "";
7023
- staticStyleMap.set(className, element);
7098
+ classMap.set(className, element);
7024
7099
  }
7025
7100
  return element;
7026
7101
  }
@@ -7045,27 +7120,36 @@ function stopStylePositionWatchers() {
7045
7120
  forEach(nodePositionWatchers.values(), (watcher) => watcher.stop());
7046
7121
  nodePositionWatchers.clear();
7047
7122
  }
7123
+ function injectStaticStyle(style, prevNode, watchAlias, callback) {
7124
+ const mode = getStyleInjectionMode();
7125
+ if (mode === "next") {
7126
+ document.head.insertBefore(
7127
+ style,
7128
+ prevNode ? prevNode.nextSibling : document.head.firstChild
7129
+ );
7130
+ setupNodePositionWatcher(style, watchAlias, callback);
7131
+ } else if (mode === "away") {
7132
+ injectStyleAway(style);
7133
+ }
7134
+ }
7048
7135
  function createStaticStyleOverrides() {
7049
7136
  const fallbackStyle = createOrUpdateStyle("darkreader--fallback", document);
7050
7137
  fallbackStyle.textContent = getModifiedFallbackStyle(theme, {strict: true});
7051
- document.head.insertBefore(fallbackStyle, document.head.firstChild);
7052
- setupNodePositionWatcher(fallbackStyle, "fallback");
7138
+ injectStaticStyle(fallbackStyle, null, "fallback");
7053
7139
  const userAgentStyle = createOrUpdateStyle("darkreader--user-agent");
7054
7140
  userAgentStyle.textContent = getModifiedUserAgentStyle(
7055
7141
  theme,
7056
7142
  isIFrame$1,
7057
7143
  theme.styleSystemControls
7058
7144
  );
7059
- document.head.insertBefore(userAgentStyle, fallbackStyle.nextSibling);
7060
- setupNodePositionWatcher(userAgentStyle, "user-agent");
7145
+ injectStaticStyle(userAgentStyle, fallbackStyle, "user-agent");
7061
7146
  const textStyle = createOrUpdateStyle("darkreader--text");
7062
7147
  if (theme.useFont || theme.textStroke > 0) {
7063
7148
  textStyle.textContent = createTextStyle(theme);
7064
7149
  } else {
7065
7150
  textStyle.textContent = "";
7066
7151
  }
7067
- document.head.insertBefore(textStyle, fallbackStyle.nextSibling);
7068
- setupNodePositionWatcher(textStyle, "text");
7152
+ injectStaticStyle(textStyle, userAgentStyle, "text");
7069
7153
  const invertStyle = createOrUpdateStyle("darkreader--invert");
7070
7154
  if (fixes && Array.isArray(fixes.invert) && fixes.invert.length > 0) {
7071
7155
  invertStyle.textContent = [
@@ -7082,17 +7166,10 @@ function createStaticStyleOverrides() {
7082
7166
  } else {
7083
7167
  invertStyle.textContent = "";
7084
7168
  }
7085
- document.head.insertBefore(invertStyle, textStyle.nextSibling);
7086
- setupNodePositionWatcher(invertStyle, "invert");
7169
+ injectStaticStyle(invertStyle, textStyle, "invert");
7087
7170
  const inlineStyle = createOrUpdateStyle("darkreader--inline");
7088
7171
  inlineStyle.textContent = getInlineOverrideStyle();
7089
- document.head.insertBefore(inlineStyle, invertStyle.nextSibling);
7090
- setupNodePositionWatcher(inlineStyle, "inline");
7091
- const overrideStyle = createOrUpdateStyle("darkreader--override");
7092
- overrideStyle.textContent =
7093
- fixes && fixes.css ? replaceCSSTemplates(fixes.css) : "";
7094
- document.head.appendChild(overrideStyle);
7095
- setupNodePositionWatcher(overrideStyle, "override");
7172
+ injectStaticStyle(inlineStyle, invertStyle, "inline");
7096
7173
  const variableStyle = createOrUpdateStyle("darkreader--variables");
7097
7174
  const selectionColors = theme?.selectionColor
7098
7175
  ? getSelectionColor(theme)
@@ -7113,13 +7190,12 @@ function createStaticStyleOverrides() {
7113
7190
  ` --darkreader-selection-text: ${selectionColors?.foregroundColorSelection ?? "initial"};`,
7114
7191
  `}`
7115
7192
  ].join("\n");
7116
- document.head.insertBefore(variableStyle, inlineStyle.nextSibling);
7117
- setupNodePositionWatcher(variableStyle, "variables", () =>
7193
+ injectStaticStyle(variableStyle, inlineStyle, "variables", () =>
7118
7194
  registerVariablesSheet(variableStyle.sheet)
7119
7195
  );
7120
7196
  registerVariablesSheet(variableStyle.sheet);
7121
7197
  const rootVarsStyle = createOrUpdateStyle("darkreader--root-vars");
7122
- document.head.insertBefore(rootVarsStyle, variableStyle.nextSibling);
7198
+ injectStaticStyle(rootVarsStyle, variableStyle, "root-vars");
7123
7199
  const enableStyleSheetsProxy = !(fixes && fixes.disableStyleSheetsProxy);
7124
7200
  const enableCustomElementRegistryProxy = !(
7125
7201
  fixes && fixes.disableCustomElementRegistryProxy
@@ -7133,6 +7209,10 @@ function createStaticStyleOverrides() {
7133
7209
  document.head.insertBefore(proxyScript, rootVarsStyle.nextSibling);
7134
7210
  proxyScript.remove();
7135
7211
  }
7212
+ const overrideStyle = createOrUpdateStyle("darkreader--override");
7213
+ overrideStyle.textContent =
7214
+ fixes && fixes.css ? replaceCSSTemplates(fixes.css) : "";
7215
+ injectStaticStyle(overrideStyle, document.head.lastChild, "override");
7136
7216
  }
7137
7217
  const shadowRootsWithOverrides = new Set();
7138
7218
  function createShadowStaticStyleOverridesInner(root) {
@@ -7208,7 +7288,8 @@ function replaceCSSTemplates($cssText) {
7208
7288
  }
7209
7289
  function cleanFallbackStyle() {
7210
7290
  const fallback =
7211
- staticStyleMap.get("darkreader--fallback") ||
7291
+ staticStyleMap.get(document.head)?.get("darkreader--fallback") ||
7292
+ staticStyleMap.get(document)?.get("darkreader--fallback") ||
7212
7293
  document.querySelector(".darkreader--fallback");
7213
7294
  if (fallback) {
7214
7295
  fallback.textContent = "";
@@ -7737,7 +7818,7 @@ function removeDynamicTheme() {
7737
7818
  selectors.forEach((selector) =>
7738
7819
  removeNode(document.head.querySelector(selector))
7739
7820
  );
7740
- staticStyleMap.clear();
7821
+ staticStyleMap = new WeakMap();
7741
7822
  removeProxy();
7742
7823
  }
7743
7824
  shadowRootsWithOverrides.forEach((root) => {