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.js 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
 
@@ -43,6 +43,8 @@
43
43
  "ui-bg-apply-dev-static-themes";
44
44
  MessageTypeUItoBG["RESET_DEV_STATIC_THEMES"] =
45
45
  "ui-bg-reset-dev-static-themes";
46
+ MessageTypeUItoBG["START_ACTIVATION"] = "ui-bg-start-activation";
47
+ MessageTypeUItoBG["RESET_ACTIVATION"] = "ui-bg-reset-activation";
46
48
  MessageTypeUItoBG["COLOR_SCHEME_CHANGE"] = "ui-bg-color-scheme-change";
47
49
  MessageTypeUItoBG["HIDE_HIGHLIGHTS"] = "ui-bg-hide-highlights";
48
50
  })(MessageTypeUItoBG || (MessageTypeUItoBG = {}));
@@ -372,9 +374,7 @@
372
374
  ];
373
375
  ({
374
376
  customThemes: filterModeSites.map((url) => {
375
- const engine = isChromium
376
- ? ThemeEngine.svgFilter
377
- : ThemeEngine.cssFilter;
377
+ const engine = ThemeEngine.cssFilter;
378
378
  return {
379
379
  url: [url],
380
380
  theme: {...DEFAULT_THEME, engine},
@@ -985,10 +985,16 @@
985
985
  function parse($color) {
986
986
  const c = $color.trim().toLowerCase();
987
987
  if (c.includes("(from ")) {
988
+ if (c.indexOf("(from") !== c.lastIndexOf("(from")) {
989
+ return null;
990
+ }
988
991
  return domParseColor(c);
989
992
  }
990
993
  if (c.match(rgbMatch)) {
991
994
  if (c.startsWith("rgb(#") || c.startsWith("rgba(#")) {
995
+ if (c.lastIndexOf("rgb") > 0) {
996
+ return null;
997
+ }
992
998
  return domParseColor(c);
993
999
  }
994
1000
  return parseRGB(c);
@@ -1011,7 +1017,10 @@
1011
1017
  if (
1012
1018
  c.endsWith(")") &&
1013
1019
  supportedColorFuncs.some(
1014
- (fn) => c.startsWith(fn) && c[fn.length] === "("
1020
+ (fn) =>
1021
+ c.startsWith(fn) &&
1022
+ c[fn.length] === "(" &&
1023
+ c.lastIndexOf(fn) === 0
1015
1024
  )
1016
1025
  ) {
1017
1026
  return domParseColor(c);
@@ -1840,6 +1849,9 @@
1840
1849
  forEach(rules, (rule) => {
1841
1850
  if (isStyleRule(rule)) {
1842
1851
  iterate(rule);
1852
+ if (rule.cssRules?.length > 0) {
1853
+ iterateCSSRules(rule.cssRules, iterate);
1854
+ }
1843
1855
  } else if (isImportRule(rule)) {
1844
1856
  try {
1845
1857
  iterateCSSRules(
@@ -2097,862 +2109,855 @@
2097
2109
  return null;
2098
2110
  }
2099
2111
 
2100
- const gradientLength = "gradient".length;
2101
- const conicGradient = "conic-";
2102
- const conicGradientLength = conicGradient.length;
2103
- const radialGradient = "radial-";
2104
- const linearGradient = "linear-";
2105
- function parseGradient(value) {
2106
- const result = [];
2107
- let index = 0;
2108
- let startIndex = conicGradient.length;
2109
- while ((index = value.indexOf("gradient", startIndex)) !== -1) {
2110
- let typeGradient;
2111
- [linearGradient, radialGradient, conicGradient].find(
2112
- (possibleType) => {
2113
- if (index - possibleType.length >= 0) {
2114
- const possibleGradient = value.substring(
2115
- index - possibleType.length,
2116
- index
2117
- );
2118
- if (possibleGradient === possibleType) {
2119
- if (
2120
- value.slice(
2121
- index - possibleType.length - 10,
2122
- index - possibleType.length - 1
2123
- ) === "repeating"
2124
- ) {
2125
- typeGradient = `repeating-${possibleType}gradient`;
2126
- return true;
2127
- }
2128
- if (
2129
- value.slice(
2130
- index - possibleType.length - 8,
2131
- index - possibleType.length - 1
2132
- ) === "-webkit"
2133
- ) {
2134
- typeGradient = `-webkit-${possibleType}gradient`;
2135
- return true;
2136
- }
2137
- typeGradient = `${possibleType}gradient`;
2138
- return true;
2139
- }
2140
- }
2112
+ let variablesSheet;
2113
+ const registeredColors = new Map();
2114
+ function registerVariablesSheet(sheet) {
2115
+ variablesSheet = sheet;
2116
+ const types = ["background", "text", "border"];
2117
+ registeredColors.forEach((registered) => {
2118
+ types.forEach((type) => {
2119
+ if (registered[type]) {
2120
+ const {variable, value} = registered[type];
2121
+ variablesSheet?.cssRules[0].style.setProperty(
2122
+ variable,
2123
+ value
2124
+ );
2141
2125
  }
2142
- );
2143
- if (!typeGradient) {
2144
- break;
2145
- }
2146
- const {start, end} = getParenthesesRange(
2147
- value,
2148
- index + gradientLength
2149
- );
2150
- const match = value.substring(start + 1, end - 1);
2151
- startIndex = end + 1 + conicGradientLength;
2152
- result.push({
2153
- typeGradient,
2154
- match,
2155
- offset: typeGradient.length + 2,
2156
- index: index - typeGradient.length + gradientLength,
2157
- hasComma: true
2158
2126
  });
2159
- }
2160
- if (result.length) {
2161
- result[result.length - 1].hasComma = false;
2162
- }
2163
- return result;
2164
- }
2165
-
2166
- const STORAGE_KEY_IMAGE_DETAILS_LIST = "__darkreader__imageDetails_v2_list";
2167
- const STORAGE_KEY_IMAGE_DETAILS_PREFIX = "__darkreader__imageDetails_v2_";
2168
- const STORAGE_KEY_CSS_FETCH_PREFIX = "__darkreader__cssFetch_";
2169
- let imageCacheTimeout = 0;
2170
- const imageDetailsCacheQueue = new Map();
2171
- const cachedImageUrls = [];
2172
- function writeImageDetailsQueue() {
2173
- imageDetailsCacheQueue.forEach((details, url) => {
2174
- if (url && url.startsWith("https://")) {
2175
- try {
2176
- const json = JSON.stringify(details);
2177
- sessionStorage.setItem(
2178
- `${STORAGE_KEY_IMAGE_DETAILS_PREFIX}${url}`,
2179
- json
2180
- );
2181
- cachedImageUrls.push(url);
2182
- } catch (err) {}
2183
- }
2184
2127
  });
2185
- imageDetailsCacheQueue.clear();
2186
- sessionStorage.setItem(
2187
- STORAGE_KEY_IMAGE_DETAILS_LIST,
2188
- JSON.stringify(cachedImageUrls)
2189
- );
2190
- }
2191
- function writeImageDetailsCache(url, imageDetails) {
2192
- if (!url || !url.startsWith("https://")) {
2193
- return;
2194
- }
2195
- imageDetailsCacheQueue.set(url, imageDetails);
2196
- clearTimeout(imageCacheTimeout);
2197
- imageCacheTimeout = setTimeout(writeImageDetailsQueue, 1000);
2198
2128
  }
2199
- function readImageDetailsCache(targetMap) {
2200
- try {
2201
- const jsonList = sessionStorage.getItem(
2202
- STORAGE_KEY_IMAGE_DETAILS_LIST
2203
- );
2204
- if (!jsonList) {
2205
- return;
2206
- }
2207
- const list = JSON.parse(jsonList);
2208
- list.forEach((url) => {
2209
- const json = sessionStorage.getItem(
2210
- `${STORAGE_KEY_IMAGE_DETAILS_PREFIX}${url}`
2211
- );
2212
- if (json) {
2213
- const details = JSON.parse(json);
2214
- targetMap.set(url, details);
2215
- }
2216
- });
2217
- } catch (err) {}
2129
+ function releaseVariablesSheet() {
2130
+ variablesSheet = null;
2131
+ clearColorPalette();
2218
2132
  }
2219
- function writeCSSFetchCache(url, cssText) {
2220
- const key = `${STORAGE_KEY_CSS_FETCH_PREFIX}${url}`;
2221
- try {
2222
- sessionStorage.setItem(key, cssText);
2223
- } catch (err) {}
2133
+ function getRegisteredVariableValue(type, registered) {
2134
+ return `var(${registered[type].variable}, ${registered[type].value})`;
2224
2135
  }
2225
- function readCSSFetchCache(url) {
2226
- const key = `${STORAGE_KEY_CSS_FETCH_PREFIX}${url}`;
2227
- try {
2228
- return sessionStorage.getItem(key) ?? null;
2229
- } catch (err) {}
2136
+ function getRegisteredColor(type, parsed) {
2137
+ const hex = rgbToHexString(parsed);
2138
+ const registered = registeredColors.get(hex);
2139
+ if (registered?.[type]) {
2140
+ return getRegisteredVariableValue(type, registered);
2141
+ }
2230
2142
  return null;
2231
2143
  }
2232
-
2233
- function toSVGMatrix(matrix) {
2234
- return matrix
2235
- .slice(0, 4)
2236
- .map((m) => m.map((m) => m.toFixed(3)).join(" "))
2237
- .join(" ");
2238
- }
2239
- function getSVGFilterMatrixValue(config) {
2240
- return toSVGMatrix(createFilterMatrix(config));
2241
- }
2242
-
2243
- const MAX_FRAME_DURATION = 1000 / 60;
2244
- class AsyncQueue {
2245
- constructor() {
2246
- this.queue = [];
2247
- this.timerId = null;
2144
+ function registerColor(type, parsed, value) {
2145
+ const hex = rgbToHexString(parsed);
2146
+ let registered;
2147
+ if (registeredColors.has(hex)) {
2148
+ registered = registeredColors.get(hex);
2149
+ } else {
2150
+ const parsed = parseColorWithCache(hex);
2151
+ registered = {parsed};
2152
+ registeredColors.set(hex, registered);
2248
2153
  }
2249
- addTask(task) {
2250
- this.queue.push(task);
2251
- this.scheduleFrame();
2154
+ const variable = `--darkreader-${type}-${hex.replace("#", "")}`;
2155
+ registered[type] = {variable, value};
2156
+ if (variablesSheet?.cssRules[0]?.style) {
2157
+ variablesSheet?.cssRules[0].style.setProperty(variable, value);
2252
2158
  }
2253
- stop() {
2254
- if (this.timerId !== null) {
2255
- cancelAnimationFrame(this.timerId);
2256
- this.timerId = null;
2159
+ return getRegisteredVariableValue(type, registered);
2160
+ }
2161
+ function getColorPalette() {
2162
+ const background = [];
2163
+ const border = [];
2164
+ const text = [];
2165
+ registeredColors.forEach((registered) => {
2166
+ if (registered.background) {
2167
+ background.push(registered.parsed);
2257
2168
  }
2258
- this.queue = [];
2259
- }
2260
- scheduleFrame() {
2261
- if (this.timerId) {
2262
- return;
2169
+ if (registered.border) {
2170
+ border.push(registered.parsed);
2171
+ }
2172
+ if (registered.text) {
2173
+ text.push(registered.parsed);
2263
2174
  }
2264
- this.timerId = requestAnimationFrame(() => {
2265
- this.timerId = null;
2266
- const start = Date.now();
2267
- let cb;
2268
- while ((cb = this.queue.shift())) {
2269
- cb();
2270
- if (Date.now() - start >= MAX_FRAME_DURATION) {
2271
- this.scheduleFrame();
2272
- break;
2273
- }
2274
- }
2275
- });
2276
- }
2277
- }
2278
-
2279
- const resolvers$1 = new Map();
2280
- const rejectors = new Map();
2281
- async function bgFetch(request) {
2282
- if (window.DarkReader?.Plugins?.fetch) {
2283
- return window.DarkReader.Plugins.fetch(request);
2284
- }
2285
- return new Promise((resolve, reject) => {
2286
- const id = generateUID();
2287
- resolvers$1.set(id, resolve);
2288
- rejectors.set(id, reject);
2289
- chrome.runtime.sendMessage({
2290
- type: MessageTypeCStoBG.FETCH,
2291
- data: request,
2292
- id
2293
- });
2294
2175
  });
2176
+ return {background, border, text};
2177
+ }
2178
+ function clearColorPalette() {
2179
+ registeredColors.clear();
2295
2180
  }
2296
- chrome.runtime.onMessage.addListener(({type, data, error, id}) => {
2297
- if (type === MessageTypeBGtoCS.FETCH_RESPONSE) {
2298
- const resolve = resolvers$1.get(id);
2299
- const reject = rejectors.get(id);
2300
- resolvers$1.delete(id);
2301
- rejectors.delete(id);
2302
- if (error) {
2303
- reject && reject(error);
2304
- } else {
2305
- resolve && resolve(data);
2306
- }
2307
- }
2308
- });
2309
2181
 
2310
- const imageManager = new AsyncQueue();
2311
- async function getImageDetails(url) {
2312
- return new Promise(async (resolve, reject) => {
2313
- try {
2314
- const dataURL = url.startsWith("data:")
2315
- ? url
2316
- : await getDataURL(url);
2317
- const blob =
2318
- tryConvertDataURLToBlobSync(dataURL) ??
2319
- (await loadAsBlob(url));
2320
- let image;
2321
- if (dataURL.startsWith("data:image/svg+xml")) {
2322
- image = await loadImage(dataURL);
2323
- } else {
2324
- image =
2325
- (await tryCreateImageBitmap(blob)) ??
2326
- (await loadImage(dataURL));
2327
- }
2328
- imageManager.addTask(() => {
2329
- const analysis = analyzeImage(image);
2330
- resolve({
2331
- src: url,
2332
- dataURL: analysis.isLarge ? "" : dataURL,
2333
- width: image.width,
2334
- height: image.height,
2335
- ...analysis
2336
- });
2337
- });
2338
- } catch (error) {
2339
- reject(error);
2340
- }
2341
- });
2182
+ function getBgPole(theme) {
2183
+ const isDarkScheme = theme.mode === 1;
2184
+ const prop = isDarkScheme
2185
+ ? "darkSchemeBackgroundColor"
2186
+ : "lightSchemeBackgroundColor";
2187
+ return theme[prop];
2342
2188
  }
2343
- async function getDataURL(url) {
2344
- const parsedURL = new URL(url);
2345
- if (parsedURL.origin === location.origin) {
2346
- return await loadAsDataURL(url);
2347
- }
2348
- return await bgFetch({url, responseType: "data-url"});
2189
+ function getFgPole(theme) {
2190
+ const isDarkScheme = theme.mode === 1;
2191
+ const prop = isDarkScheme
2192
+ ? "darkSchemeTextColor"
2193
+ : "lightSchemeTextColor";
2194
+ return theme[prop];
2349
2195
  }
2350
- async function tryCreateImageBitmap(blob) {
2351
- try {
2352
- return await createImageBitmap(blob);
2353
- } catch (err) {
2354
- logWarn(
2355
- `Unable to create image bitmap for type ${blob.type}: ${String(err)}`
2356
- );
2357
- return null;
2358
- }
2196
+ const colorModificationCache = new Map();
2197
+ function clearColorModificationCache() {
2198
+ colorModificationCache.clear();
2359
2199
  }
2360
- const INCOMPLETE_DOC_LOADING_IMAGE_LIMIT = 256;
2361
- let loadingImagesCount = 0;
2362
- async function loadImage(url) {
2363
- return new Promise((resolve, reject) => {
2364
- const image = new Image();
2365
- image.onload = () => resolve(image);
2366
- image.onerror = () => reject(`Unable to load image ${url}`);
2367
- if (
2368
- ++loadingImagesCount <= INCOMPLETE_DOC_LOADING_IMAGE_LIMIT ||
2369
- isReadyStateComplete()
2370
- ) {
2371
- image.src = url;
2372
- } else {
2373
- addReadyStateCompleteListener(() => (image.src = url));
2374
- }
2200
+ const rgbCacheKeys = ["r", "g", "b", "a"];
2201
+ const themeCacheKeys = [
2202
+ "mode",
2203
+ "brightness",
2204
+ "contrast",
2205
+ "grayscale",
2206
+ "sepia",
2207
+ "darkSchemeBackgroundColor",
2208
+ "darkSchemeTextColor",
2209
+ "lightSchemeBackgroundColor",
2210
+ "lightSchemeTextColor"
2211
+ ];
2212
+ function getCacheId(rgb, theme) {
2213
+ let resultId = "";
2214
+ rgbCacheKeys.forEach((key) => {
2215
+ resultId += `${rgb[key]};`;
2375
2216
  });
2217
+ themeCacheKeys.forEach((key) => {
2218
+ resultId += `${theme[key]};`;
2219
+ });
2220
+ return resultId;
2376
2221
  }
2377
- const MAX_ANALYSIS_PIXELS_COUNT = 32 * 32;
2378
- let canvas;
2379
- let context;
2380
- function createCanvas() {
2381
- const maxWidth = MAX_ANALYSIS_PIXELS_COUNT;
2382
- const maxHeight = MAX_ANALYSIS_PIXELS_COUNT;
2383
- canvas = document.createElement("canvas");
2384
- canvas.width = maxWidth;
2385
- canvas.height = maxHeight;
2386
- context = canvas.getContext("2d", {willReadFrequently: true});
2387
- context.imageSmoothingEnabled = false;
2388
- }
2389
- function removeCanvas() {
2390
- canvas = null;
2391
- context = null;
2392
- }
2393
- const LARGE_IMAGE_PIXELS_COUNT = 512 * 512;
2394
- function analyzeImage(image) {
2395
- if (!canvas) {
2396
- createCanvas();
2397
- }
2398
- let sw;
2399
- let sh;
2400
- if (image instanceof HTMLImageElement) {
2401
- sw = image.naturalWidth;
2402
- sh = image.naturalHeight;
2222
+ function modifyColorWithCache(
2223
+ rgb,
2224
+ theme,
2225
+ modifyHSL,
2226
+ poleColor,
2227
+ anotherPoleColor
2228
+ ) {
2229
+ let fnCache;
2230
+ if (colorModificationCache.has(modifyHSL)) {
2231
+ fnCache = colorModificationCache.get(modifyHSL);
2403
2232
  } else {
2404
- sw = image.width;
2405
- sh = image.height;
2233
+ fnCache = new Map();
2234
+ colorModificationCache.set(modifyHSL, fnCache);
2406
2235
  }
2407
- if (sw === 0 || sh === 0) {
2408
- logWarn("Image is empty");
2409
- return {
2410
- isDark: false,
2411
- isLight: false,
2412
- isTransparent: false,
2413
- isLarge: false
2414
- };
2236
+ const id = getCacheId(rgb, theme);
2237
+ if (fnCache.has(id)) {
2238
+ return fnCache.get(id);
2415
2239
  }
2416
- const isLarge = sw * sh > LARGE_IMAGE_PIXELS_COUNT;
2417
- const sourcePixelsCount = sw * sh;
2418
- const k = Math.min(
2419
- 1,
2420
- Math.sqrt(MAX_ANALYSIS_PIXELS_COUNT / sourcePixelsCount)
2240
+ const hsl = rgbToHSL(rgb);
2241
+ const pole = poleColor == null ? null : parseToHSLWithCache(poleColor);
2242
+ const anotherPole =
2243
+ anotherPoleColor == null
2244
+ ? null
2245
+ : parseToHSLWithCache(anotherPoleColor);
2246
+ const modified = modifyHSL(hsl, pole, anotherPole);
2247
+ const {r, g, b, a} = hslToRGB(modified);
2248
+ const matrix = createFilterMatrix({...theme, mode: 0});
2249
+ const [rf, gf, bf] = applyColorMatrix([r, g, b], matrix);
2250
+ const color =
2251
+ a === 1
2252
+ ? rgbToHexString({r: rf, g: gf, b: bf})
2253
+ : rgbToString({r: rf, g: gf, b: bf, a});
2254
+ fnCache.set(id, color);
2255
+ return color;
2256
+ }
2257
+ function modifyAndRegisterColor(type, rgb, theme, modifier) {
2258
+ const registered = getRegisteredColor(type, rgb);
2259
+ if (registered) {
2260
+ return registered;
2261
+ }
2262
+ const value = modifier(rgb, theme);
2263
+ return registerColor(type, rgb, value);
2264
+ }
2265
+ function modifyLightSchemeColor(rgb, theme) {
2266
+ const poleBg = getBgPole(theme);
2267
+ const poleFg = getFgPole(theme);
2268
+ return modifyColorWithCache(
2269
+ rgb,
2270
+ theme,
2271
+ modifyLightModeHSL,
2272
+ poleFg,
2273
+ poleBg
2421
2274
  );
2422
- const width = Math.ceil(sw * k);
2423
- const height = Math.ceil(sh * k);
2424
- context.clearRect(0, 0, width, height);
2425
- context.drawImage(image, 0, 0, sw, sh, 0, 0, width, height);
2426
- const imageData = context.getImageData(0, 0, width, height);
2427
- const d = imageData.data;
2428
- const TRANSPARENT_ALPHA_THRESHOLD = 0.05;
2429
- const DARK_LIGHTNESS_THRESHOLD = 0.4;
2430
- const LIGHT_LIGHTNESS_THRESHOLD = 0.7;
2431
- let transparentPixelsCount = 0;
2432
- let darkPixelsCount = 0;
2433
- let lightPixelsCount = 0;
2434
- let i, x, y;
2435
- let r, g, b, a;
2436
- let l;
2437
- for (y = 0; y < height; y++) {
2438
- for (x = 0; x < width; x++) {
2439
- i = 4 * (y * width + x);
2440
- r = d[i + 0];
2441
- g = d[i + 1];
2442
- b = d[i + 2];
2443
- a = d[i + 3];
2444
- if (a / 255 < TRANSPARENT_ALPHA_THRESHOLD) {
2445
- transparentPixelsCount++;
2446
- } else {
2447
- l = getSRGBLightness(r, g, b);
2448
- if (l < DARK_LIGHTNESS_THRESHOLD) {
2449
- darkPixelsCount++;
2450
- }
2451
- if (l > LIGHT_LIGHTNESS_THRESHOLD) {
2452
- lightPixelsCount++;
2453
- }
2454
- }
2275
+ }
2276
+ function modifyLightModeHSL({h, s, l, a}, poleFg, poleBg) {
2277
+ const isDark = l < 0.5;
2278
+ let isNeutral;
2279
+ if (isDark) {
2280
+ isNeutral = l < 0.2 || s < 0.12;
2281
+ } else {
2282
+ const isBlue = h > 200 && h < 280;
2283
+ isNeutral = s < 0.24 || (l > 0.8 && isBlue);
2284
+ }
2285
+ let hx = h;
2286
+ let sx = s;
2287
+ if (isNeutral) {
2288
+ if (isDark) {
2289
+ hx = poleFg.h;
2290
+ sx = poleFg.s;
2291
+ } else {
2292
+ hx = poleBg.h;
2293
+ sx = poleBg.s;
2455
2294
  }
2456
2295
  }
2457
- const totalPixelsCount = width * height;
2458
- const opaquePixelsCount = totalPixelsCount - transparentPixelsCount;
2459
- const DARK_IMAGE_THRESHOLD = 0.7;
2460
- const LIGHT_IMAGE_THRESHOLD = 0.7;
2461
- const TRANSPARENT_IMAGE_THRESHOLD = 0.1;
2462
- return {
2463
- isDark: darkPixelsCount / opaquePixelsCount >= DARK_IMAGE_THRESHOLD,
2464
- isLight:
2465
- lightPixelsCount / opaquePixelsCount >= LIGHT_IMAGE_THRESHOLD,
2466
- isTransparent:
2467
- transparentPixelsCount / totalPixelsCount >=
2468
- TRANSPARENT_IMAGE_THRESHOLD,
2469
- isLarge
2470
- };
2296
+ const lx = scale(l, 0, 1, poleFg.l, poleBg.l);
2297
+ return {h: hx, s: sx, l: lx, a};
2471
2298
  }
2472
- let isBlobURLSupported = null;
2473
- let canUseProxy = false;
2474
- let blobURLCheckRequested = false;
2475
- const blobURLCheckAwaiters = [];
2476
- document.addEventListener(
2477
- "__darkreader__inlineScriptsAllowed",
2478
- () => (canUseProxy = true),
2479
- {once: true}
2480
- );
2481
- async function requestBlobURLCheck() {
2482
- if (!canUseProxy) {
2483
- return;
2299
+ const MAX_BG_LIGHTNESS = 0.4;
2300
+ function modifyBgHSL({h, s, l, a}, pole) {
2301
+ const isDark = l < 0.5;
2302
+ const isBlue = h > 200 && h < 280;
2303
+ const isNeutral = s < 0.12 || (l > 0.8 && isBlue);
2304
+ if (isDark) {
2305
+ const lx = scale(l, 0, 0.5, 0, MAX_BG_LIGHTNESS);
2306
+ if (isNeutral) {
2307
+ const hx = pole.h;
2308
+ const sx = pole.s;
2309
+ return {h: hx, s: sx, l: lx, a};
2310
+ }
2311
+ return {h, s, l: lx, a};
2312
+ }
2313
+ let lx = scale(l, 0.5, 1, MAX_BG_LIGHTNESS, pole.l);
2314
+ if (isNeutral) {
2315
+ const hx = pole.h;
2316
+ const sx = pole.s;
2317
+ return {h: hx, s: sx, l: lx, a};
2318
+ }
2319
+ let hx = h;
2320
+ const isYellow = h > 60 && h < 180;
2321
+ if (isYellow) {
2322
+ const isCloserToGreen = h > 120;
2323
+ if (isCloserToGreen) {
2324
+ hx = scale(h, 120, 180, 135, 180);
2325
+ } else {
2326
+ hx = scale(h, 60, 120, 60, 105);
2327
+ }
2484
2328
  }
2485
- if (blobURLCheckRequested) {
2486
- return await new Promise((resolve) =>
2487
- blobURLCheckAwaiters.push(resolve)
2488
- );
2329
+ if (hx > 40 && hx < 80) {
2330
+ lx *= 0.75;
2489
2331
  }
2490
- blobURLCheckRequested = true;
2491
- await new Promise((resolve) => {
2492
- document.addEventListener(
2493
- "__darkreader__blobURLCheckResponse",
2494
- (e) => {
2495
- isBlobURLSupported = e.detail.blobURLAllowed;
2496
- resolve();
2497
- blobURLCheckAwaiters.forEach((r) => r());
2498
- blobURLCheckAwaiters.splice(0);
2499
- },
2500
- {once: true}
2501
- );
2502
- document.dispatchEvent(
2503
- new CustomEvent("__darkreader__blobURLCheckRequest")
2504
- );
2505
- });
2332
+ return {h: hx, s, l: lx, a};
2506
2333
  }
2507
- function isBlobURLCheckResultReady() {
2508
- return isBlobURLSupported != null || !canUseProxy;
2334
+ function _modifyBackgroundColor(rgb, theme) {
2335
+ if (theme.mode === 0) {
2336
+ return modifyLightSchemeColor(rgb, theme);
2337
+ }
2338
+ const pole = getBgPole(theme);
2339
+ return modifyColorWithCache(rgb, theme, modifyBgHSL, pole);
2509
2340
  }
2510
- function onCSPError(err) {
2511
- if (err.blockedURI === "blob") {
2512
- isBlobURLSupported = false;
2513
- document.removeEventListener("securitypolicyviolation", onCSPError);
2341
+ function modifyBackgroundColor(
2342
+ rgb,
2343
+ theme,
2344
+ shouldRegisterColorVariable = true
2345
+ ) {
2346
+ if (!shouldRegisterColorVariable) {
2347
+ return _modifyBackgroundColor(rgb, theme);
2514
2348
  }
2349
+ return modifyAndRegisterColor(
2350
+ "background",
2351
+ rgb,
2352
+ theme,
2353
+ _modifyBackgroundColor
2354
+ );
2515
2355
  }
2516
- document.addEventListener("securitypolicyviolation", onCSPError);
2517
- const objectURLs = new Set();
2518
- function getFilteredImageURL({dataURL, width, height}, theme) {
2519
- if (dataURL.startsWith("data:image/svg+xml")) {
2520
- dataURL = escapeXML(dataURL);
2356
+ const MIN_FG_LIGHTNESS = 0.55;
2357
+ function modifyBlueFgHue(hue) {
2358
+ return scale(hue, 205, 245, 205, 220);
2359
+ }
2360
+ function modifyFgHSL({h, s, l, a}, pole) {
2361
+ const isLight = l > 0.5;
2362
+ const isNeutral = l < 0.2 || s < 0.24;
2363
+ const isBlue = !isNeutral && h > 205 && h < 245;
2364
+ if (isLight) {
2365
+ const lx = scale(l, 0.5, 1, MIN_FG_LIGHTNESS, pole.l);
2366
+ if (isNeutral) {
2367
+ const hx = pole.h;
2368
+ const sx = pole.s;
2369
+ return {h: hx, s: sx, l: lx, a};
2370
+ }
2371
+ let hx = h;
2372
+ if (isBlue) {
2373
+ hx = modifyBlueFgHue(h);
2374
+ }
2375
+ return {h: hx, s, l: lx, a};
2521
2376
  }
2522
- const matrix = getSVGFilterMatrixValue(theme);
2523
- const svg = [
2524
- `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}">`,
2525
- "<defs>",
2526
- '<filter id="darkreader-image-filter">',
2527
- `<feColorMatrix type="matrix" values="${matrix}" />`,
2528
- "</filter>",
2529
- "</defs>",
2530
- `<image width="${width}" height="${height}" filter="url(#darkreader-image-filter)" xlink:href="${dataURL}" />`,
2531
- "</svg>"
2532
- ].join("");
2533
- if (!isBlobURLSupported) {
2534
- return `data:image/svg+xml;base64,${btoa(svg)}`;
2377
+ if (isNeutral) {
2378
+ const hx = pole.h;
2379
+ const sx = pole.s;
2380
+ const lx = scale(l, 0, 0.5, pole.l, MIN_FG_LIGHTNESS);
2381
+ return {h: hx, s: sx, l: lx, a};
2535
2382
  }
2536
- const bytes = new Uint8Array(svg.length);
2537
- for (let i = 0; i < svg.length; i++) {
2538
- bytes[i] = svg.charCodeAt(i);
2383
+ let hx = h;
2384
+ let lx;
2385
+ if (isBlue) {
2386
+ hx = modifyBlueFgHue(h);
2387
+ lx = scale(l, 0, 0.5, pole.l, Math.min(1, MIN_FG_LIGHTNESS + 0.05));
2388
+ } else {
2389
+ lx = scale(l, 0, 0.5, pole.l, MIN_FG_LIGHTNESS);
2539
2390
  }
2540
- const blob = new Blob([bytes], {type: "image/svg+xml"});
2541
- const objectURL = URL.createObjectURL(blob);
2542
- objectURLs.add(objectURL);
2543
- return objectURL;
2391
+ return {h: hx, s, l: lx, a};
2544
2392
  }
2545
- const xmlEscapeChars = {
2546
- "<": "&lt;",
2547
- ">": "&gt;",
2548
- "&": "&amp;",
2549
- "'": "&apos;",
2550
- '"': "&quot;"
2551
- };
2552
- function escapeXML(str) {
2553
- return str.replace(/[<>&'"]/g, (c) => xmlEscapeChars[c] ?? c);
2393
+ function _modifyForegroundColor(rgb, theme) {
2394
+ if (theme.mode === 0) {
2395
+ return modifyLightSchemeColor(rgb, theme);
2396
+ }
2397
+ const pole = getFgPole(theme);
2398
+ return modifyColorWithCache(rgb, theme, modifyFgHSL, pole);
2554
2399
  }
2555
- const dataURLBlobURLs = new Map();
2556
- function tryConvertDataURLToBlobSync(dataURL) {
2557
- const colonIndex = dataURL.indexOf(":");
2558
- const semicolonIndex = dataURL.indexOf(";", colonIndex + 1);
2559
- const commaIndex = dataURL.indexOf(",", semicolonIndex + 1);
2560
- const encoding = dataURL
2561
- .substring(semicolonIndex + 1, commaIndex)
2562
- .toLocaleLowerCase();
2563
- const mediaType = dataURL.substring(colonIndex + 1, semicolonIndex);
2564
- if (encoding !== "base64" || !mediaType) {
2565
- return null;
2400
+ function modifyForegroundColor(
2401
+ rgb,
2402
+ theme,
2403
+ shouldRegisterColorVariable = true
2404
+ ) {
2405
+ if (!shouldRegisterColorVariable) {
2406
+ return _modifyForegroundColor(rgb, theme);
2566
2407
  }
2567
- const characters = atob(dataURL.substring(commaIndex + 1));
2568
- const bytes = new Uint8Array(characters.length);
2569
- for (let i = 0; i < characters.length; i++) {
2570
- bytes[i] = characters.charCodeAt(i);
2408
+ return modifyAndRegisterColor(
2409
+ "text",
2410
+ rgb,
2411
+ theme,
2412
+ _modifyForegroundColor
2413
+ );
2414
+ }
2415
+ function modifyBorderHSL({h, s, l, a}, poleFg, poleBg) {
2416
+ const isDark = l < 0.5;
2417
+ const isNeutral = l < 0.2 || s < 0.24;
2418
+ let hx = h;
2419
+ let sx = s;
2420
+ if (isNeutral) {
2421
+ if (isDark) {
2422
+ hx = poleFg.h;
2423
+ sx = poleFg.s;
2424
+ } else {
2425
+ hx = poleBg.h;
2426
+ sx = poleBg.s;
2427
+ }
2571
2428
  }
2572
- return new Blob([bytes], {type: mediaType});
2429
+ const lx = scale(l, 0, 1, 0.5, 0.2);
2430
+ return {h: hx, s: sx, l: lx, a};
2573
2431
  }
2574
- async function tryConvertDataURLToBlobURL(dataURL) {
2575
- if (!isBlobURLSupported) {
2576
- return null;
2432
+ function _modifyBorderColor(rgb, theme) {
2433
+ if (theme.mode === 0) {
2434
+ return modifyLightSchemeColor(rgb, theme);
2577
2435
  }
2578
- const hash = getHashCode(dataURL);
2579
- let blobURL = dataURLBlobURLs.get(hash);
2580
- if (blobURL) {
2581
- return blobURL;
2436
+ const poleFg = getFgPole(theme);
2437
+ const poleBg = getBgPole(theme);
2438
+ return modifyColorWithCache(
2439
+ rgb,
2440
+ theme,
2441
+ modifyBorderHSL,
2442
+ poleFg,
2443
+ poleBg
2444
+ );
2445
+ }
2446
+ function modifyBorderColor(rgb, theme, shouldRegisterColorVariable = true) {
2447
+ if (!shouldRegisterColorVariable) {
2448
+ return _modifyBorderColor(rgb, theme);
2449
+ }
2450
+ return modifyAndRegisterColor("border", rgb, theme, _modifyBorderColor);
2451
+ }
2452
+ function modifyShadowColor(rgb, theme) {
2453
+ return modifyBackgroundColor(rgb, theme);
2454
+ }
2455
+ function modifyGradientColor(rgb, theme) {
2456
+ return modifyBackgroundColor(rgb, theme);
2457
+ }
2458
+
2459
+ const gradientLength = "gradient".length;
2460
+ const conicGradient = "conic-";
2461
+ const conicGradientLength = conicGradient.length;
2462
+ const radialGradient = "radial-";
2463
+ const linearGradient = "linear-";
2464
+ function parseGradient(value) {
2465
+ const result = [];
2466
+ let index = 0;
2467
+ let startIndex = conicGradient.length;
2468
+ while ((index = value.indexOf("gradient", startIndex)) !== -1) {
2469
+ let typeGradient;
2470
+ [linearGradient, radialGradient, conicGradient].find(
2471
+ (possibleType) => {
2472
+ if (index - possibleType.length >= 0) {
2473
+ const possibleGradient = value.substring(
2474
+ index - possibleType.length,
2475
+ index
2476
+ );
2477
+ if (possibleGradient === possibleType) {
2478
+ if (
2479
+ value.slice(
2480
+ index - possibleType.length - 10,
2481
+ index - possibleType.length - 1
2482
+ ) === "repeating"
2483
+ ) {
2484
+ typeGradient = `repeating-${possibleType}gradient`;
2485
+ return true;
2486
+ }
2487
+ if (
2488
+ value.slice(
2489
+ index - possibleType.length - 8,
2490
+ index - possibleType.length - 1
2491
+ ) === "-webkit"
2492
+ ) {
2493
+ typeGradient = `-webkit-${possibleType}gradient`;
2494
+ return true;
2495
+ }
2496
+ typeGradient = `${possibleType}gradient`;
2497
+ return true;
2498
+ }
2499
+ }
2500
+ }
2501
+ );
2502
+ if (!typeGradient) {
2503
+ break;
2504
+ }
2505
+ const {start, end} = getParenthesesRange(
2506
+ value,
2507
+ index + gradientLength
2508
+ );
2509
+ const match = value.substring(start + 1, end - 1);
2510
+ startIndex = end + 1 + conicGradientLength;
2511
+ result.push({
2512
+ typeGradient,
2513
+ match,
2514
+ offset: typeGradient.length + 2,
2515
+ index: index - typeGradient.length + gradientLength,
2516
+ hasComma: true
2517
+ });
2582
2518
  }
2583
- let blob = tryConvertDataURLToBlobSync(dataURL);
2584
- if (!blob) {
2585
- const response = await fetch(dataURL);
2586
- blob = await response.blob();
2519
+ if (result.length) {
2520
+ result[result.length - 1].hasComma = false;
2587
2521
  }
2588
- blobURL = URL.createObjectURL(blob);
2589
- dataURLBlobURLs.set(hash, blobURL);
2590
- return blobURL;
2591
- }
2592
- function cleanImageProcessingCache() {
2593
- imageManager && imageManager.stop();
2594
- removeCanvas();
2595
- objectURLs.forEach((u) => URL.revokeObjectURL(u));
2596
- objectURLs.clear();
2597
- dataURLBlobURLs.forEach((u) => URL.revokeObjectURL(u));
2598
- dataURLBlobURLs.clear();
2522
+ return result;
2599
2523
  }
2600
2524
 
2601
- let variablesSheet;
2602
- const registeredColors = new Map();
2603
- function registerVariablesSheet(sheet) {
2604
- variablesSheet = sheet;
2605
- const types = ["background", "text", "border"];
2606
- registeredColors.forEach((registered) => {
2607
- types.forEach((type) => {
2608
- if (registered[type]) {
2609
- const {variable, value} = registered[type];
2610
- variablesSheet?.cssRules[0].style.setProperty(
2611
- variable,
2612
- value
2525
+ const STORAGE_KEY_IMAGE_DETAILS_LIST = "__darkreader__imageDetails_v2_list";
2526
+ const STORAGE_KEY_IMAGE_DETAILS_PREFIX = "__darkreader__imageDetails_v2_";
2527
+ const STORAGE_KEY_CSS_FETCH_PREFIX = "__darkreader__cssFetch_";
2528
+ let imageCacheTimeout = 0;
2529
+ const imageDetailsCacheQueue = new Map();
2530
+ const cachedImageUrls = [];
2531
+ function writeImageDetailsQueue() {
2532
+ imageDetailsCacheQueue.forEach((details, url) => {
2533
+ if (url && url.startsWith("https://")) {
2534
+ try {
2535
+ const json = JSON.stringify(details);
2536
+ sessionStorage.setItem(
2537
+ `${STORAGE_KEY_IMAGE_DETAILS_PREFIX}${url}`,
2538
+ json
2613
2539
  );
2614
- }
2615
- });
2540
+ cachedImageUrls.push(url);
2541
+ } catch (err) {}
2542
+ }
2616
2543
  });
2544
+ imageDetailsCacheQueue.clear();
2545
+ sessionStorage.setItem(
2546
+ STORAGE_KEY_IMAGE_DETAILS_LIST,
2547
+ JSON.stringify(cachedImageUrls)
2548
+ );
2617
2549
  }
2618
- function releaseVariablesSheet() {
2619
- variablesSheet = null;
2620
- clearColorPalette();
2550
+ function writeImageDetailsCache(url, imageDetails) {
2551
+ if (!url || !url.startsWith("https://")) {
2552
+ return;
2553
+ }
2554
+ imageDetailsCacheQueue.set(url, imageDetails);
2555
+ clearTimeout(imageCacheTimeout);
2556
+ imageCacheTimeout = setTimeout(writeImageDetailsQueue, 1000);
2621
2557
  }
2622
- function getRegisteredVariableValue(type, registered) {
2623
- return `var(${registered[type].variable}, ${registered[type].value})`;
2558
+ function readImageDetailsCache(targetMap) {
2559
+ try {
2560
+ const jsonList = sessionStorage.getItem(
2561
+ STORAGE_KEY_IMAGE_DETAILS_LIST
2562
+ );
2563
+ if (!jsonList) {
2564
+ return;
2565
+ }
2566
+ const list = JSON.parse(jsonList);
2567
+ list.forEach((url) => {
2568
+ const json = sessionStorage.getItem(
2569
+ `${STORAGE_KEY_IMAGE_DETAILS_PREFIX}${url}`
2570
+ );
2571
+ if (json) {
2572
+ const details = JSON.parse(json);
2573
+ targetMap.set(url, details);
2574
+ }
2575
+ });
2576
+ } catch (err) {}
2624
2577
  }
2625
- function getRegisteredColor(type, parsed) {
2626
- const hex = rgbToHexString(parsed);
2627
- const registered = registeredColors.get(hex);
2628
- if (registered?.[type]) {
2629
- return getRegisteredVariableValue(type, registered);
2630
- }
2578
+ function writeCSSFetchCache(url, cssText) {
2579
+ const key = `${STORAGE_KEY_CSS_FETCH_PREFIX}${url}`;
2580
+ try {
2581
+ sessionStorage.setItem(key, cssText);
2582
+ } catch (err) {}
2583
+ }
2584
+ function readCSSFetchCache(url) {
2585
+ const key = `${STORAGE_KEY_CSS_FETCH_PREFIX}${url}`;
2586
+ try {
2587
+ return sessionStorage.getItem(key) ?? null;
2588
+ } catch (err) {}
2631
2589
  return null;
2632
2590
  }
2633
- function registerColor(type, parsed, value) {
2634
- const hex = rgbToHexString(parsed);
2635
- let registered;
2636
- if (registeredColors.has(hex)) {
2637
- registered = registeredColors.get(hex);
2638
- } else {
2639
- const parsed = parseColorWithCache(hex);
2640
- registered = {parsed};
2641
- registeredColors.set(hex, registered);
2591
+
2592
+ function toSVGMatrix(matrix) {
2593
+ return matrix
2594
+ .slice(0, 4)
2595
+ .map((m) => m.map((m) => m.toFixed(3)).join(" "))
2596
+ .join(" ");
2597
+ }
2598
+ function getSVGFilterMatrixValue(config) {
2599
+ return toSVGMatrix(createFilterMatrix(config));
2600
+ }
2601
+
2602
+ const MAX_FRAME_DURATION = 1000 / 60;
2603
+ class AsyncQueue {
2604
+ constructor() {
2605
+ this.queue = [];
2606
+ this.timerId = null;
2642
2607
  }
2643
- const variable = `--darkreader-${type}-${hex.replace("#", "")}`;
2644
- registered[type] = {variable, value};
2645
- if (variablesSheet?.cssRules[0]?.style) {
2646
- variablesSheet?.cssRules[0].style.setProperty(variable, value);
2608
+ addTask(task) {
2609
+ this.queue.push(task);
2610
+ this.scheduleFrame();
2647
2611
  }
2648
- return getRegisteredVariableValue(type, registered);
2649
- }
2650
- function getColorPalette() {
2651
- const background = [];
2652
- const border = [];
2653
- const text = [];
2654
- registeredColors.forEach((registered) => {
2655
- if (registered.background) {
2656
- background.push(registered.parsed);
2657
- }
2658
- if (registered.border) {
2659
- border.push(registered.parsed);
2612
+ stop() {
2613
+ if (this.timerId !== null) {
2614
+ cancelAnimationFrame(this.timerId);
2615
+ this.timerId = null;
2660
2616
  }
2661
- if (registered.text) {
2662
- text.push(registered.parsed);
2617
+ this.queue = [];
2618
+ }
2619
+ scheduleFrame() {
2620
+ if (this.timerId) {
2621
+ return;
2663
2622
  }
2664
- });
2665
- return {background, border, text};
2666
- }
2667
- function clearColorPalette() {
2668
- registeredColors.clear();
2623
+ this.timerId = requestAnimationFrame(() => {
2624
+ this.timerId = null;
2625
+ const start = Date.now();
2626
+ let cb;
2627
+ while ((cb = this.queue.shift())) {
2628
+ cb();
2629
+ if (Date.now() - start >= MAX_FRAME_DURATION) {
2630
+ this.scheduleFrame();
2631
+ break;
2632
+ }
2633
+ }
2634
+ });
2635
+ }
2669
2636
  }
2670
2637
 
2671
- function getBgPole(theme) {
2672
- const isDarkScheme = theme.mode === 1;
2673
- const prop = isDarkScheme
2674
- ? "darkSchemeBackgroundColor"
2675
- : "lightSchemeBackgroundColor";
2676
- return theme[prop];
2677
- }
2678
- function getFgPole(theme) {
2679
- const isDarkScheme = theme.mode === 1;
2680
- const prop = isDarkScheme
2681
- ? "darkSchemeTextColor"
2682
- : "lightSchemeTextColor";
2683
- return theme[prop];
2684
- }
2685
- const colorModificationCache = new Map();
2686
- function clearColorModificationCache() {
2687
- colorModificationCache.clear();
2688
- }
2689
- const rgbCacheKeys = ["r", "g", "b", "a"];
2690
- const themeCacheKeys$1 = [
2691
- "mode",
2692
- "brightness",
2693
- "contrast",
2694
- "grayscale",
2695
- "sepia",
2696
- "darkSchemeBackgroundColor",
2697
- "darkSchemeTextColor",
2698
- "lightSchemeBackgroundColor",
2699
- "lightSchemeTextColor"
2700
- ];
2701
- function getCacheId(rgb, theme) {
2702
- let resultId = "";
2703
- rgbCacheKeys.forEach((key) => {
2704
- resultId += `${rgb[key]};`;
2638
+ const resolvers$1 = new Map();
2639
+ const rejectors = new Map();
2640
+ async function bgFetch(request) {
2641
+ if (window.DarkReader?.Plugins?.fetch) {
2642
+ return window.DarkReader.Plugins.fetch(request);
2643
+ }
2644
+ return new Promise((resolve, reject) => {
2645
+ const id = generateUID();
2646
+ resolvers$1.set(id, resolve);
2647
+ rejectors.set(id, reject);
2648
+ chrome.runtime.sendMessage({
2649
+ type: MessageTypeCStoBG.FETCH,
2650
+ data: request,
2651
+ id
2652
+ });
2705
2653
  });
2706
- themeCacheKeys$1.forEach((key) => {
2707
- resultId += `${theme[key]};`;
2654
+ }
2655
+ chrome.runtime.onMessage.addListener(({type, data, error, id}) => {
2656
+ if (type === MessageTypeBGtoCS.FETCH_RESPONSE) {
2657
+ const resolve = resolvers$1.get(id);
2658
+ const reject = rejectors.get(id);
2659
+ resolvers$1.delete(id);
2660
+ rejectors.delete(id);
2661
+ if (error) {
2662
+ reject &&
2663
+ reject(
2664
+ typeof error === "string" ? new Error(error) : error
2665
+ );
2666
+ } else {
2667
+ resolve && resolve(data);
2668
+ }
2669
+ }
2670
+ });
2671
+
2672
+ const imageManager = new AsyncQueue();
2673
+ async function getImageDetails(url) {
2674
+ return new Promise(async (resolve, reject) => {
2675
+ try {
2676
+ const dataURL = url.startsWith("data:")
2677
+ ? url
2678
+ : await getDataURL(url);
2679
+ const blob =
2680
+ tryConvertDataURLToBlobSync(dataURL) ??
2681
+ (await loadAsBlob(url));
2682
+ let image;
2683
+ if (dataURL.startsWith("data:image/svg+xml")) {
2684
+ image = await loadImage(dataURL);
2685
+ } else {
2686
+ image =
2687
+ (await tryCreateImageBitmap(blob)) ??
2688
+ (await loadImage(dataURL));
2689
+ }
2690
+ imageManager.addTask(() => {
2691
+ const analysis = analyzeImage(image);
2692
+ resolve({
2693
+ src: url,
2694
+ dataURL: analysis.isLarge ? "" : dataURL,
2695
+ width: image.width,
2696
+ height: image.height,
2697
+ ...analysis
2698
+ });
2699
+ });
2700
+ } catch (error) {
2701
+ reject(error);
2702
+ }
2708
2703
  });
2709
- return resultId;
2710
2704
  }
2711
- function modifyColorWithCache(
2712
- rgb,
2713
- theme,
2714
- modifyHSL,
2715
- poleColor,
2716
- anotherPoleColor
2717
- ) {
2718
- let fnCache;
2719
- if (colorModificationCache.has(modifyHSL)) {
2720
- fnCache = colorModificationCache.get(modifyHSL);
2721
- } else {
2722
- fnCache = new Map();
2723
- colorModificationCache.set(modifyHSL, fnCache);
2724
- }
2725
- const id = getCacheId(rgb, theme);
2726
- if (fnCache.has(id)) {
2727
- return fnCache.get(id);
2705
+ async function getDataURL(url) {
2706
+ const parsedURL = new URL(url);
2707
+ if (parsedURL.origin === location.origin) {
2708
+ return await loadAsDataURL(url);
2728
2709
  }
2729
- const hsl = rgbToHSL(rgb);
2730
- const pole = poleColor == null ? null : parseToHSLWithCache(poleColor);
2731
- const anotherPole =
2732
- anotherPoleColor == null
2733
- ? null
2734
- : parseToHSLWithCache(anotherPoleColor);
2735
- const modified = modifyHSL(hsl, pole, anotherPole);
2736
- const {r, g, b, a} = hslToRGB(modified);
2737
- const matrix = createFilterMatrix(theme);
2738
- const [rf, gf, bf] = applyColorMatrix([r, g, b], matrix);
2739
- const color =
2740
- a === 1
2741
- ? rgbToHexString({r: rf, g: gf, b: bf})
2742
- : rgbToString({r: rf, g: gf, b: bf, a});
2743
- fnCache.set(id, color);
2744
- return color;
2710
+ return await bgFetch({url, responseType: "data-url"});
2745
2711
  }
2746
- function modifyAndRegisterColor(type, rgb, theme, modifier) {
2747
- const registered = getRegisteredColor(type, rgb);
2748
- if (registered) {
2749
- return registered;
2712
+ async function tryCreateImageBitmap(blob) {
2713
+ try {
2714
+ return await createImageBitmap(blob);
2715
+ } catch (err) {
2716
+ logWarn(
2717
+ `Unable to create image bitmap for type ${blob.type}: ${String(err)}`
2718
+ );
2719
+ return null;
2750
2720
  }
2751
- const value = modifier(rgb, theme);
2752
- return registerColor(type, rgb, value);
2753
- }
2754
- function modifyLightSchemeColor(rgb, theme) {
2755
- const poleBg = getBgPole(theme);
2756
- const poleFg = getFgPole(theme);
2757
- return modifyColorWithCache(
2758
- rgb,
2759
- theme,
2760
- modifyLightModeHSL,
2761
- poleFg,
2762
- poleBg
2763
- );
2764
2721
  }
2765
- function modifyLightModeHSL({h, s, l, a}, poleFg, poleBg) {
2766
- const isDark = l < 0.5;
2767
- let isNeutral;
2768
- if (isDark) {
2769
- isNeutral = l < 0.2 || s < 0.12;
2770
- } else {
2771
- const isBlue = h > 200 && h < 280;
2772
- isNeutral = s < 0.24 || (l > 0.8 && isBlue);
2773
- }
2774
- let hx = h;
2775
- let sx = l;
2776
- if (isNeutral) {
2777
- if (isDark) {
2778
- hx = poleFg.h;
2779
- sx = poleFg.s;
2722
+ const INCOMPLETE_DOC_LOADING_IMAGE_LIMIT = 256;
2723
+ let loadingImagesCount = 0;
2724
+ async function loadImage(url) {
2725
+ return new Promise((resolve, reject) => {
2726
+ const image = new Image();
2727
+ image.onload = () => resolve(image);
2728
+ image.onerror = () => reject(`Unable to load image ${url}`);
2729
+ if (
2730
+ ++loadingImagesCount <= INCOMPLETE_DOC_LOADING_IMAGE_LIMIT ||
2731
+ isReadyStateComplete()
2732
+ ) {
2733
+ image.src = url;
2780
2734
  } else {
2781
- hx = poleBg.h;
2782
- sx = poleBg.s;
2735
+ addReadyStateCompleteListener(() => (image.src = url));
2783
2736
  }
2784
- }
2785
- const lx = scale(l, 0, 1, poleFg.l, poleBg.l);
2786
- return {h: hx, s: sx, l: lx, a};
2737
+ });
2787
2738
  }
2788
- const MAX_BG_LIGHTNESS = 0.4;
2789
- function modifyBgHSL({h, s, l, a}, pole) {
2790
- const isDark = l < 0.5;
2791
- const isBlue = h > 200 && h < 280;
2792
- const isNeutral = s < 0.12 || (l > 0.8 && isBlue);
2793
- if (isDark) {
2794
- const lx = scale(l, 0, 0.5, 0, MAX_BG_LIGHTNESS);
2795
- if (isNeutral) {
2796
- const hx = pole.h;
2797
- const sx = pole.s;
2798
- return {h: hx, s: sx, l: lx, a};
2799
- }
2800
- return {h, s, l: lx, a};
2739
+ const MAX_ANALYSIS_PIXELS_COUNT = 32 * 32;
2740
+ let canvas;
2741
+ let context;
2742
+ function createCanvas() {
2743
+ const maxWidth = MAX_ANALYSIS_PIXELS_COUNT;
2744
+ const maxHeight = MAX_ANALYSIS_PIXELS_COUNT;
2745
+ canvas = document.createElement("canvas");
2746
+ canvas.width = maxWidth;
2747
+ canvas.height = maxHeight;
2748
+ context = canvas.getContext("2d", {willReadFrequently: true});
2749
+ context.imageSmoothingEnabled = false;
2750
+ }
2751
+ function removeCanvas() {
2752
+ canvas = null;
2753
+ context = null;
2754
+ }
2755
+ const LARGE_IMAGE_PIXELS_COUNT = 512 * 512;
2756
+ function analyzeImage(image) {
2757
+ if (!canvas) {
2758
+ createCanvas();
2801
2759
  }
2802
- let lx = scale(l, 0.5, 1, MAX_BG_LIGHTNESS, pole.l);
2803
- if (isNeutral) {
2804
- const hx = pole.h;
2805
- const sx = pole.s;
2806
- return {h: hx, s: sx, l: lx, a};
2760
+ let sw;
2761
+ let sh;
2762
+ if (image instanceof HTMLImageElement) {
2763
+ sw = image.naturalWidth;
2764
+ sh = image.naturalHeight;
2765
+ } else {
2766
+ sw = image.width;
2767
+ sh = image.height;
2807
2768
  }
2808
- let hx = h;
2809
- const isYellow = h > 60 && h < 180;
2810
- if (isYellow) {
2811
- const isCloserToGreen = h > 120;
2812
- if (isCloserToGreen) {
2813
- hx = scale(h, 120, 180, 135, 180);
2814
- } else {
2815
- hx = scale(h, 60, 120, 60, 105);
2769
+ if (sw === 0 || sh === 0) {
2770
+ logWarn("Image is empty");
2771
+ return {
2772
+ isDark: false,
2773
+ isLight: false,
2774
+ isTransparent: false,
2775
+ isLarge: false
2776
+ };
2777
+ }
2778
+ const isLarge = sw * sh > LARGE_IMAGE_PIXELS_COUNT;
2779
+ const sourcePixelsCount = sw * sh;
2780
+ const k = Math.min(
2781
+ 1,
2782
+ Math.sqrt(MAX_ANALYSIS_PIXELS_COUNT / sourcePixelsCount)
2783
+ );
2784
+ const width = Math.ceil(sw * k);
2785
+ const height = Math.ceil(sh * k);
2786
+ context.clearRect(0, 0, width, height);
2787
+ context.drawImage(image, 0, 0, sw, sh, 0, 0, width, height);
2788
+ const imageData = context.getImageData(0, 0, width, height);
2789
+ const d = imageData.data;
2790
+ const TRANSPARENT_ALPHA_THRESHOLD = 0.05;
2791
+ const DARK_LIGHTNESS_THRESHOLD = 0.4;
2792
+ const LIGHT_LIGHTNESS_THRESHOLD = 0.7;
2793
+ let transparentPixelsCount = 0;
2794
+ let darkPixelsCount = 0;
2795
+ let lightPixelsCount = 0;
2796
+ let i, x, y;
2797
+ let r, g, b, a;
2798
+ let l;
2799
+ for (y = 0; y < height; y++) {
2800
+ for (x = 0; x < width; x++) {
2801
+ i = 4 * (y * width + x);
2802
+ r = d[i + 0];
2803
+ g = d[i + 1];
2804
+ b = d[i + 2];
2805
+ a = d[i + 3];
2806
+ if (a / 255 < TRANSPARENT_ALPHA_THRESHOLD) {
2807
+ transparentPixelsCount++;
2808
+ } else {
2809
+ l = getSRGBLightness(r, g, b);
2810
+ if (l < DARK_LIGHTNESS_THRESHOLD) {
2811
+ darkPixelsCount++;
2812
+ }
2813
+ if (l > LIGHT_LIGHTNESS_THRESHOLD) {
2814
+ lightPixelsCount++;
2815
+ }
2816
+ }
2816
2817
  }
2817
2818
  }
2818
- if (hx > 40 && hx < 80) {
2819
- lx *= 0.75;
2819
+ const totalPixelsCount = width * height;
2820
+ const opaquePixelsCount = totalPixelsCount - transparentPixelsCount;
2821
+ const DARK_IMAGE_THRESHOLD = 0.7;
2822
+ const LIGHT_IMAGE_THRESHOLD = 0.7;
2823
+ const TRANSPARENT_IMAGE_THRESHOLD = 0.1;
2824
+ return {
2825
+ isDark: darkPixelsCount / opaquePixelsCount >= DARK_IMAGE_THRESHOLD,
2826
+ isLight:
2827
+ lightPixelsCount / opaquePixelsCount >= LIGHT_IMAGE_THRESHOLD,
2828
+ isTransparent:
2829
+ transparentPixelsCount / totalPixelsCount >=
2830
+ TRANSPARENT_IMAGE_THRESHOLD,
2831
+ isLarge
2832
+ };
2833
+ }
2834
+ let isBlobURLSupported = null;
2835
+ let canUseProxy = false;
2836
+ let blobURLCheckRequested = false;
2837
+ const blobURLCheckAwaiters = [];
2838
+ document.addEventListener(
2839
+ "__darkreader__inlineScriptsAllowed",
2840
+ () => (canUseProxy = true),
2841
+ {once: true}
2842
+ );
2843
+ async function requestBlobURLCheck() {
2844
+ if (!canUseProxy) {
2845
+ return;
2820
2846
  }
2821
- return {h: hx, s, l: lx, a};
2847
+ if (blobURLCheckRequested) {
2848
+ return await new Promise((resolve) =>
2849
+ blobURLCheckAwaiters.push(resolve)
2850
+ );
2851
+ }
2852
+ blobURLCheckRequested = true;
2853
+ await new Promise((resolve) => {
2854
+ document.addEventListener(
2855
+ "__darkreader__blobURLCheckResponse",
2856
+ (e) => {
2857
+ isBlobURLSupported = e.detail.blobURLAllowed;
2858
+ resolve();
2859
+ blobURLCheckAwaiters.forEach((r) => r());
2860
+ blobURLCheckAwaiters.splice(0);
2861
+ },
2862
+ {once: true}
2863
+ );
2864
+ document.dispatchEvent(
2865
+ new CustomEvent("__darkreader__blobURLCheckRequest")
2866
+ );
2867
+ });
2822
2868
  }
2823
- function _modifyBackgroundColor(rgb, theme) {
2824
- if (theme.mode === 0) {
2825
- return modifyLightSchemeColor(rgb, theme);
2826
- }
2827
- const pole = getBgPole(theme);
2828
- return modifyColorWithCache(
2829
- rgb,
2830
- {...theme, mode: 0},
2831
- modifyBgHSL,
2832
- pole
2833
- );
2869
+ function isBlobURLCheckResultReady() {
2870
+ return isBlobURLSupported != null || !canUseProxy;
2834
2871
  }
2835
- function modifyBackgroundColor(
2836
- rgb,
2837
- theme,
2838
- shouldRegisterColorVariable = true
2839
- ) {
2840
- if (!shouldRegisterColorVariable) {
2841
- return _modifyBackgroundColor(rgb, theme);
2872
+ function onCSPError(err) {
2873
+ if (err.blockedURI === "blob") {
2874
+ isBlobURLSupported = false;
2875
+ document.removeEventListener("securitypolicyviolation", onCSPError);
2842
2876
  }
2843
- return modifyAndRegisterColor(
2844
- "background",
2845
- rgb,
2846
- theme,
2847
- _modifyBackgroundColor
2848
- );
2849
- }
2850
- const MIN_FG_LIGHTNESS = 0.55;
2851
- function modifyBlueFgHue(hue) {
2852
- return scale(hue, 205, 245, 205, 220);
2853
2877
  }
2854
- function modifyFgHSL({h, s, l, a}, pole) {
2855
- const isLight = l > 0.5;
2856
- const isNeutral = l < 0.2 || s < 0.24;
2857
- const isBlue = !isNeutral && h > 205 && h < 245;
2858
- if (isLight) {
2859
- const lx = scale(l, 0.5, 1, MIN_FG_LIGHTNESS, pole.l);
2860
- if (isNeutral) {
2861
- const hx = pole.h;
2862
- const sx = pole.s;
2863
- return {h: hx, s: sx, l: lx, a};
2864
- }
2865
- let hx = h;
2866
- if (isBlue) {
2867
- hx = modifyBlueFgHue(h);
2868
- }
2869
- return {h: hx, s, l: lx, a};
2878
+ document.addEventListener("securitypolicyviolation", onCSPError);
2879
+ const objectURLs = new Set();
2880
+ function getFilteredImageURL({dataURL, width, height}, theme) {
2881
+ if (dataURL.startsWith("data:image/svg+xml")) {
2882
+ dataURL = escapeXML(dataURL);
2870
2883
  }
2871
- if (isNeutral) {
2872
- const hx = pole.h;
2873
- const sx = pole.s;
2874
- const lx = scale(l, 0, 0.5, pole.l, MIN_FG_LIGHTNESS);
2875
- return {h: hx, s: sx, l: lx, a};
2884
+ const matrix = getSVGFilterMatrixValue(theme);
2885
+ const svg = [
2886
+ `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}">`,
2887
+ "<defs>",
2888
+ '<filter id="darkreader-image-filter">',
2889
+ `<feColorMatrix type="matrix" values="${matrix}" />`,
2890
+ "</filter>",
2891
+ "</defs>",
2892
+ `<image width="${width}" height="${height}" filter="url(#darkreader-image-filter)" xlink:href="${dataURL}" />`,
2893
+ "</svg>"
2894
+ ].join("");
2895
+ if (!isBlobURLSupported) {
2896
+ return `data:image/svg+xml;base64,${btoa(svg)}`;
2876
2897
  }
2877
- let hx = h;
2878
- let lx;
2879
- if (isBlue) {
2880
- hx = modifyBlueFgHue(h);
2881
- lx = scale(l, 0, 0.5, pole.l, Math.min(1, MIN_FG_LIGHTNESS + 0.05));
2882
- } else {
2883
- lx = scale(l, 0, 0.5, pole.l, MIN_FG_LIGHTNESS);
2898
+ const bytes = new Uint8Array(svg.length);
2899
+ for (let i = 0; i < svg.length; i++) {
2900
+ bytes[i] = svg.charCodeAt(i);
2884
2901
  }
2885
- return {h: hx, s, l: lx, a};
2902
+ const blob = new Blob([bytes], {type: "image/svg+xml"});
2903
+ const objectURL = URL.createObjectURL(blob);
2904
+ objectURLs.add(objectURL);
2905
+ return objectURL;
2886
2906
  }
2887
- function _modifyForegroundColor(rgb, theme) {
2888
- if (theme.mode === 0) {
2889
- return modifyLightSchemeColor(rgb, theme);
2890
- }
2891
- const pole = getFgPole(theme);
2892
- return modifyColorWithCache(
2893
- rgb,
2894
- {...theme, mode: 0},
2895
- modifyFgHSL,
2896
- pole
2897
- );
2907
+ const xmlEscapeChars = {
2908
+ "<": "&lt;",
2909
+ ">": "&gt;",
2910
+ "&": "&amp;",
2911
+ "'": "&apos;",
2912
+ '"': "&quot;"
2913
+ };
2914
+ function escapeXML(str) {
2915
+ return str.replace(/[<>&'"]/g, (c) => xmlEscapeChars[c] ?? c);
2898
2916
  }
2899
- function modifyForegroundColor(
2900
- rgb,
2901
- theme,
2902
- shouldRegisterColorVariable = true
2903
- ) {
2904
- if (!shouldRegisterColorVariable) {
2905
- return _modifyForegroundColor(rgb, theme);
2917
+ const dataURLBlobURLs = new Map();
2918
+ function tryConvertDataURLToBlobSync(dataURL) {
2919
+ const colonIndex = dataURL.indexOf(":");
2920
+ const semicolonIndex = dataURL.indexOf(";", colonIndex + 1);
2921
+ const commaIndex = dataURL.indexOf(",", semicolonIndex + 1);
2922
+ const encoding = dataURL
2923
+ .substring(semicolonIndex + 1, commaIndex)
2924
+ .toLocaleLowerCase();
2925
+ const mediaType = dataURL.substring(colonIndex + 1, semicolonIndex);
2926
+ if (encoding !== "base64" || !mediaType) {
2927
+ return null;
2906
2928
  }
2907
- return modifyAndRegisterColor(
2908
- "text",
2909
- rgb,
2910
- theme,
2911
- _modifyForegroundColor
2912
- );
2913
- }
2914
- function modifyBorderHSL({h, s, l, a}, poleFg, poleBg) {
2915
- const isDark = l < 0.5;
2916
- const isNeutral = l < 0.2 || s < 0.24;
2917
- let hx = h;
2918
- let sx = s;
2919
- if (isNeutral) {
2920
- if (isDark) {
2921
- hx = poleFg.h;
2922
- sx = poleFg.s;
2923
- } else {
2924
- hx = poleBg.h;
2925
- sx = poleBg.s;
2926
- }
2929
+ const characters = atob(dataURL.substring(commaIndex + 1));
2930
+ const bytes = new Uint8Array(characters.length);
2931
+ for (let i = 0; i < characters.length; i++) {
2932
+ bytes[i] = characters.charCodeAt(i);
2927
2933
  }
2928
- const lx = scale(l, 0, 1, 0.5, 0.2);
2929
- return {h: hx, s: sx, l: lx, a};
2934
+ return new Blob([bytes], {type: mediaType});
2930
2935
  }
2931
- function _modifyBorderColor(rgb, theme) {
2932
- if (theme.mode === 0) {
2933
- return modifyLightSchemeColor(rgb, theme);
2936
+ async function tryConvertDataURLToBlobURL(dataURL) {
2937
+ if (!isBlobURLSupported) {
2938
+ return null;
2934
2939
  }
2935
- const poleFg = getFgPole(theme);
2936
- const poleBg = getBgPole(theme);
2937
- return modifyColorWithCache(
2938
- rgb,
2939
- {...theme, mode: 0},
2940
- modifyBorderHSL,
2941
- poleFg,
2942
- poleBg
2943
- );
2944
- }
2945
- function modifyBorderColor(rgb, theme, shouldRegisterColorVariable = true) {
2946
- if (!shouldRegisterColorVariable) {
2947
- return _modifyBorderColor(rgb, theme);
2940
+ const hash = getHashCode(dataURL);
2941
+ let blobURL = dataURLBlobURLs.get(hash);
2942
+ if (blobURL) {
2943
+ return blobURL;
2948
2944
  }
2949
- return modifyAndRegisterColor("border", rgb, theme, _modifyBorderColor);
2950
- }
2951
- function modifyShadowColor(rgb, theme) {
2952
- return modifyBackgroundColor(rgb, theme);
2945
+ let blob = tryConvertDataURLToBlobSync(dataURL);
2946
+ if (!blob) {
2947
+ const response = await fetch(dataURL);
2948
+ blob = await response.blob();
2949
+ }
2950
+ blobURL = URL.createObjectURL(blob);
2951
+ dataURLBlobURLs.set(hash, blobURL);
2952
+ return blobURL;
2953
2953
  }
2954
- function modifyGradientColor(rgb, theme) {
2955
- return modifyBackgroundColor(rgb, theme);
2954
+ function cleanImageProcessingCache() {
2955
+ imageManager && imageManager.stop();
2956
+ removeCanvas();
2957
+ objectURLs.forEach((u) => URL.revokeObjectURL(u));
2958
+ objectURLs.clear();
2959
+ dataURLBlobURLs.forEach((u) => URL.revokeObjectURL(u));
2960
+ dataURLBlobURLs.clear();
2956
2961
  }
2957
2962
 
2958
2963
  function getPriority(ruleStyle, property) {
@@ -3223,10 +3228,18 @@
3223
3228
  "auto"
3224
3229
  ]);
3225
3230
  function getColorModifier(prop, value, rule) {
3226
- if (unparsableColors.has(value.toLowerCase())) {
3231
+ if (
3232
+ unparsableColors.has(value.toLowerCase()) &&
3233
+ !(prop === "color" && value === "initial")
3234
+ ) {
3227
3235
  return value;
3228
3236
  }
3229
- const rgb = parseColorWithCache(value);
3237
+ let rgb = null;
3238
+ if (prop === "color" && value === "initial") {
3239
+ rgb = {r: 0, g: 0, b: 0, a: 1};
3240
+ } else {
3241
+ rgb = parseColorWithCache(value);
3242
+ }
3230
3243
  if (!rgb) {
3231
3244
  logWarn("Couldn't parse color", value);
3232
3245
  return null;
@@ -3655,7 +3668,7 @@
3655
3668
  return null;
3656
3669
  }
3657
3670
  return (theme) =>
3658
- `${modifyForegroundColor(thumb, theme)} ${modifyBackgroundColor(thumb, theme)}`;
3671
+ `${modifyForegroundColor(thumb, theme)} ${modifyBackgroundColor(track, theme)}`;
3659
3672
  }
3660
3673
  function getColorSchemeModifier() {
3661
3674
  return (theme) => (theme.mode === 0 ? "dark light" : "dark");
@@ -4014,7 +4027,7 @@
4014
4027
  const modified = modify();
4015
4028
  if (unknownVars.size > 0) {
4016
4029
  const isFallbackResolved = modified.match(
4017
- /^var\(.*?, (var\(--darkreader-bg--.*\))|(#[0-9A-Fa-f]+)|([a-z]+)|(rgba?\(.+\))|(hsla?\(.+\))\)$/
4030
+ /^var\(.*?, ((var\(--darkreader-bg--.*\))|(#[0-9A-Fa-f]+)|([a-z]+)|(rgba?\(.+\))|(hsla?\(.+\)))\)$/
4018
4031
  );
4019
4032
  if (isFallbackResolved) {
4020
4033
  return modified;
@@ -4492,17 +4505,6 @@
4492
4505
  return replaced;
4493
4506
  }
4494
4507
 
4495
- const themeCacheKeys = [
4496
- "mode",
4497
- "brightness",
4498
- "contrast",
4499
- "grayscale",
4500
- "sepia",
4501
- "darkSchemeBackgroundColor",
4502
- "darkSchemeTextColor",
4503
- "lightSchemeBackgroundColor",
4504
- "lightSchemeTextColor"
4505
- ];
4506
4508
  function getThemeKey(theme) {
4507
4509
  let resultKey = "";
4508
4510
  themeCacheKeys.forEach((key) => {
@@ -4816,6 +4818,12 @@
4816
4818
  function buildStyleSheet() {
4817
4819
  function createTarget(group, parent) {
4818
4820
  const {rule} = group;
4821
+ if (isStyleRule(rule)) {
4822
+ const {selectorText} = rule;
4823
+ const index = parent.cssRules.length;
4824
+ parent.insertRule(`${selectorText} {}`, index);
4825
+ return parent.cssRules[index];
4826
+ }
4819
4827
  if (isMediaRule(rule)) {
4820
4828
  const {media} = rule;
4821
4829
  const index = parent.cssRules.length;
@@ -5123,6 +5131,27 @@
5123
5131
  return {render, destroy, commands};
5124
5132
  }
5125
5133
 
5134
+ const hostsBreakingOnStylePosition = ["www.diffusioneshop.com", "zhale.me"];
5135
+ const mode = hostsBreakingOnStylePosition.includes(location.hostname)
5136
+ ? "away"
5137
+ : "next";
5138
+ function getStyleInjectionMode() {
5139
+ return mode;
5140
+ }
5141
+ function injectStyleAway(styleElement) {
5142
+ let container = document.body.querySelector(
5143
+ ".darkreader-style-container"
5144
+ );
5145
+ if (!container) {
5146
+ container = document.createElement("div");
5147
+ container.classList.add("darkreader");
5148
+ container.classList.add("darkreader-style-container");
5149
+ container.style.display = "none";
5150
+ document.body.append(container);
5151
+ }
5152
+ container.append(styleElement);
5153
+ }
5154
+
5126
5155
  const overrides = {
5127
5156
  "background-color": {
5128
5157
  customProp: "--darkreader-inline-bgcolor",
@@ -5923,22 +5952,29 @@
5923
5952
  rejectorsForLoadingLinks.clear();
5924
5953
  }
5925
5954
  function manageStyle(element, {update, loadingStart, loadingEnd}) {
5926
- const prevStyles = [];
5927
- let next = element;
5928
- while (
5929
- (next = next.nextElementSibling) &&
5930
- next.matches(".darkreader")
5931
- ) {
5932
- prevStyles.push(next);
5933
- }
5934
- let corsCopy =
5935
- prevStyles.find(
5936
- (el) => el.matches(".darkreader--cors") && !corsStyleSet.has(el)
5937
- ) || null;
5938
- let syncStyle =
5939
- prevStyles.find(
5940
- (el) => el.matches(".darkreader--sync") && !syncStyleSet.has(el)
5941
- ) || null;
5955
+ const inMode = getStyleInjectionMode();
5956
+ let corsCopy = null;
5957
+ let syncStyle = null;
5958
+ if (inMode === "next") {
5959
+ const prevStyles = [];
5960
+ let next = element;
5961
+ while (
5962
+ (next = next.nextElementSibling) &&
5963
+ next.matches(".darkreader")
5964
+ ) {
5965
+ prevStyles.push(next);
5966
+ }
5967
+ corsCopy =
5968
+ prevStyles.find(
5969
+ (el) =>
5970
+ el.matches(".darkreader--cors") && !corsStyleSet.has(el)
5971
+ ) || null;
5972
+ syncStyle =
5973
+ prevStyles.find(
5974
+ (el) =>
5975
+ el.matches(".darkreader--sync") && !syncStyleSet.has(el)
5976
+ ) || null;
5977
+ }
5942
5978
  let corsCopyPositionWatcher = null;
5943
5979
  let syncStylePositionWatcher = null;
5944
5980
  let cancelAsyncOperations = false;
@@ -6023,21 +6059,31 @@
6023
6059
  return cssRules;
6024
6060
  }
6025
6061
  function insertStyle() {
6026
- if (corsCopy) {
6027
- if (element.nextSibling !== corsCopy) {
6062
+ if (inMode === "next") {
6063
+ if (corsCopy) {
6064
+ if (element.nextSibling !== corsCopy) {
6065
+ element.parentNode.insertBefore(
6066
+ corsCopy,
6067
+ element.nextSibling
6068
+ );
6069
+ }
6070
+ if (corsCopy.nextSibling !== syncStyle) {
6071
+ element.parentNode.insertBefore(
6072
+ syncStyle,
6073
+ corsCopy.nextSibling
6074
+ );
6075
+ }
6076
+ } else if (element.nextSibling !== syncStyle) {
6028
6077
  element.parentNode.insertBefore(
6029
- corsCopy,
6078
+ syncStyle,
6030
6079
  element.nextSibling
6031
6080
  );
6032
6081
  }
6033
- if (corsCopy.nextSibling !== syncStyle) {
6034
- element.parentNode.insertBefore(
6035
- syncStyle,
6036
- corsCopy.nextSibling
6037
- );
6082
+ } else if (inMode === "away") {
6083
+ if (corsCopy && !corsCopy.parentNode) {
6084
+ injectStyleAway(corsCopy);
6038
6085
  }
6039
- } else if (element.nextSibling !== syncStyle) {
6040
- element.parentNode.insertBefore(syncStyle, element.nextSibling);
6086
+ injectStyleAway(syncStyle);
6041
6087
  }
6042
6088
  }
6043
6089
  function createSyncStyle() {
@@ -6095,7 +6141,12 @@
6095
6141
  return cssRules;
6096
6142
  }
6097
6143
  }
6098
- cssText = await loadText(element.href);
6144
+ try {
6145
+ cssText = await loadText(element.href);
6146
+ } catch (err) {
6147
+ logWarn(err);
6148
+ cssText = "";
6149
+ }
6099
6150
  cssBasePath = getCSSBaseBath(element.href);
6100
6151
  if (cancelAsyncOperations) {
6101
6152
  return null;
@@ -6127,12 +6178,31 @@
6127
6178
  corsCopy.textContent = fullCSSText;
6128
6179
  }
6129
6180
  } else {
6130
- corsCopy = createCORSCopy(element, fullCSSText);
6181
+ corsCopy = createCORSCopy(
6182
+ fullCSSText,
6183
+ inMode === "next"
6184
+ ? (cc) =>
6185
+ element.parentNode.insertBefore(
6186
+ cc,
6187
+ element.nextSibling
6188
+ )
6189
+ : injectStyleAway
6190
+ );
6191
+ if (corsCopy) {
6192
+ if (inMode === "next") {
6193
+ element.parentNode.insertBefore(
6194
+ corsCopy,
6195
+ element.nextSibling
6196
+ );
6197
+ } else if (inMode === "away") {
6198
+ injectStyleAway(corsCopy);
6199
+ }
6200
+ }
6131
6201
  }
6132
6202
  } catch (err) {
6133
6203
  logWarn(err);
6134
6204
  }
6135
- if (corsCopy) {
6205
+ if (corsCopy && inMode === "next") {
6136
6206
  corsCopyPositionWatcher = watchForNodePosition(
6137
6207
  corsCopy,
6138
6208
  "prev-sibling"
@@ -6199,7 +6269,7 @@
6199
6269
  removeCSSRulesFromSheet(sheet);
6200
6270
  if (syncStylePositionWatcher) {
6201
6271
  syncStylePositionWatcher.run();
6202
- } else {
6272
+ } else if (inMode === "next") {
6203
6273
  syncStylePositionWatcher = watchForNodePosition(
6204
6274
  syncStyle,
6205
6275
  "prev-sibling",
@@ -6422,7 +6492,7 @@
6422
6492
  cssText = cssText.trim();
6423
6493
  return cssText;
6424
6494
  }
6425
- function createCORSCopy(srcElement, cssText) {
6495
+ function createCORSCopy(cssText, inject) {
6426
6496
  if (!cssText) {
6427
6497
  return null;
6428
6498
  }
@@ -6431,7 +6501,7 @@
6431
6501
  cors.classList.add("darkreader--cors");
6432
6502
  cors.media = "screen";
6433
6503
  cors.textContent = cssText;
6434
- srcElement.parentNode.insertBefore(cors, srcElement.nextSibling);
6504
+ inject(cors);
6435
6505
  cors.sheet.disabled = true;
6436
6506
  corsStyleSet.add(cors);
6437
6507
  return cors;
@@ -7234,20 +7304,24 @@
7234
7304
  let isIFrame$1 = null;
7235
7305
  let ignoredImageAnalysisSelectors = [];
7236
7306
  let ignoredInlineSelectors = [];
7237
- const staticStyleMap = new Map();
7307
+ let staticStyleMap = new WeakMap();
7238
7308
  function createOrUpdateStyle(className, root = document.head || document) {
7239
7309
  let element = root.querySelector(`.${className}`);
7310
+ if (!staticStyleMap.has(root)) {
7311
+ staticStyleMap.set(root, new Map());
7312
+ }
7313
+ const classMap = staticStyleMap.get(root);
7240
7314
  if (element) {
7241
- staticStyleMap.set(className, element);
7242
- } else if (staticStyleMap.has(className)) {
7243
- element = staticStyleMap.get(className);
7315
+ classMap.set(className, element);
7316
+ } else if (classMap.has(className)) {
7317
+ element = classMap.get(className);
7244
7318
  } else {
7245
7319
  element = document.createElement("style");
7246
7320
  element.classList.add("darkreader");
7247
7321
  element.classList.add(className);
7248
7322
  element.media = "screen";
7249
7323
  element.textContent = "";
7250
- staticStyleMap.set(className, element);
7324
+ classMap.set(className, element);
7251
7325
  }
7252
7326
  return element;
7253
7327
  }
@@ -7273,6 +7347,18 @@
7273
7347
  forEach(nodePositionWatchers.values(), (watcher) => watcher.stop());
7274
7348
  nodePositionWatchers.clear();
7275
7349
  }
7350
+ function injectStaticStyle(style, prevNode, watchAlias, callback) {
7351
+ const mode = getStyleInjectionMode();
7352
+ if (mode === "next") {
7353
+ document.head.insertBefore(
7354
+ style,
7355
+ prevNode ? prevNode.nextSibling : document.head.firstChild
7356
+ );
7357
+ setupNodePositionWatcher(style, watchAlias, callback);
7358
+ } else if (mode === "away") {
7359
+ injectStyleAway(style);
7360
+ }
7361
+ }
7276
7362
  function createStaticStyleOverrides() {
7277
7363
  const fallbackStyle = createOrUpdateStyle(
7278
7364
  "darkreader--fallback",
@@ -7281,24 +7367,21 @@
7281
7367
  fallbackStyle.textContent = getModifiedFallbackStyle(theme, {
7282
7368
  strict: true
7283
7369
  });
7284
- document.head.insertBefore(fallbackStyle, document.head.firstChild);
7285
- setupNodePositionWatcher(fallbackStyle, "fallback");
7370
+ injectStaticStyle(fallbackStyle, null, "fallback");
7286
7371
  const userAgentStyle = createOrUpdateStyle("darkreader--user-agent");
7287
7372
  userAgentStyle.textContent = getModifiedUserAgentStyle(
7288
7373
  theme,
7289
7374
  isIFrame$1,
7290
7375
  theme.styleSystemControls
7291
7376
  );
7292
- document.head.insertBefore(userAgentStyle, fallbackStyle.nextSibling);
7293
- setupNodePositionWatcher(userAgentStyle, "user-agent");
7377
+ injectStaticStyle(userAgentStyle, fallbackStyle, "user-agent");
7294
7378
  const textStyle = createOrUpdateStyle("darkreader--text");
7295
7379
  if (theme.useFont || theme.textStroke > 0) {
7296
7380
  textStyle.textContent = createTextStyle(theme);
7297
7381
  } else {
7298
7382
  textStyle.textContent = "";
7299
7383
  }
7300
- document.head.insertBefore(textStyle, fallbackStyle.nextSibling);
7301
- setupNodePositionWatcher(textStyle, "text");
7384
+ injectStaticStyle(textStyle, userAgentStyle, "text");
7302
7385
  const invertStyle = createOrUpdateStyle("darkreader--invert");
7303
7386
  if (fixes && Array.isArray(fixes.invert) && fixes.invert.length > 0) {
7304
7387
  invertStyle.textContent = [
@@ -7315,17 +7398,10 @@
7315
7398
  } else {
7316
7399
  invertStyle.textContent = "";
7317
7400
  }
7318
- document.head.insertBefore(invertStyle, textStyle.nextSibling);
7319
- setupNodePositionWatcher(invertStyle, "invert");
7401
+ injectStaticStyle(invertStyle, textStyle, "invert");
7320
7402
  const inlineStyle = createOrUpdateStyle("darkreader--inline");
7321
7403
  inlineStyle.textContent = getInlineOverrideStyle();
7322
- document.head.insertBefore(inlineStyle, invertStyle.nextSibling);
7323
- setupNodePositionWatcher(inlineStyle, "inline");
7324
- const overrideStyle = createOrUpdateStyle("darkreader--override");
7325
- overrideStyle.textContent =
7326
- fixes && fixes.css ? replaceCSSTemplates(fixes.css) : "";
7327
- document.head.appendChild(overrideStyle);
7328
- setupNodePositionWatcher(overrideStyle, "override");
7404
+ injectStaticStyle(inlineStyle, invertStyle, "inline");
7329
7405
  const variableStyle = createOrUpdateStyle("darkreader--variables");
7330
7406
  const selectionColors = theme?.selectionColor
7331
7407
  ? getSelectionColor(theme)
@@ -7346,13 +7422,12 @@
7346
7422
  ` --darkreader-selection-text: ${selectionColors?.foregroundColorSelection ?? "initial"};`,
7347
7423
  `}`
7348
7424
  ].join("\n");
7349
- document.head.insertBefore(variableStyle, inlineStyle.nextSibling);
7350
- setupNodePositionWatcher(variableStyle, "variables", () =>
7425
+ injectStaticStyle(variableStyle, inlineStyle, "variables", () =>
7351
7426
  registerVariablesSheet(variableStyle.sheet)
7352
7427
  );
7353
7428
  registerVariablesSheet(variableStyle.sheet);
7354
7429
  const rootVarsStyle = createOrUpdateStyle("darkreader--root-vars");
7355
- document.head.insertBefore(rootVarsStyle, variableStyle.nextSibling);
7430
+ injectStaticStyle(rootVarsStyle, variableStyle, "root-vars");
7356
7431
  const enableStyleSheetsProxy = !(
7357
7432
  fixes && fixes.disableStyleSheetsProxy
7358
7433
  );
@@ -7368,6 +7443,10 @@
7368
7443
  document.head.insertBefore(proxyScript, rootVarsStyle.nextSibling);
7369
7444
  proxyScript.remove();
7370
7445
  }
7446
+ const overrideStyle = createOrUpdateStyle("darkreader--override");
7447
+ overrideStyle.textContent =
7448
+ fixes && fixes.css ? replaceCSSTemplates(fixes.css) : "";
7449
+ injectStaticStyle(overrideStyle, document.head.lastChild, "override");
7371
7450
  }
7372
7451
  const shadowRootsWithOverrides = new Set();
7373
7452
  function createShadowStaticStyleOverridesInner(root) {
@@ -7443,7 +7522,8 @@
7443
7522
  }
7444
7523
  function cleanFallbackStyle() {
7445
7524
  const fallback =
7446
- staticStyleMap.get("darkreader--fallback") ||
7525
+ staticStyleMap.get(document.head)?.get("darkreader--fallback") ||
7526
+ staticStyleMap.get(document)?.get("darkreader--fallback") ||
7447
7527
  document.querySelector(".darkreader--fallback");
7448
7528
  if (fallback) {
7449
7529
  fallback.textContent = "";
@@ -7992,7 +8072,7 @@
7992
8072
  selectors.forEach((selector) =>
7993
8073
  removeNode(document.head.querySelector(selector))
7994
8074
  );
7995
- staticStyleMap.clear();
8075
+ staticStyleMap = new WeakMap();
7996
8076
  removeProxy();
7997
8077
  }
7998
8078
  shadowRootsWithOverrides.forEach((root) => {