darkreader 4.9.104 → 4.9.108

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 +991 -853
  3. package/darkreader.mjs +949 -810
  4. package/package.json +27 -28
package/darkreader.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Dark Reader v4.9.104
2
+ * Dark Reader v4.9.108
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 = {}));
@@ -364,6 +366,7 @@
364
366
  darkColorScheme: "Default",
365
367
  immediateModify: false
366
368
  };
369
+ if (__PLUS__);
367
370
  const filterModeSites = [
368
371
  "*.officeapps.live.com",
369
372
  "*.sharepoint.com",
@@ -372,9 +375,7 @@
372
375
  ];
373
376
  ({
374
377
  customThemes: filterModeSites.map((url) => {
375
- const engine = isChromium
376
- ? ThemeEngine.svgFilter
377
- : ThemeEngine.cssFilter;
378
+ const engine = ThemeEngine.cssFilter;
378
379
  return {
379
380
  url: [url],
380
381
  theme: {...DEFAULT_THEME, engine},
@@ -985,10 +986,16 @@
985
986
  function parse($color) {
986
987
  const c = $color.trim().toLowerCase();
987
988
  if (c.includes("(from ")) {
989
+ if (c.indexOf("(from") !== c.lastIndexOf("(from")) {
990
+ return null;
991
+ }
988
992
  return domParseColor(c);
989
993
  }
990
994
  if (c.match(rgbMatch)) {
991
995
  if (c.startsWith("rgb(#") || c.startsWith("rgba(#")) {
996
+ if (c.lastIndexOf("rgb") > 0) {
997
+ return null;
998
+ }
992
999
  return domParseColor(c);
993
1000
  }
994
1001
  return parseRGB(c);
@@ -1011,7 +1018,10 @@
1011
1018
  if (
1012
1019
  c.endsWith(")") &&
1013
1020
  supportedColorFuncs.some(
1014
- (fn) => c.startsWith(fn) && c[fn.length] === "("
1021
+ (fn) =>
1022
+ c.startsWith(fn) &&
1023
+ c[fn.length] === "(" &&
1024
+ c.lastIndexOf(fn) === 0
1015
1025
  )
1016
1026
  ) {
1017
1027
  return domParseColor(c);
@@ -1840,6 +1850,9 @@
1840
1850
  forEach(rules, (rule) => {
1841
1851
  if (isStyleRule(rule)) {
1842
1852
  iterate(rule);
1853
+ if (rule.cssRules?.length > 0) {
1854
+ iterateCSSRules(rule.cssRules, iterate);
1855
+ }
1843
1856
  } else if (isImportRule(rule)) {
1844
1857
  try {
1845
1858
  iterateCSSRules(
@@ -2097,862 +2110,911 @@
2097
2110
  return null;
2098
2111
  }
2099
2112
 
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
- }
2141
- }
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
- });
2159
- }
2160
- if (result.length) {
2161
- result[result.length - 1].hasComma = false;
2162
- }
2163
- return result;
2113
+ function getBackgroundPoles(_theme) {
2114
+ return ["", ""];
2164
2115
  }
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
- });
2185
- imageDetailsCacheQueue.clear();
2186
- sessionStorage.setItem(
2187
- STORAGE_KEY_IMAGE_DETAILS_LIST,
2188
- JSON.stringify(cachedImageUrls)
2189
- );
2116
+ function getTextPoles(_theme) {
2117
+ return ["", ""];
2190
2118
  }
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);
2119
+ function modifyBgColorExtended({h, s, l, a}, _pole1, _pole2) {
2120
+ return {h, s, l, a};
2198
2121
  }
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);
2122
+ function modifyFgColorExtended({h, s, l, a}, _pole1, _pole2) {
2123
+ return {h, s, l, a};
2124
+ }
2125
+ function modifyLightSchemeColorExtended({h, s, l, a}, _pole1, _pole2) {
2126
+ return {h, s, l, a};
2127
+ }
2128
+
2129
+ let variablesSheet;
2130
+ const registeredColors = new Map();
2131
+ function registerVariablesSheet(sheet) {
2132
+ variablesSheet = sheet;
2133
+ const types = ["background", "text", "border"];
2134
+ registeredColors.forEach((registered) => {
2135
+ types.forEach((type) => {
2136
+ if (registered[type]) {
2137
+ const {variable, value} = registered[type];
2138
+ variablesSheet?.cssRules[0].style.setProperty(
2139
+ variable,
2140
+ value
2141
+ );
2215
2142
  }
2216
2143
  });
2217
- } catch (err) {}
2218
- }
2219
- function writeCSSFetchCache(url, cssText) {
2220
- const key = `${STORAGE_KEY_CSS_FETCH_PREFIX}${url}`;
2221
- try {
2222
- sessionStorage.setItem(key, cssText);
2223
- } catch (err) {}
2144
+ });
2224
2145
  }
2225
- function readCSSFetchCache(url) {
2226
- const key = `${STORAGE_KEY_CSS_FETCH_PREFIX}${url}`;
2227
- try {
2228
- return sessionStorage.getItem(key) ?? null;
2229
- } catch (err) {}
2230
- return null;
2146
+ function releaseVariablesSheet() {
2147
+ variablesSheet = null;
2148
+ clearColorPalette();
2231
2149
  }
2232
-
2233
- function toSVGMatrix(matrix) {
2234
- return matrix
2235
- .slice(0, 4)
2236
- .map((m) => m.map((m) => m.toFixed(3)).join(" "))
2237
- .join(" ");
2150
+ function getRegisteredVariableValue(type, registered) {
2151
+ return `var(${registered[type].variable}, ${registered[type].value})`;
2238
2152
  }
2239
- function getSVGFilterMatrixValue(config) {
2240
- return toSVGMatrix(createFilterMatrix(config));
2153
+ function getRegisteredColor(type, parsed) {
2154
+ const hex = rgbToHexString(parsed);
2155
+ const registered = registeredColors.get(hex);
2156
+ if (registered?.[type]) {
2157
+ return getRegisteredVariableValue(type, registered);
2158
+ }
2159
+ return null;
2241
2160
  }
2242
-
2243
- const MAX_FRAME_DURATION = 1000 / 60;
2244
- class AsyncQueue {
2245
- constructor() {
2246
- this.queue = [];
2247
- this.timerId = null;
2161
+ function registerColor(type, parsed, value) {
2162
+ const hex = rgbToHexString(parsed);
2163
+ let registered;
2164
+ if (registeredColors.has(hex)) {
2165
+ registered = registeredColors.get(hex);
2166
+ } else {
2167
+ const parsed = parseColorWithCache(hex);
2168
+ registered = {parsed};
2169
+ registeredColors.set(hex, registered);
2248
2170
  }
2249
- addTask(task) {
2250
- this.queue.push(task);
2251
- this.scheduleFrame();
2171
+ const variable = `--darkreader-${type}-${hex.replace("#", "")}`;
2172
+ registered[type] = {variable, value};
2173
+ if (variablesSheet?.cssRules[0]?.style) {
2174
+ variablesSheet?.cssRules[0].style.setProperty(variable, value);
2252
2175
  }
2253
- stop() {
2254
- if (this.timerId !== null) {
2255
- cancelAnimationFrame(this.timerId);
2256
- this.timerId = null;
2176
+ return getRegisteredVariableValue(type, registered);
2177
+ }
2178
+ function getColorPalette() {
2179
+ const background = [];
2180
+ const border = [];
2181
+ const text = [];
2182
+ registeredColors.forEach((registered) => {
2183
+ if (registered.background) {
2184
+ background.push(registered.parsed);
2257
2185
  }
2258
- this.queue = [];
2259
- }
2260
- scheduleFrame() {
2261
- if (this.timerId) {
2262
- return;
2186
+ if (registered.border) {
2187
+ border.push(registered.parsed);
2263
2188
  }
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
- }
2189
+ if (registered.text) {
2190
+ text.push(registered.parsed);
2191
+ }
2192
+ });
2193
+ return {background, border, text};
2194
+ }
2195
+ function clearColorPalette() {
2196
+ registeredColors.clear();
2277
2197
  }
2278
2198
 
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
- });
2199
+ function getBgPole(theme) {
2200
+ const isDarkScheme = theme.mode === 1;
2201
+ const prop = isDarkScheme
2202
+ ? "darkSchemeBackgroundColor"
2203
+ : "lightSchemeBackgroundColor";
2204
+ return theme[prop];
2295
2205
  }
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
-
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
- }
2206
+ function getFgPole(theme) {
2207
+ const isDarkScheme = theme.mode === 1;
2208
+ const prop = isDarkScheme
2209
+ ? "darkSchemeTextColor"
2210
+ : "lightSchemeTextColor";
2211
+ return theme[prop];
2212
+ }
2213
+ const colorModificationCache = new Map();
2214
+ function clearColorModificationCache() {
2215
+ colorModificationCache.clear();
2216
+ }
2217
+ const rgbCacheKeys = ["r", "g", "b", "a"];
2218
+ const themeCacheKeys = [
2219
+ "mode",
2220
+ "brightness",
2221
+ "contrast",
2222
+ "grayscale",
2223
+ "sepia",
2224
+ "darkSchemeBackgroundColor",
2225
+ "darkSchemeTextColor",
2226
+ "lightSchemeBackgroundColor",
2227
+ "lightSchemeTextColor"
2228
+ ];
2229
+ function getCacheId(rgb, theme) {
2230
+ let resultId = "";
2231
+ rgbCacheKeys.forEach((key) => {
2232
+ resultId += `${rgb[key]};`;
2341
2233
  });
2234
+ themeCacheKeys.forEach((key) => {
2235
+ resultId += `${theme[key]};`;
2236
+ });
2237
+ return resultId;
2342
2238
  }
2343
- async function getDataURL(url) {
2344
- const parsedURL = new URL(url);
2345
- if (parsedURL.origin === location.origin) {
2346
- return await loadAsDataURL(url);
2239
+ function modifyColorWithCache(
2240
+ rgb,
2241
+ theme,
2242
+ modifyHSL,
2243
+ poleColor,
2244
+ anotherPoleColor
2245
+ ) {
2246
+ let fnCache;
2247
+ if (colorModificationCache.has(modifyHSL)) {
2248
+ fnCache = colorModificationCache.get(modifyHSL);
2249
+ } else {
2250
+ fnCache = new Map();
2251
+ colorModificationCache.set(modifyHSL, fnCache);
2347
2252
  }
2348
- return await bgFetch({url, responseType: "data-url"});
2349
- }
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;
2253
+ const id = getCacheId(rgb, theme);
2254
+ if (fnCache.has(id)) {
2255
+ return fnCache.get(id);
2358
2256
  }
2257
+ const hsl = rgbToHSL(rgb);
2258
+ const pole = poleColor == null ? null : parseToHSLWithCache(poleColor);
2259
+ const anotherPole =
2260
+ anotherPoleColor == null
2261
+ ? null
2262
+ : parseToHSLWithCache(anotherPoleColor);
2263
+ const modified = modifyHSL(hsl, pole, anotherPole);
2264
+ const {r, g, b, a} = hslToRGB(modified);
2265
+ const matrix = createFilterMatrix({...theme, mode: 0});
2266
+ const [rf, gf, bf] = applyColorMatrix([r, g, b], matrix);
2267
+ const color =
2268
+ a === 1
2269
+ ? rgbToHexString({r: rf, g: gf, b: bf})
2270
+ : rgbToString({r: rf, g: gf, b: bf, a});
2271
+ fnCache.set(id, color);
2272
+ return color;
2359
2273
  }
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
- }
2375
- });
2376
- }
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;
2274
+ function modifyAndRegisterColor(type, rgb, theme, modifier) {
2275
+ const registered = getRegisteredColor(type, rgb);
2276
+ if (registered) {
2277
+ return registered;
2278
+ }
2279
+ const value = modifier(rgb, theme);
2280
+ return registerColor(type, rgb, value);
2388
2281
  }
2389
- function removeCanvas() {
2390
- canvas = null;
2391
- context = null;
2282
+ function modifyLightSchemeColor(rgb, theme) {
2283
+ const poleBg = getBgPole(theme);
2284
+ const poleFg = getFgPole(theme);
2285
+ return modifyColorWithCache(
2286
+ rgb,
2287
+ theme,
2288
+ modifyLightModeHSL,
2289
+ poleFg,
2290
+ poleBg
2291
+ );
2392
2292
  }
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;
2293
+ function modifyLightModeHSL({h, s, l, a}, poleFg, poleBg) {
2294
+ const isDark = l < 0.5;
2295
+ let isNeutral;
2296
+ if (isDark) {
2297
+ isNeutral = l < 0.2 || s < 0.12;
2403
2298
  } else {
2404
- sw = image.width;
2405
- sh = image.height;
2406
- }
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
- };
2299
+ const isBlue = h > 200 && h < 280;
2300
+ isNeutral = s < 0.24 || (l > 0.8 && isBlue);
2415
2301
  }
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)
2421
- );
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
- }
2302
+ let hx = h;
2303
+ let sx = s;
2304
+ if (isNeutral) {
2305
+ if (isDark) {
2306
+ hx = poleFg.h;
2307
+ sx = poleFg.s;
2308
+ } else {
2309
+ hx = poleBg.h;
2310
+ sx = poleBg.s;
2455
2311
  }
2456
2312
  }
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
- };
2313
+ const lx = scale(l, 0, 1, poleFg.l, poleBg.l);
2314
+ return {h: hx, s: sx, l: lx, a};
2471
2315
  }
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;
2316
+ const MAX_BG_LIGHTNESS = 0.4;
2317
+ function modifyBgHSL({h, s, l, a}, pole) {
2318
+ const isDark = l < 0.5;
2319
+ const isBlue = h > 200 && h < 280;
2320
+ const isNeutral = s < 0.12 || (l > 0.8 && isBlue);
2321
+ if (isDark) {
2322
+ const lx = scale(l, 0, 0.5, 0, MAX_BG_LIGHTNESS);
2323
+ if (isNeutral) {
2324
+ const hx = pole.h;
2325
+ const sx = pole.s;
2326
+ return {h: hx, s: sx, l: lx, a};
2327
+ }
2328
+ return {h, s, l: lx, a};
2484
2329
  }
2485
- if (blobURLCheckRequested) {
2486
- return await new Promise((resolve) =>
2487
- blobURLCheckAwaiters.push(resolve)
2488
- );
2330
+ let lx = scale(l, 0.5, 1, MAX_BG_LIGHTNESS, pole.l);
2331
+ if (isNeutral) {
2332
+ const hx = pole.h;
2333
+ const sx = pole.s;
2334
+ return {h: hx, s: sx, l: lx, a};
2489
2335
  }
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
- });
2336
+ let hx = h;
2337
+ const isYellow = h > 60 && h < 180;
2338
+ if (isYellow) {
2339
+ const isCloserToGreen = h > 120;
2340
+ if (isCloserToGreen) {
2341
+ hx = scale(h, 120, 180, 135, 180);
2342
+ } else {
2343
+ hx = scale(h, 60, 120, 60, 105);
2344
+ }
2345
+ }
2346
+ if (hx > 40 && hx < 80) {
2347
+ lx *= 0.75;
2348
+ }
2349
+ return {h: hx, s, l: lx, a};
2506
2350
  }
2507
- function isBlobURLCheckResultReady() {
2508
- return isBlobURLSupported != null || !canUseProxy;
2351
+ function _modifyBackgroundColor(rgb, theme) {
2352
+ if (theme.mode === 0) {
2353
+ if (__PLUS__) {
2354
+ const poles = getBackgroundPoles();
2355
+ return modifyColorWithCache(
2356
+ rgb,
2357
+ theme,
2358
+ modifyLightSchemeColorExtended,
2359
+ poles[0],
2360
+ poles[1]
2361
+ );
2362
+ }
2363
+ return modifyLightSchemeColor(rgb, theme);
2364
+ }
2365
+ if (__PLUS__) {
2366
+ const poles = getBackgroundPoles();
2367
+ return modifyColorWithCache(
2368
+ rgb,
2369
+ theme,
2370
+ modifyBgColorExtended,
2371
+ poles[0],
2372
+ poles[1]
2373
+ );
2374
+ }
2375
+ const pole = getBgPole(theme);
2376
+ return modifyColorWithCache(rgb, theme, modifyBgHSL, pole);
2509
2377
  }
2510
- function onCSPError(err) {
2511
- if (err.blockedURI === "blob") {
2512
- isBlobURLSupported = false;
2513
- document.removeEventListener("securitypolicyviolation", onCSPError);
2378
+ function modifyBackgroundColor(
2379
+ rgb,
2380
+ theme,
2381
+ shouldRegisterColorVariable = true
2382
+ ) {
2383
+ if (!shouldRegisterColorVariable) {
2384
+ return _modifyBackgroundColor(rgb, theme);
2514
2385
  }
2386
+ return modifyAndRegisterColor(
2387
+ "background",
2388
+ rgb,
2389
+ theme,
2390
+ _modifyBackgroundColor
2391
+ );
2515
2392
  }
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);
2393
+ const MIN_FG_LIGHTNESS = 0.55;
2394
+ function modifyBlueFgHue(hue) {
2395
+ return scale(hue, 205, 245, 205, 220);
2396
+ }
2397
+ function modifyFgHSL({h, s, l, a}, pole) {
2398
+ const isLight = l > 0.5;
2399
+ const isNeutral = l < 0.2 || s < 0.24;
2400
+ const isBlue = !isNeutral && h > 205 && h < 245;
2401
+ if (isLight) {
2402
+ const lx = scale(l, 0.5, 1, MIN_FG_LIGHTNESS, pole.l);
2403
+ if (isNeutral) {
2404
+ const hx = pole.h;
2405
+ const sx = pole.s;
2406
+ return {h: hx, s: sx, l: lx, a};
2407
+ }
2408
+ let hx = h;
2409
+ if (isBlue) {
2410
+ hx = modifyBlueFgHue(h);
2411
+ }
2412
+ return {h: hx, s, l: lx, a};
2521
2413
  }
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)}`;
2414
+ if (isNeutral) {
2415
+ const hx = pole.h;
2416
+ const sx = pole.s;
2417
+ const lx = scale(l, 0, 0.5, pole.l, MIN_FG_LIGHTNESS);
2418
+ return {h: hx, s: sx, l: lx, a};
2535
2419
  }
2536
- const bytes = new Uint8Array(svg.length);
2537
- for (let i = 0; i < svg.length; i++) {
2538
- bytes[i] = svg.charCodeAt(i);
2420
+ let hx = h;
2421
+ let lx;
2422
+ if (isBlue) {
2423
+ hx = modifyBlueFgHue(h);
2424
+ lx = scale(l, 0, 0.5, pole.l, Math.min(1, MIN_FG_LIGHTNESS + 0.05));
2425
+ } else {
2426
+ lx = scale(l, 0, 0.5, pole.l, MIN_FG_LIGHTNESS);
2539
2427
  }
2540
- const blob = new Blob([bytes], {type: "image/svg+xml"});
2541
- const objectURL = URL.createObjectURL(blob);
2542
- objectURLs.add(objectURL);
2543
- return objectURL;
2544
- }
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);
2428
+ return {h: hx, s, l: lx, a};
2554
2429
  }
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;
2430
+ function _modifyForegroundColor(rgb, theme) {
2431
+ if (theme.mode === 0) {
2432
+ if (__PLUS__) {
2433
+ const poles = getTextPoles();
2434
+ return modifyColorWithCache(
2435
+ rgb,
2436
+ theme,
2437
+ modifyLightSchemeColorExtended,
2438
+ poles[0],
2439
+ poles[1]
2440
+ );
2441
+ }
2442
+ return modifyLightSchemeColor(rgb, theme);
2566
2443
  }
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);
2444
+ if (__PLUS__) {
2445
+ const poles = getTextPoles();
2446
+ return modifyColorWithCache(
2447
+ rgb,
2448
+ theme,
2449
+ modifyFgColorExtended,
2450
+ poles[0],
2451
+ poles[1]
2452
+ );
2571
2453
  }
2572
- return new Blob([bytes], {type: mediaType});
2454
+ const pole = getFgPole(theme);
2455
+ return modifyColorWithCache(rgb, theme, modifyFgHSL, pole);
2573
2456
  }
2574
- async function tryConvertDataURLToBlobURL(dataURL) {
2575
- if (!isBlobURLSupported) {
2576
- return null;
2457
+ function modifyForegroundColor(
2458
+ rgb,
2459
+ theme,
2460
+ shouldRegisterColorVariable = true
2461
+ ) {
2462
+ if (!shouldRegisterColorVariable) {
2463
+ return _modifyForegroundColor(rgb, theme);
2577
2464
  }
2578
- const hash = getHashCode(dataURL);
2579
- let blobURL = dataURLBlobURLs.get(hash);
2580
- if (blobURL) {
2581
- return blobURL;
2465
+ return modifyAndRegisterColor(
2466
+ "text",
2467
+ rgb,
2468
+ theme,
2469
+ _modifyForegroundColor
2470
+ );
2471
+ }
2472
+ function modifyBorderHSL({h, s, l, a}, poleFg, poleBg) {
2473
+ const isDark = l < 0.5;
2474
+ const isNeutral = l < 0.2 || s < 0.24;
2475
+ let hx = h;
2476
+ let sx = s;
2477
+ if (isNeutral) {
2478
+ if (isDark) {
2479
+ hx = poleFg.h;
2480
+ sx = poleFg.s;
2481
+ } else {
2482
+ hx = poleBg.h;
2483
+ sx = poleBg.s;
2484
+ }
2582
2485
  }
2583
- let blob = tryConvertDataURLToBlobSync(dataURL);
2584
- if (!blob) {
2585
- const response = await fetch(dataURL);
2586
- blob = await response.blob();
2486
+ const lx = scale(l, 0, 1, 0.5, 0.2);
2487
+ return {h: hx, s: sx, l: lx, a};
2488
+ }
2489
+ function _modifyBorderColor(rgb, theme) {
2490
+ if (theme.mode === 0) {
2491
+ return modifyLightSchemeColor(rgb, theme);
2587
2492
  }
2588
- blobURL = URL.createObjectURL(blob);
2589
- dataURLBlobURLs.set(hash, blobURL);
2590
- return blobURL;
2493
+ const poleFg = getFgPole(theme);
2494
+ const poleBg = getBgPole(theme);
2495
+ return modifyColorWithCache(
2496
+ rgb,
2497
+ theme,
2498
+ modifyBorderHSL,
2499
+ poleFg,
2500
+ poleBg
2501
+ );
2591
2502
  }
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();
2503
+ function modifyBorderColor(rgb, theme, shouldRegisterColorVariable = true) {
2504
+ if (!shouldRegisterColorVariable) {
2505
+ return _modifyBorderColor(rgb, theme);
2506
+ }
2507
+ return modifyAndRegisterColor("border", rgb, theme, _modifyBorderColor);
2508
+ }
2509
+ function modifyShadowColor(rgb, theme) {
2510
+ return modifyBackgroundColor(rgb, theme);
2511
+ }
2512
+ function modifyGradientColor(rgb, theme) {
2513
+ return modifyBackgroundColor(rgb, theme);
2514
+ }
2515
+
2516
+ const gradientLength = "gradient".length;
2517
+ const conicGradient = "conic-";
2518
+ const conicGradientLength = conicGradient.length;
2519
+ const radialGradient = "radial-";
2520
+ const linearGradient = "linear-";
2521
+ function parseGradient(value) {
2522
+ const result = [];
2523
+ let index = 0;
2524
+ let startIndex = conicGradient.length;
2525
+ while ((index = value.indexOf("gradient", startIndex)) !== -1) {
2526
+ let typeGradient;
2527
+ [linearGradient, radialGradient, conicGradient].find(
2528
+ (possibleType) => {
2529
+ if (index - possibleType.length >= 0) {
2530
+ const possibleGradient = value.substring(
2531
+ index - possibleType.length,
2532
+ index
2533
+ );
2534
+ if (possibleGradient === possibleType) {
2535
+ if (
2536
+ value.slice(
2537
+ index - possibleType.length - 10,
2538
+ index - possibleType.length - 1
2539
+ ) === "repeating"
2540
+ ) {
2541
+ typeGradient = `repeating-${possibleType}gradient`;
2542
+ return true;
2543
+ }
2544
+ if (
2545
+ value.slice(
2546
+ index - possibleType.length - 8,
2547
+ index - possibleType.length - 1
2548
+ ) === "-webkit"
2549
+ ) {
2550
+ typeGradient = `-webkit-${possibleType}gradient`;
2551
+ return true;
2552
+ }
2553
+ typeGradient = `${possibleType}gradient`;
2554
+ return true;
2555
+ }
2556
+ }
2557
+ }
2558
+ );
2559
+ if (!typeGradient) {
2560
+ break;
2561
+ }
2562
+ const {start, end} = getParenthesesRange(
2563
+ value,
2564
+ index + gradientLength
2565
+ );
2566
+ const match = value.substring(start + 1, end - 1);
2567
+ startIndex = end + 1 + conicGradientLength;
2568
+ result.push({
2569
+ typeGradient,
2570
+ match,
2571
+ offset: typeGradient.length + 2,
2572
+ index: index - typeGradient.length + gradientLength,
2573
+ hasComma: true
2574
+ });
2575
+ }
2576
+ if (result.length) {
2577
+ result[result.length - 1].hasComma = false;
2578
+ }
2579
+ return result;
2599
2580
  }
2600
2581
 
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
2582
+ const STORAGE_KEY_IMAGE_DETAILS_LIST = "__darkreader__imageDetails_v2_list";
2583
+ const STORAGE_KEY_IMAGE_DETAILS_PREFIX = "__darkreader__imageDetails_v2_";
2584
+ const STORAGE_KEY_CSS_FETCH_PREFIX = "__darkreader__cssFetch_";
2585
+ let imageCacheTimeout = 0;
2586
+ const imageDetailsCacheQueue = new Map();
2587
+ const cachedImageUrls = [];
2588
+ function writeImageDetailsQueue() {
2589
+ imageDetailsCacheQueue.forEach((details, url) => {
2590
+ if (url && url.startsWith("https://")) {
2591
+ try {
2592
+ const json = JSON.stringify(details);
2593
+ sessionStorage.setItem(
2594
+ `${STORAGE_KEY_IMAGE_DETAILS_PREFIX}${url}`,
2595
+ json
2613
2596
  );
2614
- }
2615
- });
2597
+ cachedImageUrls.push(url);
2598
+ } catch (err) {}
2599
+ }
2616
2600
  });
2601
+ imageDetailsCacheQueue.clear();
2602
+ sessionStorage.setItem(
2603
+ STORAGE_KEY_IMAGE_DETAILS_LIST,
2604
+ JSON.stringify(cachedImageUrls)
2605
+ );
2617
2606
  }
2618
- function releaseVariablesSheet() {
2619
- variablesSheet = null;
2620
- clearColorPalette();
2607
+ function writeImageDetailsCache(url, imageDetails) {
2608
+ if (!url || !url.startsWith("https://")) {
2609
+ return;
2610
+ }
2611
+ imageDetailsCacheQueue.set(url, imageDetails);
2612
+ clearTimeout(imageCacheTimeout);
2613
+ imageCacheTimeout = setTimeout(writeImageDetailsQueue, 1000);
2621
2614
  }
2622
- function getRegisteredVariableValue(type, registered) {
2623
- return `var(${registered[type].variable}, ${registered[type].value})`;
2615
+ function readImageDetailsCache(targetMap) {
2616
+ try {
2617
+ const jsonList = sessionStorage.getItem(
2618
+ STORAGE_KEY_IMAGE_DETAILS_LIST
2619
+ );
2620
+ if (!jsonList) {
2621
+ return;
2622
+ }
2623
+ const list = JSON.parse(jsonList);
2624
+ list.forEach((url) => {
2625
+ const json = sessionStorage.getItem(
2626
+ `${STORAGE_KEY_IMAGE_DETAILS_PREFIX}${url}`
2627
+ );
2628
+ if (json) {
2629
+ const details = JSON.parse(json);
2630
+ targetMap.set(url, details);
2631
+ }
2632
+ });
2633
+ } catch (err) {}
2624
2634
  }
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
- }
2635
+ function writeCSSFetchCache(url, cssText) {
2636
+ const key = `${STORAGE_KEY_CSS_FETCH_PREFIX}${url}`;
2637
+ try {
2638
+ sessionStorage.setItem(key, cssText);
2639
+ } catch (err) {}
2640
+ }
2641
+ function readCSSFetchCache(url) {
2642
+ const key = `${STORAGE_KEY_CSS_FETCH_PREFIX}${url}`;
2643
+ try {
2644
+ return sessionStorage.getItem(key) ?? null;
2645
+ } catch (err) {}
2631
2646
  return null;
2632
2647
  }
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);
2648
+
2649
+ function toSVGMatrix(matrix) {
2650
+ return matrix
2651
+ .slice(0, 4)
2652
+ .map((m) => m.map((m) => m.toFixed(3)).join(" "))
2653
+ .join(" ");
2654
+ }
2655
+ function getSVGFilterMatrixValue(config) {
2656
+ return toSVGMatrix(createFilterMatrix(config));
2657
+ }
2658
+
2659
+ const MAX_FRAME_DURATION = 1000 / 60;
2660
+ class AsyncQueue {
2661
+ constructor() {
2662
+ this.queue = [];
2663
+ this.timerId = null;
2642
2664
  }
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);
2665
+ addTask(task) {
2666
+ this.queue.push(task);
2667
+ this.scheduleFrame();
2647
2668
  }
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);
2669
+ stop() {
2670
+ if (this.timerId !== null) {
2671
+ cancelAnimationFrame(this.timerId);
2672
+ this.timerId = null;
2660
2673
  }
2661
- if (registered.text) {
2662
- text.push(registered.parsed);
2674
+ this.queue = [];
2675
+ }
2676
+ scheduleFrame() {
2677
+ if (this.timerId) {
2678
+ return;
2663
2679
  }
2664
- });
2665
- return {background, border, text};
2666
- }
2667
- function clearColorPalette() {
2668
- registeredColors.clear();
2680
+ this.timerId = requestAnimationFrame(() => {
2681
+ this.timerId = null;
2682
+ const start = Date.now();
2683
+ let cb;
2684
+ while ((cb = this.queue.shift())) {
2685
+ cb();
2686
+ if (Date.now() - start >= MAX_FRAME_DURATION) {
2687
+ this.scheduleFrame();
2688
+ break;
2689
+ }
2690
+ }
2691
+ });
2692
+ }
2669
2693
  }
2670
2694
 
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]};`;
2695
+ const resolvers$1 = new Map();
2696
+ const rejectors = new Map();
2697
+ async function bgFetch(request) {
2698
+ if (window.DarkReader?.Plugins?.fetch) {
2699
+ return window.DarkReader.Plugins.fetch(request);
2700
+ }
2701
+ return new Promise((resolve, reject) => {
2702
+ const id = generateUID();
2703
+ resolvers$1.set(id, resolve);
2704
+ rejectors.set(id, reject);
2705
+ chrome.runtime.sendMessage({
2706
+ type: MessageTypeCStoBG.FETCH,
2707
+ data: request,
2708
+ id
2709
+ });
2705
2710
  });
2706
- themeCacheKeys$1.forEach((key) => {
2707
- resultId += `${theme[key]};`;
2711
+ }
2712
+ chrome.runtime.onMessage.addListener(({type, data, error, id}) => {
2713
+ if (type === MessageTypeBGtoCS.FETCH_RESPONSE) {
2714
+ const resolve = resolvers$1.get(id);
2715
+ const reject = rejectors.get(id);
2716
+ resolvers$1.delete(id);
2717
+ rejectors.delete(id);
2718
+ if (error) {
2719
+ reject &&
2720
+ reject(
2721
+ typeof error === "string" ? new Error(error) : error
2722
+ );
2723
+ } else {
2724
+ resolve && resolve(data);
2725
+ }
2726
+ }
2727
+ });
2728
+
2729
+ const imageManager = new AsyncQueue();
2730
+ async function getImageDetails(url) {
2731
+ return new Promise(async (resolve, reject) => {
2732
+ try {
2733
+ const dataURL = url.startsWith("data:")
2734
+ ? url
2735
+ : await getDataURL(url);
2736
+ const blob =
2737
+ tryConvertDataURLToBlobSync(dataURL) ??
2738
+ (await loadAsBlob(url));
2739
+ let image;
2740
+ if (dataURL.startsWith("data:image/svg+xml")) {
2741
+ image = await loadImage(dataURL);
2742
+ } else {
2743
+ image =
2744
+ (await tryCreateImageBitmap(blob)) ??
2745
+ (await loadImage(dataURL));
2746
+ }
2747
+ imageManager.addTask(() => {
2748
+ const analysis = analyzeImage(image);
2749
+ resolve({
2750
+ src: url,
2751
+ dataURL: analysis.isLarge ? "" : dataURL,
2752
+ width: image.width,
2753
+ height: image.height,
2754
+ ...analysis
2755
+ });
2756
+ });
2757
+ } catch (error) {
2758
+ reject(error);
2759
+ }
2708
2760
  });
2709
- return resultId;
2710
2761
  }
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);
2762
+ async function getDataURL(url) {
2763
+ const parsedURL = new URL(url);
2764
+ if (parsedURL.origin === location.origin) {
2765
+ return await loadAsDataURL(url);
2728
2766
  }
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;
2767
+ return await bgFetch({url, responseType: "data-url"});
2745
2768
  }
2746
- function modifyAndRegisterColor(type, rgb, theme, modifier) {
2747
- const registered = getRegisteredColor(type, rgb);
2748
- if (registered) {
2749
- return registered;
2769
+ async function tryCreateImageBitmap(blob) {
2770
+ try {
2771
+ return await createImageBitmap(blob);
2772
+ } catch (err) {
2773
+ logWarn(
2774
+ `Unable to create image bitmap for type ${blob.type}: ${String(err)}`
2775
+ );
2776
+ return null;
2750
2777
  }
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
2778
  }
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;
2779
+ const INCOMPLETE_DOC_LOADING_IMAGE_LIMIT = 256;
2780
+ let loadingImagesCount = 0;
2781
+ async function loadImage(url) {
2782
+ return new Promise((resolve, reject) => {
2783
+ const image = new Image();
2784
+ image.onload = () => resolve(image);
2785
+ image.onerror = () => reject(`Unable to load image ${url}`);
2786
+ if (
2787
+ ++loadingImagesCount <= INCOMPLETE_DOC_LOADING_IMAGE_LIMIT ||
2788
+ isReadyStateComplete()
2789
+ ) {
2790
+ image.src = url;
2780
2791
  } else {
2781
- hx = poleBg.h;
2782
- sx = poleBg.s;
2792
+ addReadyStateCompleteListener(() => (image.src = url));
2783
2793
  }
2784
- }
2785
- const lx = scale(l, 0, 1, poleFg.l, poleBg.l);
2786
- return {h: hx, s: sx, l: lx, a};
2794
+ });
2787
2795
  }
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};
2796
+ const MAX_ANALYSIS_PIXELS_COUNT = 32 * 32;
2797
+ let canvas;
2798
+ let context;
2799
+ function createCanvas() {
2800
+ const maxWidth = MAX_ANALYSIS_PIXELS_COUNT;
2801
+ const maxHeight = MAX_ANALYSIS_PIXELS_COUNT;
2802
+ canvas = document.createElement("canvas");
2803
+ canvas.width = maxWidth;
2804
+ canvas.height = maxHeight;
2805
+ context = canvas.getContext("2d", {willReadFrequently: true});
2806
+ context.imageSmoothingEnabled = false;
2807
+ }
2808
+ function removeCanvas() {
2809
+ canvas = null;
2810
+ context = null;
2811
+ }
2812
+ const LARGE_IMAGE_PIXELS_COUNT = 512 * 512;
2813
+ function analyzeImage(image) {
2814
+ if (!canvas) {
2815
+ createCanvas();
2801
2816
  }
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};
2817
+ let sw;
2818
+ let sh;
2819
+ if (image instanceof HTMLImageElement) {
2820
+ sw = image.naturalWidth;
2821
+ sh = image.naturalHeight;
2822
+ } else {
2823
+ sw = image.width;
2824
+ sh = image.height;
2807
2825
  }
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);
2816
- }
2826
+ if (sw === 0 || sh === 0) {
2827
+ logWarn("Image is empty");
2828
+ return {
2829
+ isDark: false,
2830
+ isLight: false,
2831
+ isTransparent: false,
2832
+ isLarge: false
2833
+ };
2817
2834
  }
2818
- if (hx > 40 && hx < 80) {
2819
- lx *= 0.75;
2835
+ const isLarge = sw * sh > LARGE_IMAGE_PIXELS_COUNT;
2836
+ const sourcePixelsCount = sw * sh;
2837
+ const k = Math.min(
2838
+ 1,
2839
+ Math.sqrt(MAX_ANALYSIS_PIXELS_COUNT / sourcePixelsCount)
2840
+ );
2841
+ const width = Math.ceil(sw * k);
2842
+ const height = Math.ceil(sh * k);
2843
+ context.clearRect(0, 0, width, height);
2844
+ context.drawImage(image, 0, 0, sw, sh, 0, 0, width, height);
2845
+ const imageData = context.getImageData(0, 0, width, height);
2846
+ const d = imageData.data;
2847
+ const TRANSPARENT_ALPHA_THRESHOLD = 0.05;
2848
+ const DARK_LIGHTNESS_THRESHOLD = 0.4;
2849
+ const LIGHT_LIGHTNESS_THRESHOLD = 0.7;
2850
+ let transparentPixelsCount = 0;
2851
+ let darkPixelsCount = 0;
2852
+ let lightPixelsCount = 0;
2853
+ let i, x, y;
2854
+ let r, g, b, a;
2855
+ let l;
2856
+ for (y = 0; y < height; y++) {
2857
+ for (x = 0; x < width; x++) {
2858
+ i = 4 * (y * width + x);
2859
+ r = d[i + 0];
2860
+ g = d[i + 1];
2861
+ b = d[i + 2];
2862
+ a = d[i + 3];
2863
+ if (a / 255 < TRANSPARENT_ALPHA_THRESHOLD) {
2864
+ transparentPixelsCount++;
2865
+ } else {
2866
+ l = getSRGBLightness(r, g, b);
2867
+ if (l < DARK_LIGHTNESS_THRESHOLD) {
2868
+ darkPixelsCount++;
2869
+ }
2870
+ if (l > LIGHT_LIGHTNESS_THRESHOLD) {
2871
+ lightPixelsCount++;
2872
+ }
2873
+ }
2874
+ }
2820
2875
  }
2821
- return {h: hx, s, l: lx, a};
2876
+ const totalPixelsCount = width * height;
2877
+ const opaquePixelsCount = totalPixelsCount - transparentPixelsCount;
2878
+ const DARK_IMAGE_THRESHOLD = 0.7;
2879
+ const LIGHT_IMAGE_THRESHOLD = 0.7;
2880
+ const TRANSPARENT_IMAGE_THRESHOLD = 0.1;
2881
+ return {
2882
+ isDark: darkPixelsCount / opaquePixelsCount >= DARK_IMAGE_THRESHOLD,
2883
+ isLight:
2884
+ lightPixelsCount / opaquePixelsCount >= LIGHT_IMAGE_THRESHOLD,
2885
+ isTransparent:
2886
+ transparentPixelsCount / totalPixelsCount >=
2887
+ TRANSPARENT_IMAGE_THRESHOLD,
2888
+ isLarge
2889
+ };
2822
2890
  }
2823
- function _modifyBackgroundColor(rgb, theme) {
2824
- if (theme.mode === 0) {
2825
- return modifyLightSchemeColor(rgb, theme);
2891
+ let isBlobURLSupported = null;
2892
+ let canUseProxy = false;
2893
+ let blobURLCheckRequested = false;
2894
+ const blobURLCheckAwaiters = [];
2895
+ document.addEventListener(
2896
+ "__darkreader__inlineScriptsAllowed",
2897
+ () => (canUseProxy = true),
2898
+ {once: true}
2899
+ );
2900
+ async function requestBlobURLCheck() {
2901
+ if (!canUseProxy) {
2902
+ return;
2826
2903
  }
2827
- const pole = getBgPole(theme);
2828
- return modifyColorWithCache(
2829
- rgb,
2830
- {...theme, mode: 0},
2831
- modifyBgHSL,
2832
- pole
2833
- );
2904
+ if (blobURLCheckRequested) {
2905
+ return await new Promise((resolve) =>
2906
+ blobURLCheckAwaiters.push(resolve)
2907
+ );
2908
+ }
2909
+ blobURLCheckRequested = true;
2910
+ await new Promise((resolve) => {
2911
+ document.addEventListener(
2912
+ "__darkreader__blobURLCheckResponse",
2913
+ (e) => {
2914
+ isBlobURLSupported = e.detail.blobURLAllowed;
2915
+ resolve();
2916
+ blobURLCheckAwaiters.forEach((r) => r());
2917
+ blobURLCheckAwaiters.splice(0);
2918
+ },
2919
+ {once: true}
2920
+ );
2921
+ document.dispatchEvent(
2922
+ new CustomEvent("__darkreader__blobURLCheckRequest")
2923
+ );
2924
+ });
2834
2925
  }
2835
- function modifyBackgroundColor(
2836
- rgb,
2837
- theme,
2838
- shouldRegisterColorVariable = true
2839
- ) {
2840
- if (!shouldRegisterColorVariable) {
2841
- return _modifyBackgroundColor(rgb, theme);
2842
- }
2843
- return modifyAndRegisterColor(
2844
- "background",
2845
- rgb,
2846
- theme,
2847
- _modifyBackgroundColor
2848
- );
2926
+ function isBlobURLCheckResultReady() {
2927
+ return isBlobURLSupported != null || !canUseProxy;
2849
2928
  }
2850
- const MIN_FG_LIGHTNESS = 0.55;
2851
- function modifyBlueFgHue(hue) {
2852
- return scale(hue, 205, 245, 205, 220);
2929
+ function onCSPError(err) {
2930
+ if (err.blockedURI === "blob") {
2931
+ isBlobURLSupported = false;
2932
+ document.removeEventListener("securitypolicyviolation", onCSPError);
2933
+ }
2853
2934
  }
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};
2935
+ document.addEventListener("securitypolicyviolation", onCSPError);
2936
+ const objectURLs = new Set();
2937
+ function getFilteredImageURL({dataURL, width, height}, theme) {
2938
+ if (dataURL.startsWith("data:image/svg+xml")) {
2939
+ dataURL = escapeXML(dataURL);
2870
2940
  }
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};
2941
+ const matrix = getSVGFilterMatrixValue(theme);
2942
+ const svg = [
2943
+ `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}">`,
2944
+ "<defs>",
2945
+ '<filter id="darkreader-image-filter">',
2946
+ `<feColorMatrix type="matrix" values="${matrix}" />`,
2947
+ "</filter>",
2948
+ "</defs>",
2949
+ `<image width="${width}" height="${height}" filter="url(#darkreader-image-filter)" xlink:href="${dataURL}" />`,
2950
+ "</svg>"
2951
+ ].join("");
2952
+ if (!isBlobURLSupported) {
2953
+ return `data:image/svg+xml;base64,${btoa(svg)}`;
2876
2954
  }
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);
2955
+ const bytes = new Uint8Array(svg.length);
2956
+ for (let i = 0; i < svg.length; i++) {
2957
+ bytes[i] = svg.charCodeAt(i);
2884
2958
  }
2885
- return {h: hx, s, l: lx, a};
2959
+ const blob = new Blob([bytes], {type: "image/svg+xml"});
2960
+ const objectURL = URL.createObjectURL(blob);
2961
+ objectURLs.add(objectURL);
2962
+ return objectURL;
2886
2963
  }
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
- );
2964
+ const xmlEscapeChars = {
2965
+ "<": "&lt;",
2966
+ ">": "&gt;",
2967
+ "&": "&amp;",
2968
+ "'": "&apos;",
2969
+ '"': "&quot;"
2970
+ };
2971
+ function escapeXML(str) {
2972
+ return str.replace(/[<>&'"]/g, (c) => xmlEscapeChars[c] ?? c);
2898
2973
  }
2899
- function modifyForegroundColor(
2900
- rgb,
2901
- theme,
2902
- shouldRegisterColorVariable = true
2903
- ) {
2904
- if (!shouldRegisterColorVariable) {
2905
- return _modifyForegroundColor(rgb, theme);
2974
+ const dataURLBlobURLs = new Map();
2975
+ function tryConvertDataURLToBlobSync(dataURL) {
2976
+ const colonIndex = dataURL.indexOf(":");
2977
+ const semicolonIndex = dataURL.indexOf(";", colonIndex + 1);
2978
+ const commaIndex = dataURL.indexOf(",", semicolonIndex + 1);
2979
+ const encoding = dataURL
2980
+ .substring(semicolonIndex + 1, commaIndex)
2981
+ .toLocaleLowerCase();
2982
+ const mediaType = dataURL.substring(colonIndex + 1, semicolonIndex);
2983
+ if (encoding !== "base64" || !mediaType) {
2984
+ return null;
2906
2985
  }
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
- }
2986
+ const characters = atob(dataURL.substring(commaIndex + 1));
2987
+ const bytes = new Uint8Array(characters.length);
2988
+ for (let i = 0; i < characters.length; i++) {
2989
+ bytes[i] = characters.charCodeAt(i);
2927
2990
  }
2928
- const lx = scale(l, 0, 1, 0.5, 0.2);
2929
- return {h: hx, s: sx, l: lx, a};
2991
+ return new Blob([bytes], {type: mediaType});
2930
2992
  }
2931
- function _modifyBorderColor(rgb, theme) {
2932
- if (theme.mode === 0) {
2933
- return modifyLightSchemeColor(rgb, theme);
2993
+ async function tryConvertDataURLToBlobURL(dataURL) {
2994
+ if (!isBlobURLSupported) {
2995
+ return null;
2934
2996
  }
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);
2997
+ const hash = getHashCode(dataURL);
2998
+ let blobURL = dataURLBlobURLs.get(hash);
2999
+ if (blobURL) {
3000
+ return blobURL;
2948
3001
  }
2949
- return modifyAndRegisterColor("border", rgb, theme, _modifyBorderColor);
2950
- }
2951
- function modifyShadowColor(rgb, theme) {
2952
- return modifyBackgroundColor(rgb, theme);
3002
+ let blob = tryConvertDataURLToBlobSync(dataURL);
3003
+ if (!blob) {
3004
+ const response = await fetch(dataURL);
3005
+ blob = await response.blob();
3006
+ }
3007
+ blobURL = URL.createObjectURL(blob);
3008
+ dataURLBlobURLs.set(hash, blobURL);
3009
+ return blobURL;
2953
3010
  }
2954
- function modifyGradientColor(rgb, theme) {
2955
- return modifyBackgroundColor(rgb, theme);
3011
+ function cleanImageProcessingCache() {
3012
+ imageManager && imageManager.stop();
3013
+ removeCanvas();
3014
+ objectURLs.forEach((u) => URL.revokeObjectURL(u));
3015
+ objectURLs.clear();
3016
+ dataURLBlobURLs.forEach((u) => URL.revokeObjectURL(u));
3017
+ dataURLBlobURLs.clear();
2956
3018
  }
2957
3019
 
2958
3020
  function getPriority(ruleStyle, property) {
@@ -3223,10 +3285,18 @@
3223
3285
  "auto"
3224
3286
  ]);
3225
3287
  function getColorModifier(prop, value, rule) {
3226
- if (unparsableColors.has(value.toLowerCase())) {
3288
+ if (
3289
+ unparsableColors.has(value.toLowerCase()) &&
3290
+ !(prop === "color" && value === "initial")
3291
+ ) {
3227
3292
  return value;
3228
3293
  }
3229
- const rgb = parseColorWithCache(value);
3294
+ let rgb = null;
3295
+ if (prop === "color" && value === "initial") {
3296
+ rgb = {r: 0, g: 0, b: 0, a: 1};
3297
+ } else {
3298
+ rgb = parseColorWithCache(value);
3299
+ }
3230
3300
  if (!rgb) {
3231
3301
  logWarn("Couldn't parse color", value);
3232
3302
  return null;
@@ -3655,7 +3725,7 @@
3655
3725
  return null;
3656
3726
  }
3657
3727
  return (theme) =>
3658
- `${modifyForegroundColor(thumb, theme)} ${modifyBackgroundColor(thumb, theme)}`;
3728
+ `${modifyForegroundColor(thumb, theme)} ${modifyBackgroundColor(track, theme)}`;
3659
3729
  }
3660
3730
  function getColorSchemeModifier() {
3661
3731
  return (theme) => (theme.mode === 0 ? "dark light" : "dark");
@@ -4014,7 +4084,7 @@
4014
4084
  const modified = modify();
4015
4085
  if (unknownVars.size > 0) {
4016
4086
  const isFallbackResolved = modified.match(
4017
- /^var\(.*?, (var\(--darkreader-bg--.*\))|(#[0-9A-Fa-f]+)|([a-z]+)|(rgba?\(.+\))|(hsla?\(.+\))\)$/
4087
+ /^var\(.*?, ((var\(--darkreader-bg--.*\))|(#[0-9A-Fa-f]+)|([a-z]+)|(rgba?\(.+\))|(hsla?\(.+\)))\)$/
4018
4088
  );
4019
4089
  if (isFallbackResolved) {
4020
4090
  return modified;
@@ -4492,17 +4562,6 @@
4492
4562
  return replaced;
4493
4563
  }
4494
4564
 
4495
- const themeCacheKeys = [
4496
- "mode",
4497
- "brightness",
4498
- "contrast",
4499
- "grayscale",
4500
- "sepia",
4501
- "darkSchemeBackgroundColor",
4502
- "darkSchemeTextColor",
4503
- "lightSchemeBackgroundColor",
4504
- "lightSchemeTextColor"
4505
- ];
4506
4565
  function getThemeKey(theme) {
4507
4566
  let resultKey = "";
4508
4567
  themeCacheKeys.forEach((key) => {
@@ -4816,6 +4875,12 @@
4816
4875
  function buildStyleSheet() {
4817
4876
  function createTarget(group, parent) {
4818
4877
  const {rule} = group;
4878
+ if (isStyleRule(rule)) {
4879
+ const {selectorText} = rule;
4880
+ const index = parent.cssRules.length;
4881
+ parent.insertRule(`${selectorText} {}`, index);
4882
+ return parent.cssRules[index];
4883
+ }
4819
4884
  if (isMediaRule(rule)) {
4820
4885
  const {media} = rule;
4821
4886
  const index = parent.cssRules.length;
@@ -5123,6 +5188,27 @@
5123
5188
  return {render, destroy, commands};
5124
5189
  }
5125
5190
 
5191
+ const hostsBreakingOnStylePosition = ["www.diffusioneshop.com", "zhale.me"];
5192
+ const mode = hostsBreakingOnStylePosition.includes(location.hostname)
5193
+ ? "away"
5194
+ : "next";
5195
+ function getStyleInjectionMode() {
5196
+ return mode;
5197
+ }
5198
+ function injectStyleAway(styleElement) {
5199
+ let container = document.body.querySelector(
5200
+ ".darkreader-style-container"
5201
+ );
5202
+ if (!container) {
5203
+ container = document.createElement("div");
5204
+ container.classList.add("darkreader");
5205
+ container.classList.add("darkreader-style-container");
5206
+ container.style.display = "none";
5207
+ document.body.append(container);
5208
+ }
5209
+ container.append(styleElement);
5210
+ }
5211
+
5126
5212
  const overrides = {
5127
5213
  "background-color": {
5128
5214
  customProp: "--darkreader-inline-bgcolor",
@@ -5923,22 +6009,29 @@
5923
6009
  rejectorsForLoadingLinks.clear();
5924
6010
  }
5925
6011
  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;
6012
+ const inMode = getStyleInjectionMode();
6013
+ let corsCopy = null;
6014
+ let syncStyle = null;
6015
+ if (inMode === "next") {
6016
+ const prevStyles = [];
6017
+ let next = element;
6018
+ while (
6019
+ (next = next.nextElementSibling) &&
6020
+ next.matches(".darkreader")
6021
+ ) {
6022
+ prevStyles.push(next);
6023
+ }
6024
+ corsCopy =
6025
+ prevStyles.find(
6026
+ (el) =>
6027
+ el.matches(".darkreader--cors") && !corsStyleSet.has(el)
6028
+ ) || null;
6029
+ syncStyle =
6030
+ prevStyles.find(
6031
+ (el) =>
6032
+ el.matches(".darkreader--sync") && !syncStyleSet.has(el)
6033
+ ) || null;
6034
+ }
5942
6035
  let corsCopyPositionWatcher = null;
5943
6036
  let syncStylePositionWatcher = null;
5944
6037
  let cancelAsyncOperations = false;
@@ -6023,21 +6116,31 @@
6023
6116
  return cssRules;
6024
6117
  }
6025
6118
  function insertStyle() {
6026
- if (corsCopy) {
6027
- if (element.nextSibling !== corsCopy) {
6119
+ if (inMode === "next") {
6120
+ if (corsCopy) {
6121
+ if (element.nextSibling !== corsCopy) {
6122
+ element.parentNode.insertBefore(
6123
+ corsCopy,
6124
+ element.nextSibling
6125
+ );
6126
+ }
6127
+ if (corsCopy.nextSibling !== syncStyle) {
6128
+ element.parentNode.insertBefore(
6129
+ syncStyle,
6130
+ corsCopy.nextSibling
6131
+ );
6132
+ }
6133
+ } else if (element.nextSibling !== syncStyle) {
6028
6134
  element.parentNode.insertBefore(
6029
- corsCopy,
6135
+ syncStyle,
6030
6136
  element.nextSibling
6031
6137
  );
6032
6138
  }
6033
- if (corsCopy.nextSibling !== syncStyle) {
6034
- element.parentNode.insertBefore(
6035
- syncStyle,
6036
- corsCopy.nextSibling
6037
- );
6139
+ } else if (inMode === "away") {
6140
+ if (corsCopy && !corsCopy.parentNode) {
6141
+ injectStyleAway(corsCopy);
6038
6142
  }
6039
- } else if (element.nextSibling !== syncStyle) {
6040
- element.parentNode.insertBefore(syncStyle, element.nextSibling);
6143
+ injectStyleAway(syncStyle);
6041
6144
  }
6042
6145
  }
6043
6146
  function createSyncStyle() {
@@ -6095,7 +6198,12 @@
6095
6198
  return cssRules;
6096
6199
  }
6097
6200
  }
6098
- cssText = await loadText(element.href);
6201
+ try {
6202
+ cssText = await loadText(element.href);
6203
+ } catch (err) {
6204
+ logWarn(err);
6205
+ cssText = "";
6206
+ }
6099
6207
  cssBasePath = getCSSBaseBath(element.href);
6100
6208
  if (cancelAsyncOperations) {
6101
6209
  return null;
@@ -6127,12 +6235,31 @@
6127
6235
  corsCopy.textContent = fullCSSText;
6128
6236
  }
6129
6237
  } else {
6130
- corsCopy = createCORSCopy(element, fullCSSText);
6238
+ corsCopy = createCORSCopy(
6239
+ fullCSSText,
6240
+ inMode === "next"
6241
+ ? (cc) =>
6242
+ element.parentNode.insertBefore(
6243
+ cc,
6244
+ element.nextSibling
6245
+ )
6246
+ : injectStyleAway
6247
+ );
6248
+ if (corsCopy) {
6249
+ if (inMode === "next") {
6250
+ element.parentNode.insertBefore(
6251
+ corsCopy,
6252
+ element.nextSibling
6253
+ );
6254
+ } else if (inMode === "away") {
6255
+ injectStyleAway(corsCopy);
6256
+ }
6257
+ }
6131
6258
  }
6132
6259
  } catch (err) {
6133
6260
  logWarn(err);
6134
6261
  }
6135
- if (corsCopy) {
6262
+ if (corsCopy && inMode === "next") {
6136
6263
  corsCopyPositionWatcher = watchForNodePosition(
6137
6264
  corsCopy,
6138
6265
  "prev-sibling"
@@ -6199,7 +6326,7 @@
6199
6326
  removeCSSRulesFromSheet(sheet);
6200
6327
  if (syncStylePositionWatcher) {
6201
6328
  syncStylePositionWatcher.run();
6202
- } else {
6329
+ } else if (inMode === "next") {
6203
6330
  syncStylePositionWatcher = watchForNodePosition(
6204
6331
  syncStyle,
6205
6332
  "prev-sibling",
@@ -6422,7 +6549,7 @@
6422
6549
  cssText = cssText.trim();
6423
6550
  return cssText;
6424
6551
  }
6425
- function createCORSCopy(srcElement, cssText) {
6552
+ function createCORSCopy(cssText, inject) {
6426
6553
  if (!cssText) {
6427
6554
  return null;
6428
6555
  }
@@ -6431,7 +6558,7 @@
6431
6558
  cors.classList.add("darkreader--cors");
6432
6559
  cors.media = "screen";
6433
6560
  cors.textContent = cssText;
6434
- srcElement.parentNode.insertBefore(cors, srcElement.nextSibling);
6561
+ inject(cors);
6435
6562
  cors.sheet.disabled = true;
6436
6563
  corsStyleSet.add(cors);
6437
6564
  return cors;
@@ -6675,7 +6802,7 @@
6675
6802
  );
6676
6803
  }
6677
6804
  let blobURLAllowed = null;
6678
- async function checkBlobURLSupport() {
6805
+ function checkBlobURLSupport() {
6679
6806
  if (blobURLAllowed != null) {
6680
6807
  document.dispatchEvent(
6681
6808
  new CustomEvent("__darkreader__blobURLCheckResponse", {
@@ -6692,17 +6819,18 @@
6692
6819
  }
6693
6820
  const blob = new Blob([bytes], {type: "image/svg+xml"});
6694
6821
  const objectURL = URL.createObjectURL(blob);
6695
- try {
6696
- const image = new Image();
6697
- await new Promise((resolve, reject) => {
6698
- image.onload = () => resolve();
6699
- image.onerror = () => reject();
6700
- image.src = objectURL;
6701
- });
6822
+ const image = new Image();
6823
+ image.onload = () => {
6702
6824
  blobURLAllowed = true;
6703
- } catch (err) {
6825
+ sendBlobURLCheckResponse();
6826
+ };
6827
+ image.onerror = () => {
6704
6828
  blobURLAllowed = false;
6705
- }
6829
+ sendBlobURLCheckResponse();
6830
+ };
6831
+ image.src = objectURL;
6832
+ }
6833
+ function sendBlobURLCheckResponse() {
6706
6834
  document.dispatchEvent(
6707
6835
  new CustomEvent("__darkreader__blobURLCheckResponse", {
6708
6836
  detail: {blobURLAllowed}
@@ -7233,20 +7361,24 @@
7233
7361
  let isIFrame$1 = null;
7234
7362
  let ignoredImageAnalysisSelectors = [];
7235
7363
  let ignoredInlineSelectors = [];
7236
- const staticStyleMap = new Map();
7364
+ let staticStyleMap = new WeakMap();
7237
7365
  function createOrUpdateStyle(className, root = document.head || document) {
7238
7366
  let element = root.querySelector(`.${className}`);
7367
+ if (!staticStyleMap.has(root)) {
7368
+ staticStyleMap.set(root, new Map());
7369
+ }
7370
+ const classMap = staticStyleMap.get(root);
7239
7371
  if (element) {
7240
- staticStyleMap.set(className, element);
7241
- } else if (staticStyleMap.has(className)) {
7242
- element = staticStyleMap.get(className);
7372
+ classMap.set(className, element);
7373
+ } else if (classMap.has(className)) {
7374
+ element = classMap.get(className);
7243
7375
  } else {
7244
7376
  element = document.createElement("style");
7245
7377
  element.classList.add("darkreader");
7246
7378
  element.classList.add(className);
7247
7379
  element.media = "screen";
7248
7380
  element.textContent = "";
7249
- staticStyleMap.set(className, element);
7381
+ classMap.set(className, element);
7250
7382
  }
7251
7383
  return element;
7252
7384
  }
@@ -7272,6 +7404,18 @@
7272
7404
  forEach(nodePositionWatchers.values(), (watcher) => watcher.stop());
7273
7405
  nodePositionWatchers.clear();
7274
7406
  }
7407
+ function injectStaticStyle(style, prevNode, watchAlias, callback) {
7408
+ const mode = getStyleInjectionMode();
7409
+ if (mode === "next") {
7410
+ document.head.insertBefore(
7411
+ style,
7412
+ prevNode ? prevNode.nextSibling : document.head.firstChild
7413
+ );
7414
+ setupNodePositionWatcher(style, watchAlias, callback);
7415
+ } else if (mode === "away") {
7416
+ injectStyleAway(style);
7417
+ }
7418
+ }
7275
7419
  function createStaticStyleOverrides() {
7276
7420
  const fallbackStyle = createOrUpdateStyle(
7277
7421
  "darkreader--fallback",
@@ -7280,24 +7424,21 @@
7280
7424
  fallbackStyle.textContent = getModifiedFallbackStyle(theme, {
7281
7425
  strict: true
7282
7426
  });
7283
- document.head.insertBefore(fallbackStyle, document.head.firstChild);
7284
- setupNodePositionWatcher(fallbackStyle, "fallback");
7427
+ injectStaticStyle(fallbackStyle, null, "fallback");
7285
7428
  const userAgentStyle = createOrUpdateStyle("darkreader--user-agent");
7286
7429
  userAgentStyle.textContent = getModifiedUserAgentStyle(
7287
7430
  theme,
7288
7431
  isIFrame$1,
7289
7432
  theme.styleSystemControls
7290
7433
  );
7291
- document.head.insertBefore(userAgentStyle, fallbackStyle.nextSibling);
7292
- setupNodePositionWatcher(userAgentStyle, "user-agent");
7434
+ injectStaticStyle(userAgentStyle, fallbackStyle, "user-agent");
7293
7435
  const textStyle = createOrUpdateStyle("darkreader--text");
7294
7436
  if (theme.useFont || theme.textStroke > 0) {
7295
7437
  textStyle.textContent = createTextStyle(theme);
7296
7438
  } else {
7297
7439
  textStyle.textContent = "";
7298
7440
  }
7299
- document.head.insertBefore(textStyle, fallbackStyle.nextSibling);
7300
- setupNodePositionWatcher(textStyle, "text");
7441
+ injectStaticStyle(textStyle, userAgentStyle, "text");
7301
7442
  const invertStyle = createOrUpdateStyle("darkreader--invert");
7302
7443
  if (fixes && Array.isArray(fixes.invert) && fixes.invert.length > 0) {
7303
7444
  invertStyle.textContent = [
@@ -7314,17 +7455,10 @@
7314
7455
  } else {
7315
7456
  invertStyle.textContent = "";
7316
7457
  }
7317
- document.head.insertBefore(invertStyle, textStyle.nextSibling);
7318
- setupNodePositionWatcher(invertStyle, "invert");
7458
+ injectStaticStyle(invertStyle, textStyle, "invert");
7319
7459
  const inlineStyle = createOrUpdateStyle("darkreader--inline");
7320
7460
  inlineStyle.textContent = getInlineOverrideStyle();
7321
- document.head.insertBefore(inlineStyle, invertStyle.nextSibling);
7322
- setupNodePositionWatcher(inlineStyle, "inline");
7323
- const overrideStyle = createOrUpdateStyle("darkreader--override");
7324
- overrideStyle.textContent =
7325
- fixes && fixes.css ? replaceCSSTemplates(fixes.css) : "";
7326
- document.head.appendChild(overrideStyle);
7327
- setupNodePositionWatcher(overrideStyle, "override");
7461
+ injectStaticStyle(inlineStyle, invertStyle, "inline");
7328
7462
  const variableStyle = createOrUpdateStyle("darkreader--variables");
7329
7463
  const selectionColors = theme?.selectionColor
7330
7464
  ? getSelectionColor(theme)
@@ -7345,13 +7479,12 @@
7345
7479
  ` --darkreader-selection-text: ${selectionColors?.foregroundColorSelection ?? "initial"};`,
7346
7480
  `}`
7347
7481
  ].join("\n");
7348
- document.head.insertBefore(variableStyle, inlineStyle.nextSibling);
7349
- setupNodePositionWatcher(variableStyle, "variables", () =>
7482
+ injectStaticStyle(variableStyle, inlineStyle, "variables", () =>
7350
7483
  registerVariablesSheet(variableStyle.sheet)
7351
7484
  );
7352
7485
  registerVariablesSheet(variableStyle.sheet);
7353
7486
  const rootVarsStyle = createOrUpdateStyle("darkreader--root-vars");
7354
- document.head.insertBefore(rootVarsStyle, variableStyle.nextSibling);
7487
+ injectStaticStyle(rootVarsStyle, variableStyle, "root-vars");
7355
7488
  const enableStyleSheetsProxy = !(
7356
7489
  fixes && fixes.disableStyleSheetsProxy
7357
7490
  );
@@ -7367,6 +7500,10 @@
7367
7500
  document.head.insertBefore(proxyScript, rootVarsStyle.nextSibling);
7368
7501
  proxyScript.remove();
7369
7502
  }
7503
+ const overrideStyle = createOrUpdateStyle("darkreader--override");
7504
+ overrideStyle.textContent =
7505
+ fixes && fixes.css ? replaceCSSTemplates(fixes.css) : "";
7506
+ injectStaticStyle(overrideStyle, document.head.lastChild, "override");
7370
7507
  }
7371
7508
  const shadowRootsWithOverrides = new Set();
7372
7509
  function createShadowStaticStyleOverridesInner(root) {
@@ -7442,7 +7579,8 @@
7442
7579
  }
7443
7580
  function cleanFallbackStyle() {
7444
7581
  const fallback =
7445
- staticStyleMap.get("darkreader--fallback") ||
7582
+ staticStyleMap.get(document.head)?.get("darkreader--fallback") ||
7583
+ staticStyleMap.get(document)?.get("darkreader--fallback") ||
7446
7584
  document.querySelector(".darkreader--fallback");
7447
7585
  if (fallback) {
7448
7586
  fallback.textContent = "";
@@ -7991,7 +8129,7 @@
7991
8129
  selectors.forEach((selector) =>
7992
8130
  removeNode(document.head.querySelector(selector))
7993
8131
  );
7994
- staticStyleMap.clear();
8132
+ staticStyleMap = new WeakMap();
7995
8133
  removeProxy();
7996
8134
  }
7997
8135
  shadowRootsWithOverrides.forEach((root) => {