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.mjs 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
 
@@ -29,6 +29,8 @@ var MessageTypeUItoBG;
29
29
  "ui-bg-apply-dev-static-themes";
30
30
  MessageTypeUItoBG["RESET_DEV_STATIC_THEMES"] =
31
31
  "ui-bg-reset-dev-static-themes";
32
+ MessageTypeUItoBG["START_ACTIVATION"] = "ui-bg-start-activation";
33
+ MessageTypeUItoBG["RESET_ACTIVATION"] = "ui-bg-reset-activation";
32
34
  MessageTypeUItoBG["COLOR_SCHEME_CHANGE"] = "ui-bg-color-scheme-change";
33
35
  MessageTypeUItoBG["HIDE_HIGHLIGHTS"] = "ui-bg-hide-highlights";
34
36
  })(MessageTypeUItoBG || (MessageTypeUItoBG = {}));
@@ -347,6 +349,7 @@ const DEFAULT_THEME = {
347
349
  darkColorScheme: "Default",
348
350
  immediateModify: false
349
351
  };
352
+ if (__PLUS__);
350
353
  const filterModeSites = [
351
354
  "*.officeapps.live.com",
352
355
  "*.sharepoint.com",
@@ -355,9 +358,7 @@ const filterModeSites = [
355
358
  ];
356
359
  ({
357
360
  customThemes: filterModeSites.map((url) => {
358
- const engine = isChromium
359
- ? ThemeEngine.svgFilter
360
- : ThemeEngine.cssFilter;
361
+ const engine = ThemeEngine.cssFilter;
361
362
  return {
362
363
  url: [url],
363
364
  theme: {...DEFAULT_THEME, engine},
@@ -956,10 +957,16 @@ const supportedColorFuncs = [
956
957
  function parse($color) {
957
958
  const c = $color.trim().toLowerCase();
958
959
  if (c.includes("(from ")) {
960
+ if (c.indexOf("(from") !== c.lastIndexOf("(from")) {
961
+ return null;
962
+ }
959
963
  return domParseColor(c);
960
964
  }
961
965
  if (c.match(rgbMatch)) {
962
966
  if (c.startsWith("rgb(#") || c.startsWith("rgba(#")) {
967
+ if (c.lastIndexOf("rgb") > 0) {
968
+ return null;
969
+ }
963
970
  return domParseColor(c);
964
971
  }
965
972
  return parseRGB(c);
@@ -982,7 +989,10 @@ function parse($color) {
982
989
  if (
983
990
  c.endsWith(")") &&
984
991
  supportedColorFuncs.some(
985
- (fn) => c.startsWith(fn) && c[fn.length] === "("
992
+ (fn) =>
993
+ c.startsWith(fn) &&
994
+ c[fn.length] === "(" &&
995
+ c.lastIndexOf(fn) === 0
986
996
  )
987
997
  ) {
988
998
  return domParseColor(c);
@@ -1798,6 +1808,9 @@ function iterateCSSRules(rules, iterate, onImportError) {
1798
1808
  forEach(rules, (rule) => {
1799
1809
  if (isStyleRule(rule)) {
1800
1810
  iterate(rule);
1811
+ if (rule.cssRules?.length > 0) {
1812
+ iterateCSSRules(rule.cssRules, iterate);
1813
+ }
1801
1814
  } else if (isImportRule(rule)) {
1802
1815
  try {
1803
1816
  iterateCSSRules(
@@ -2055,817 +2068,868 @@ function getSheetScope(sheet) {
2055
2068
  return null;
2056
2069
  }
2057
2070
 
2058
- const gradientLength = "gradient".length;
2059
- const conicGradient = "conic-";
2060
- const conicGradientLength = conicGradient.length;
2061
- const radialGradient = "radial-";
2062
- const linearGradient = "linear-";
2063
- function parseGradient(value) {
2064
- const result = [];
2065
- let index = 0;
2066
- let startIndex = conicGradient.length;
2067
- while ((index = value.indexOf("gradient", startIndex)) !== -1) {
2068
- let typeGradient;
2069
- [linearGradient, radialGradient, conicGradient].find((possibleType) => {
2070
- if (index - possibleType.length >= 0) {
2071
- const possibleGradient = value.substring(
2072
- index - possibleType.length,
2073
- index
2074
- );
2075
- if (possibleGradient === possibleType) {
2076
- if (
2077
- value.slice(
2078
- index - possibleType.length - 10,
2079
- index - possibleType.length - 1
2080
- ) === "repeating"
2081
- ) {
2082
- typeGradient = `repeating-${possibleType}gradient`;
2083
- return true;
2084
- }
2085
- if (
2086
- value.slice(
2087
- index - possibleType.length - 8,
2088
- index - possibleType.length - 1
2089
- ) === "-webkit"
2090
- ) {
2091
- typeGradient = `-webkit-${possibleType}gradient`;
2092
- return true;
2093
- }
2094
- typeGradient = `${possibleType}gradient`;
2095
- return true;
2096
- }
2097
- }
2098
- });
2099
- if (!typeGradient) {
2100
- break;
2101
- }
2102
- const {start, end} = getParenthesesRange(value, index + gradientLength);
2103
- const match = value.substring(start + 1, end - 1);
2104
- startIndex = end + 1 + conicGradientLength;
2105
- result.push({
2106
- typeGradient,
2107
- match,
2108
- offset: typeGradient.length + 2,
2109
- index: index - typeGradient.length + gradientLength,
2110
- hasComma: true
2111
- });
2112
- }
2113
- if (result.length) {
2114
- result[result.length - 1].hasComma = false;
2115
- }
2116
- return result;
2071
+ function getBackgroundPoles(_theme) {
2072
+ return ["", ""];
2117
2073
  }
2118
-
2119
- const STORAGE_KEY_IMAGE_DETAILS_LIST = "__darkreader__imageDetails_v2_list";
2120
- const STORAGE_KEY_IMAGE_DETAILS_PREFIX = "__darkreader__imageDetails_v2_";
2121
- const STORAGE_KEY_CSS_FETCH_PREFIX = "__darkreader__cssFetch_";
2122
- let imageCacheTimeout = 0;
2123
- const imageDetailsCacheQueue = new Map();
2124
- const cachedImageUrls = [];
2125
- function writeImageDetailsQueue() {
2126
- imageDetailsCacheQueue.forEach((details, url) => {
2127
- if (url && url.startsWith("https://")) {
2128
- try {
2129
- const json = JSON.stringify(details);
2130
- sessionStorage.setItem(
2131
- `${STORAGE_KEY_IMAGE_DETAILS_PREFIX}${url}`,
2132
- json
2133
- );
2134
- cachedImageUrls.push(url);
2135
- } catch (err) {}
2136
- }
2137
- });
2138
- imageDetailsCacheQueue.clear();
2139
- sessionStorage.setItem(
2140
- STORAGE_KEY_IMAGE_DETAILS_LIST,
2141
- JSON.stringify(cachedImageUrls)
2142
- );
2074
+ function getTextPoles(_theme) {
2075
+ return ["", ""];
2143
2076
  }
2144
- function writeImageDetailsCache(url, imageDetails) {
2145
- if (!url || !url.startsWith("https://")) {
2146
- return;
2147
- }
2148
- imageDetailsCacheQueue.set(url, imageDetails);
2149
- clearTimeout(imageCacheTimeout);
2150
- imageCacheTimeout = setTimeout(writeImageDetailsQueue, 1000);
2077
+ function modifyBgColorExtended({h, s, l, a}, _pole1, _pole2) {
2078
+ return {h, s, l, a};
2151
2079
  }
2152
- function readImageDetailsCache(targetMap) {
2153
- try {
2154
- const jsonList = sessionStorage.getItem(STORAGE_KEY_IMAGE_DETAILS_LIST);
2155
- if (!jsonList) {
2156
- return;
2157
- }
2158
- const list = JSON.parse(jsonList);
2159
- list.forEach((url) => {
2160
- const json = sessionStorage.getItem(
2161
- `${STORAGE_KEY_IMAGE_DETAILS_PREFIX}${url}`
2162
- );
2163
- if (json) {
2164
- const details = JSON.parse(json);
2165
- targetMap.set(url, details);
2080
+ function modifyFgColorExtended({h, s, l, a}, _pole1, _pole2) {
2081
+ return {h, s, l, a};
2082
+ }
2083
+ function modifyLightSchemeColorExtended({h, s, l, a}, _pole1, _pole2) {
2084
+ return {h, s, l, a};
2085
+ }
2086
+
2087
+ let variablesSheet;
2088
+ const registeredColors = new Map();
2089
+ function registerVariablesSheet(sheet) {
2090
+ variablesSheet = sheet;
2091
+ const types = ["background", "text", "border"];
2092
+ registeredColors.forEach((registered) => {
2093
+ types.forEach((type) => {
2094
+ if (registered[type]) {
2095
+ const {variable, value} = registered[type];
2096
+ variablesSheet?.cssRules[0].style.setProperty(variable, value);
2166
2097
  }
2167
2098
  });
2168
- } catch (err) {}
2169
- }
2170
- function writeCSSFetchCache(url, cssText) {
2171
- const key = `${STORAGE_KEY_CSS_FETCH_PREFIX}${url}`;
2172
- try {
2173
- sessionStorage.setItem(key, cssText);
2174
- } catch (err) {}
2099
+ });
2175
2100
  }
2176
- function readCSSFetchCache(url) {
2177
- const key = `${STORAGE_KEY_CSS_FETCH_PREFIX}${url}`;
2178
- try {
2179
- return sessionStorage.getItem(key) ?? null;
2180
- } catch (err) {}
2181
- return null;
2101
+ function releaseVariablesSheet() {
2102
+ variablesSheet = null;
2103
+ clearColorPalette();
2182
2104
  }
2183
-
2184
- function toSVGMatrix(matrix) {
2185
- return matrix
2186
- .slice(0, 4)
2187
- .map((m) => m.map((m) => m.toFixed(3)).join(" "))
2188
- .join(" ");
2105
+ function getRegisteredVariableValue(type, registered) {
2106
+ return `var(${registered[type].variable}, ${registered[type].value})`;
2189
2107
  }
2190
- function getSVGFilterMatrixValue(config) {
2191
- return toSVGMatrix(createFilterMatrix(config));
2108
+ function getRegisteredColor(type, parsed) {
2109
+ const hex = rgbToHexString(parsed);
2110
+ const registered = registeredColors.get(hex);
2111
+ if (registered?.[type]) {
2112
+ return getRegisteredVariableValue(type, registered);
2113
+ }
2114
+ return null;
2192
2115
  }
2193
-
2194
- const MAX_FRAME_DURATION = 1000 / 60;
2195
- class AsyncQueue {
2196
- constructor() {
2197
- this.queue = [];
2198
- this.timerId = null;
2116
+ function registerColor(type, parsed, value) {
2117
+ const hex = rgbToHexString(parsed);
2118
+ let registered;
2119
+ if (registeredColors.has(hex)) {
2120
+ registered = registeredColors.get(hex);
2121
+ } else {
2122
+ const parsed = parseColorWithCache(hex);
2123
+ registered = {parsed};
2124
+ registeredColors.set(hex, registered);
2199
2125
  }
2200
- addTask(task) {
2201
- this.queue.push(task);
2202
- this.scheduleFrame();
2126
+ const variable = `--darkreader-${type}-${hex.replace("#", "")}`;
2127
+ registered[type] = {variable, value};
2128
+ if (variablesSheet?.cssRules[0]?.style) {
2129
+ variablesSheet?.cssRules[0].style.setProperty(variable, value);
2203
2130
  }
2204
- stop() {
2205
- if (this.timerId !== null) {
2206
- cancelAnimationFrame(this.timerId);
2207
- this.timerId = null;
2131
+ return getRegisteredVariableValue(type, registered);
2132
+ }
2133
+ function getColorPalette() {
2134
+ const background = [];
2135
+ const border = [];
2136
+ const text = [];
2137
+ registeredColors.forEach((registered) => {
2138
+ if (registered.background) {
2139
+ background.push(registered.parsed);
2208
2140
  }
2209
- this.queue = [];
2210
- }
2211
- scheduleFrame() {
2212
- if (this.timerId) {
2213
- return;
2141
+ if (registered.border) {
2142
+ border.push(registered.parsed);
2143
+ }
2144
+ if (registered.text) {
2145
+ text.push(registered.parsed);
2214
2146
  }
2215
- this.timerId = requestAnimationFrame(() => {
2216
- this.timerId = null;
2217
- const start = Date.now();
2218
- let cb;
2219
- while ((cb = this.queue.shift())) {
2220
- cb();
2221
- if (Date.now() - start >= MAX_FRAME_DURATION) {
2222
- this.scheduleFrame();
2223
- break;
2224
- }
2225
- }
2226
- });
2227
- }
2228
- }
2229
-
2230
- const resolvers$1 = new Map();
2231
- const rejectors = new Map();
2232
- async function bgFetch(request) {
2233
- if (window.DarkReader?.Plugins?.fetch) {
2234
- return window.DarkReader.Plugins.fetch(request);
2235
- }
2236
- return new Promise((resolve, reject) => {
2237
- const id = generateUID();
2238
- resolvers$1.set(id, resolve);
2239
- rejectors.set(id, reject);
2240
- chrome.runtime.sendMessage({
2241
- type: MessageTypeCStoBG.FETCH,
2242
- data: request,
2243
- id
2244
- });
2245
2147
  });
2148
+ return {background, border, text};
2149
+ }
2150
+ function clearColorPalette() {
2151
+ registeredColors.clear();
2246
2152
  }
2247
- chrome.runtime.onMessage.addListener(({type, data, error, id}) => {
2248
- if (type === MessageTypeBGtoCS.FETCH_RESPONSE) {
2249
- const resolve = resolvers$1.get(id);
2250
- const reject = rejectors.get(id);
2251
- resolvers$1.delete(id);
2252
- rejectors.delete(id);
2253
- if (error) {
2254
- reject && reject(error);
2255
- } else {
2256
- resolve && resolve(data);
2257
- }
2258
- }
2259
- });
2260
2153
 
2261
- const imageManager = new AsyncQueue();
2262
- async function getImageDetails(url) {
2263
- return new Promise(async (resolve, reject) => {
2264
- try {
2265
- const dataURL = url.startsWith("data:")
2266
- ? url
2267
- : await getDataURL(url);
2268
- const blob =
2269
- tryConvertDataURLToBlobSync(dataURL) ?? (await loadAsBlob(url));
2270
- let image;
2271
- if (dataURL.startsWith("data:image/svg+xml")) {
2272
- image = await loadImage(dataURL);
2273
- } else {
2274
- image =
2275
- (await tryCreateImageBitmap(blob)) ??
2276
- (await loadImage(dataURL));
2277
- }
2278
- imageManager.addTask(() => {
2279
- const analysis = analyzeImage(image);
2280
- resolve({
2281
- src: url,
2282
- dataURL: analysis.isLarge ? "" : dataURL,
2283
- width: image.width,
2284
- height: image.height,
2285
- ...analysis
2286
- });
2287
- });
2288
- } catch (error) {
2289
- reject(error);
2290
- }
2154
+ function getBgPole(theme) {
2155
+ const isDarkScheme = theme.mode === 1;
2156
+ const prop = isDarkScheme
2157
+ ? "darkSchemeBackgroundColor"
2158
+ : "lightSchemeBackgroundColor";
2159
+ return theme[prop];
2160
+ }
2161
+ function getFgPole(theme) {
2162
+ const isDarkScheme = theme.mode === 1;
2163
+ const prop = isDarkScheme ? "darkSchemeTextColor" : "lightSchemeTextColor";
2164
+ return theme[prop];
2165
+ }
2166
+ const colorModificationCache = new Map();
2167
+ function clearColorModificationCache() {
2168
+ colorModificationCache.clear();
2169
+ }
2170
+ const rgbCacheKeys = ["r", "g", "b", "a"];
2171
+ const themeCacheKeys = [
2172
+ "mode",
2173
+ "brightness",
2174
+ "contrast",
2175
+ "grayscale",
2176
+ "sepia",
2177
+ "darkSchemeBackgroundColor",
2178
+ "darkSchemeTextColor",
2179
+ "lightSchemeBackgroundColor",
2180
+ "lightSchemeTextColor"
2181
+ ];
2182
+ function getCacheId(rgb, theme) {
2183
+ let resultId = "";
2184
+ rgbCacheKeys.forEach((key) => {
2185
+ resultId += `${rgb[key]};`;
2291
2186
  });
2187
+ themeCacheKeys.forEach((key) => {
2188
+ resultId += `${theme[key]};`;
2189
+ });
2190
+ return resultId;
2292
2191
  }
2293
- async function getDataURL(url) {
2294
- const parsedURL = new URL(url);
2295
- if (parsedURL.origin === location.origin) {
2296
- return await loadAsDataURL(url);
2192
+ function modifyColorWithCache(
2193
+ rgb,
2194
+ theme,
2195
+ modifyHSL,
2196
+ poleColor,
2197
+ anotherPoleColor
2198
+ ) {
2199
+ let fnCache;
2200
+ if (colorModificationCache.has(modifyHSL)) {
2201
+ fnCache = colorModificationCache.get(modifyHSL);
2202
+ } else {
2203
+ fnCache = new Map();
2204
+ colorModificationCache.set(modifyHSL, fnCache);
2297
2205
  }
2298
- return await bgFetch({url, responseType: "data-url"});
2206
+ const id = getCacheId(rgb, theme);
2207
+ if (fnCache.has(id)) {
2208
+ return fnCache.get(id);
2209
+ }
2210
+ const hsl = rgbToHSL(rgb);
2211
+ const pole = poleColor == null ? null : parseToHSLWithCache(poleColor);
2212
+ const anotherPole =
2213
+ anotherPoleColor == null ? null : parseToHSLWithCache(anotherPoleColor);
2214
+ const modified = modifyHSL(hsl, pole, anotherPole);
2215
+ const {r, g, b, a} = hslToRGB(modified);
2216
+ const matrix = createFilterMatrix({...theme, mode: 0});
2217
+ const [rf, gf, bf] = applyColorMatrix([r, g, b], matrix);
2218
+ const color =
2219
+ a === 1
2220
+ ? rgbToHexString({r: rf, g: gf, b: bf})
2221
+ : rgbToString({r: rf, g: gf, b: bf, a});
2222
+ fnCache.set(id, color);
2223
+ return color;
2299
2224
  }
2300
- async function tryCreateImageBitmap(blob) {
2301
- try {
2302
- return await createImageBitmap(blob);
2303
- } catch (err) {
2304
- logWarn(
2305
- `Unable to create image bitmap for type ${blob.type}: ${String(err)}`
2306
- );
2307
- return null;
2225
+ function modifyAndRegisterColor(type, rgb, theme, modifier) {
2226
+ const registered = getRegisteredColor(type, rgb);
2227
+ if (registered) {
2228
+ return registered;
2308
2229
  }
2230
+ const value = modifier(rgb, theme);
2231
+ return registerColor(type, rgb, value);
2309
2232
  }
2310
- const INCOMPLETE_DOC_LOADING_IMAGE_LIMIT = 256;
2311
- let loadingImagesCount = 0;
2312
- async function loadImage(url) {
2313
- return new Promise((resolve, reject) => {
2314
- const image = new Image();
2315
- image.onload = () => resolve(image);
2316
- image.onerror = () => reject(`Unable to load image ${url}`);
2317
- if (
2318
- ++loadingImagesCount <= INCOMPLETE_DOC_LOADING_IMAGE_LIMIT ||
2319
- isReadyStateComplete()
2320
- ) {
2321
- image.src = url;
2233
+ function modifyLightSchemeColor(rgb, theme) {
2234
+ const poleBg = getBgPole(theme);
2235
+ const poleFg = getFgPole(theme);
2236
+ return modifyColorWithCache(rgb, theme, modifyLightModeHSL, poleFg, poleBg);
2237
+ }
2238
+ function modifyLightModeHSL({h, s, l, a}, poleFg, poleBg) {
2239
+ const isDark = l < 0.5;
2240
+ let isNeutral;
2241
+ if (isDark) {
2242
+ isNeutral = l < 0.2 || s < 0.12;
2243
+ } else {
2244
+ const isBlue = h > 200 && h < 280;
2245
+ isNeutral = s < 0.24 || (l > 0.8 && isBlue);
2246
+ }
2247
+ let hx = h;
2248
+ let sx = s;
2249
+ if (isNeutral) {
2250
+ if (isDark) {
2251
+ hx = poleFg.h;
2252
+ sx = poleFg.s;
2322
2253
  } else {
2323
- addReadyStateCompleteListener(() => (image.src = url));
2254
+ hx = poleBg.h;
2255
+ sx = poleBg.s;
2324
2256
  }
2325
- });
2257
+ }
2258
+ const lx = scale(l, 0, 1, poleFg.l, poleBg.l);
2259
+ return {h: hx, s: sx, l: lx, a};
2326
2260
  }
2327
- const MAX_ANALYSIS_PIXELS_COUNT = 32 * 32;
2328
- let canvas;
2329
- let context;
2330
- function createCanvas() {
2331
- const maxWidth = MAX_ANALYSIS_PIXELS_COUNT;
2332
- const maxHeight = MAX_ANALYSIS_PIXELS_COUNT;
2333
- canvas = document.createElement("canvas");
2334
- canvas.width = maxWidth;
2335
- canvas.height = maxHeight;
2336
- context = canvas.getContext("2d", {willReadFrequently: true});
2337
- context.imageSmoothingEnabled = false;
2261
+ const MAX_BG_LIGHTNESS = 0.4;
2262
+ function modifyBgHSL({h, s, l, a}, pole) {
2263
+ const isDark = l < 0.5;
2264
+ const isBlue = h > 200 && h < 280;
2265
+ const isNeutral = s < 0.12 || (l > 0.8 && isBlue);
2266
+ if (isDark) {
2267
+ const lx = scale(l, 0, 0.5, 0, MAX_BG_LIGHTNESS);
2268
+ if (isNeutral) {
2269
+ const hx = pole.h;
2270
+ const sx = pole.s;
2271
+ return {h: hx, s: sx, l: lx, a};
2272
+ }
2273
+ return {h, s, l: lx, a};
2274
+ }
2275
+ let lx = scale(l, 0.5, 1, MAX_BG_LIGHTNESS, pole.l);
2276
+ if (isNeutral) {
2277
+ const hx = pole.h;
2278
+ const sx = pole.s;
2279
+ return {h: hx, s: sx, l: lx, a};
2280
+ }
2281
+ let hx = h;
2282
+ const isYellow = h > 60 && h < 180;
2283
+ if (isYellow) {
2284
+ const isCloserToGreen = h > 120;
2285
+ if (isCloserToGreen) {
2286
+ hx = scale(h, 120, 180, 135, 180);
2287
+ } else {
2288
+ hx = scale(h, 60, 120, 60, 105);
2289
+ }
2290
+ }
2291
+ if (hx > 40 && hx < 80) {
2292
+ lx *= 0.75;
2293
+ }
2294
+ return {h: hx, s, l: lx, a};
2338
2295
  }
2339
- function removeCanvas() {
2340
- canvas = null;
2341
- context = null;
2296
+ function _modifyBackgroundColor(rgb, theme) {
2297
+ if (theme.mode === 0) {
2298
+ if (__PLUS__) {
2299
+ const poles = getBackgroundPoles();
2300
+ return modifyColorWithCache(
2301
+ rgb,
2302
+ theme,
2303
+ modifyLightSchemeColorExtended,
2304
+ poles[0],
2305
+ poles[1]
2306
+ );
2307
+ }
2308
+ return modifyLightSchemeColor(rgb, theme);
2309
+ }
2310
+ if (__PLUS__) {
2311
+ const poles = getBackgroundPoles();
2312
+ return modifyColorWithCache(
2313
+ rgb,
2314
+ theme,
2315
+ modifyBgColorExtended,
2316
+ poles[0],
2317
+ poles[1]
2318
+ );
2319
+ }
2320
+ const pole = getBgPole(theme);
2321
+ return modifyColorWithCache(rgb, theme, modifyBgHSL, pole);
2342
2322
  }
2343
- const LARGE_IMAGE_PIXELS_COUNT = 512 * 512;
2344
- function analyzeImage(image) {
2345
- if (!canvas) {
2346
- createCanvas();
2323
+ function modifyBackgroundColor(rgb, theme, shouldRegisterColorVariable = true) {
2324
+ if (!shouldRegisterColorVariable) {
2325
+ return _modifyBackgroundColor(rgb, theme);
2347
2326
  }
2348
- let sw;
2349
- let sh;
2350
- if (image instanceof HTMLImageElement) {
2351
- sw = image.naturalWidth;
2352
- sh = image.naturalHeight;
2327
+ return modifyAndRegisterColor(
2328
+ "background",
2329
+ rgb,
2330
+ theme,
2331
+ _modifyBackgroundColor
2332
+ );
2333
+ }
2334
+ const MIN_FG_LIGHTNESS = 0.55;
2335
+ function modifyBlueFgHue(hue) {
2336
+ return scale(hue, 205, 245, 205, 220);
2337
+ }
2338
+ function modifyFgHSL({h, s, l, a}, pole) {
2339
+ const isLight = l > 0.5;
2340
+ const isNeutral = l < 0.2 || s < 0.24;
2341
+ const isBlue = !isNeutral && h > 205 && h < 245;
2342
+ if (isLight) {
2343
+ const lx = scale(l, 0.5, 1, MIN_FG_LIGHTNESS, pole.l);
2344
+ if (isNeutral) {
2345
+ const hx = pole.h;
2346
+ const sx = pole.s;
2347
+ return {h: hx, s: sx, l: lx, a};
2348
+ }
2349
+ let hx = h;
2350
+ if (isBlue) {
2351
+ hx = modifyBlueFgHue(h);
2352
+ }
2353
+ return {h: hx, s, l: lx, a};
2354
+ }
2355
+ if (isNeutral) {
2356
+ const hx = pole.h;
2357
+ const sx = pole.s;
2358
+ const lx = scale(l, 0, 0.5, pole.l, MIN_FG_LIGHTNESS);
2359
+ return {h: hx, s: sx, l: lx, a};
2360
+ }
2361
+ let hx = h;
2362
+ let lx;
2363
+ if (isBlue) {
2364
+ hx = modifyBlueFgHue(h);
2365
+ lx = scale(l, 0, 0.5, pole.l, Math.min(1, MIN_FG_LIGHTNESS + 0.05));
2353
2366
  } else {
2354
- sw = image.width;
2355
- sh = image.height;
2367
+ lx = scale(l, 0, 0.5, pole.l, MIN_FG_LIGHTNESS);
2356
2368
  }
2357
- if (sw === 0 || sh === 0) {
2358
- logWarn("Image is empty");
2359
- return {
2360
- isDark: false,
2361
- isLight: false,
2362
- isTransparent: false,
2363
- isLarge: false
2364
- };
2369
+ return {h: hx, s, l: lx, a};
2370
+ }
2371
+ function _modifyForegroundColor(rgb, theme) {
2372
+ if (theme.mode === 0) {
2373
+ if (__PLUS__) {
2374
+ const poles = getTextPoles();
2375
+ return modifyColorWithCache(
2376
+ rgb,
2377
+ theme,
2378
+ modifyLightSchemeColorExtended,
2379
+ poles[0],
2380
+ poles[1]
2381
+ );
2382
+ }
2383
+ return modifyLightSchemeColor(rgb, theme);
2365
2384
  }
2366
- const isLarge = sw * sh > LARGE_IMAGE_PIXELS_COUNT;
2367
- const sourcePixelsCount = sw * sh;
2368
- const k = Math.min(
2369
- 1,
2370
- Math.sqrt(MAX_ANALYSIS_PIXELS_COUNT / sourcePixelsCount)
2371
- );
2372
- const width = Math.ceil(sw * k);
2373
- const height = Math.ceil(sh * k);
2374
- context.clearRect(0, 0, width, height);
2375
- context.drawImage(image, 0, 0, sw, sh, 0, 0, width, height);
2376
- const imageData = context.getImageData(0, 0, width, height);
2377
- const d = imageData.data;
2378
- const TRANSPARENT_ALPHA_THRESHOLD = 0.05;
2379
- const DARK_LIGHTNESS_THRESHOLD = 0.4;
2380
- const LIGHT_LIGHTNESS_THRESHOLD = 0.7;
2381
- let transparentPixelsCount = 0;
2382
- let darkPixelsCount = 0;
2383
- let lightPixelsCount = 0;
2384
- let i, x, y;
2385
- let r, g, b, a;
2386
- let l;
2387
- for (y = 0; y < height; y++) {
2388
- for (x = 0; x < width; x++) {
2389
- i = 4 * (y * width + x);
2390
- r = d[i + 0];
2391
- g = d[i + 1];
2392
- b = d[i + 2];
2393
- a = d[i + 3];
2394
- if (a / 255 < TRANSPARENT_ALPHA_THRESHOLD) {
2395
- transparentPixelsCount++;
2396
- } else {
2397
- l = getSRGBLightness(r, g, b);
2398
- if (l < DARK_LIGHTNESS_THRESHOLD) {
2399
- darkPixelsCount++;
2400
- }
2401
- if (l > LIGHT_LIGHTNESS_THRESHOLD) {
2402
- lightPixelsCount++;
2385
+ if (__PLUS__) {
2386
+ const poles = getTextPoles();
2387
+ return modifyColorWithCache(
2388
+ rgb,
2389
+ theme,
2390
+ modifyFgColorExtended,
2391
+ poles[0],
2392
+ poles[1]
2393
+ );
2394
+ }
2395
+ const pole = getFgPole(theme);
2396
+ return modifyColorWithCache(rgb, theme, modifyFgHSL, pole);
2397
+ }
2398
+ function modifyForegroundColor(rgb, theme, shouldRegisterColorVariable = true) {
2399
+ if (!shouldRegisterColorVariable) {
2400
+ return _modifyForegroundColor(rgb, theme);
2401
+ }
2402
+ return modifyAndRegisterColor("text", rgb, theme, _modifyForegroundColor);
2403
+ }
2404
+ function modifyBorderHSL({h, s, l, a}, poleFg, poleBg) {
2405
+ const isDark = l < 0.5;
2406
+ const isNeutral = l < 0.2 || s < 0.24;
2407
+ let hx = h;
2408
+ let sx = s;
2409
+ if (isNeutral) {
2410
+ if (isDark) {
2411
+ hx = poleFg.h;
2412
+ sx = poleFg.s;
2413
+ } else {
2414
+ hx = poleBg.h;
2415
+ sx = poleBg.s;
2416
+ }
2417
+ }
2418
+ const lx = scale(l, 0, 1, 0.5, 0.2);
2419
+ return {h: hx, s: sx, l: lx, a};
2420
+ }
2421
+ function _modifyBorderColor(rgb, theme) {
2422
+ if (theme.mode === 0) {
2423
+ return modifyLightSchemeColor(rgb, theme);
2424
+ }
2425
+ const poleFg = getFgPole(theme);
2426
+ const poleBg = getBgPole(theme);
2427
+ return modifyColorWithCache(rgb, theme, modifyBorderHSL, poleFg, poleBg);
2428
+ }
2429
+ function modifyBorderColor(rgb, theme, shouldRegisterColorVariable = true) {
2430
+ if (!shouldRegisterColorVariable) {
2431
+ return _modifyBorderColor(rgb, theme);
2432
+ }
2433
+ return modifyAndRegisterColor("border", rgb, theme, _modifyBorderColor);
2434
+ }
2435
+ function modifyShadowColor(rgb, theme) {
2436
+ return modifyBackgroundColor(rgb, theme);
2437
+ }
2438
+ function modifyGradientColor(rgb, theme) {
2439
+ return modifyBackgroundColor(rgb, theme);
2440
+ }
2441
+
2442
+ const gradientLength = "gradient".length;
2443
+ const conicGradient = "conic-";
2444
+ const conicGradientLength = conicGradient.length;
2445
+ const radialGradient = "radial-";
2446
+ const linearGradient = "linear-";
2447
+ function parseGradient(value) {
2448
+ const result = [];
2449
+ let index = 0;
2450
+ let startIndex = conicGradient.length;
2451
+ while ((index = value.indexOf("gradient", startIndex)) !== -1) {
2452
+ let typeGradient;
2453
+ [linearGradient, radialGradient, conicGradient].find((possibleType) => {
2454
+ if (index - possibleType.length >= 0) {
2455
+ const possibleGradient = value.substring(
2456
+ index - possibleType.length,
2457
+ index
2458
+ );
2459
+ if (possibleGradient === possibleType) {
2460
+ if (
2461
+ value.slice(
2462
+ index - possibleType.length - 10,
2463
+ index - possibleType.length - 1
2464
+ ) === "repeating"
2465
+ ) {
2466
+ typeGradient = `repeating-${possibleType}gradient`;
2467
+ return true;
2468
+ }
2469
+ if (
2470
+ value.slice(
2471
+ index - possibleType.length - 8,
2472
+ index - possibleType.length - 1
2473
+ ) === "-webkit"
2474
+ ) {
2475
+ typeGradient = `-webkit-${possibleType}gradient`;
2476
+ return true;
2477
+ }
2478
+ typeGradient = `${possibleType}gradient`;
2479
+ return true;
2403
2480
  }
2404
2481
  }
2482
+ });
2483
+ if (!typeGradient) {
2484
+ break;
2405
2485
  }
2486
+ const {start, end} = getParenthesesRange(value, index + gradientLength);
2487
+ const match = value.substring(start + 1, end - 1);
2488
+ startIndex = end + 1 + conicGradientLength;
2489
+ result.push({
2490
+ typeGradient,
2491
+ match,
2492
+ offset: typeGradient.length + 2,
2493
+ index: index - typeGradient.length + gradientLength,
2494
+ hasComma: true
2495
+ });
2406
2496
  }
2407
- const totalPixelsCount = width * height;
2408
- const opaquePixelsCount = totalPixelsCount - transparentPixelsCount;
2409
- const DARK_IMAGE_THRESHOLD = 0.7;
2410
- const LIGHT_IMAGE_THRESHOLD = 0.7;
2411
- const TRANSPARENT_IMAGE_THRESHOLD = 0.1;
2412
- return {
2413
- isDark: darkPixelsCount / opaquePixelsCount >= DARK_IMAGE_THRESHOLD,
2414
- isLight: lightPixelsCount / opaquePixelsCount >= LIGHT_IMAGE_THRESHOLD,
2415
- isTransparent:
2416
- transparentPixelsCount / totalPixelsCount >=
2417
- TRANSPARENT_IMAGE_THRESHOLD,
2418
- isLarge
2419
- };
2497
+ if (result.length) {
2498
+ result[result.length - 1].hasComma = false;
2499
+ }
2500
+ return result;
2420
2501
  }
2421
- let isBlobURLSupported = null;
2422
- let canUseProxy = false;
2423
- let blobURLCheckRequested = false;
2424
- const blobURLCheckAwaiters = [];
2425
- document.addEventListener(
2426
- "__darkreader__inlineScriptsAllowed",
2427
- () => (canUseProxy = true),
2428
- {once: true}
2429
- );
2430
- async function requestBlobURLCheck() {
2431
- if (!canUseProxy) {
2502
+
2503
+ const STORAGE_KEY_IMAGE_DETAILS_LIST = "__darkreader__imageDetails_v2_list";
2504
+ const STORAGE_KEY_IMAGE_DETAILS_PREFIX = "__darkreader__imageDetails_v2_";
2505
+ const STORAGE_KEY_CSS_FETCH_PREFIX = "__darkreader__cssFetch_";
2506
+ let imageCacheTimeout = 0;
2507
+ const imageDetailsCacheQueue = new Map();
2508
+ const cachedImageUrls = [];
2509
+ function writeImageDetailsQueue() {
2510
+ imageDetailsCacheQueue.forEach((details, url) => {
2511
+ if (url && url.startsWith("https://")) {
2512
+ try {
2513
+ const json = JSON.stringify(details);
2514
+ sessionStorage.setItem(
2515
+ `${STORAGE_KEY_IMAGE_DETAILS_PREFIX}${url}`,
2516
+ json
2517
+ );
2518
+ cachedImageUrls.push(url);
2519
+ } catch (err) {}
2520
+ }
2521
+ });
2522
+ imageDetailsCacheQueue.clear();
2523
+ sessionStorage.setItem(
2524
+ STORAGE_KEY_IMAGE_DETAILS_LIST,
2525
+ JSON.stringify(cachedImageUrls)
2526
+ );
2527
+ }
2528
+ function writeImageDetailsCache(url, imageDetails) {
2529
+ if (!url || !url.startsWith("https://")) {
2432
2530
  return;
2433
2531
  }
2434
- if (blobURLCheckRequested) {
2435
- return await new Promise((resolve) =>
2436
- blobURLCheckAwaiters.push(resolve)
2437
- );
2438
- }
2439
- blobURLCheckRequested = true;
2440
- await new Promise((resolve) => {
2441
- document.addEventListener(
2442
- "__darkreader__blobURLCheckResponse",
2443
- (e) => {
2444
- isBlobURLSupported = e.detail.blobURLAllowed;
2445
- resolve();
2446
- blobURLCheckAwaiters.forEach((r) => r());
2447
- blobURLCheckAwaiters.splice(0);
2448
- },
2449
- {once: true}
2450
- );
2451
- document.dispatchEvent(
2452
- new CustomEvent("__darkreader__blobURLCheckRequest")
2453
- );
2454
- });
2532
+ imageDetailsCacheQueue.set(url, imageDetails);
2533
+ clearTimeout(imageCacheTimeout);
2534
+ imageCacheTimeout = setTimeout(writeImageDetailsQueue, 1000);
2455
2535
  }
2456
- function isBlobURLCheckResultReady() {
2457
- return isBlobURLSupported != null || !canUseProxy;
2536
+ function readImageDetailsCache(targetMap) {
2537
+ try {
2538
+ const jsonList = sessionStorage.getItem(STORAGE_KEY_IMAGE_DETAILS_LIST);
2539
+ if (!jsonList) {
2540
+ return;
2541
+ }
2542
+ const list = JSON.parse(jsonList);
2543
+ list.forEach((url) => {
2544
+ const json = sessionStorage.getItem(
2545
+ `${STORAGE_KEY_IMAGE_DETAILS_PREFIX}${url}`
2546
+ );
2547
+ if (json) {
2548
+ const details = JSON.parse(json);
2549
+ targetMap.set(url, details);
2550
+ }
2551
+ });
2552
+ } catch (err) {}
2458
2553
  }
2459
- function onCSPError(err) {
2460
- if (err.blockedURI === "blob") {
2461
- isBlobURLSupported = false;
2462
- document.removeEventListener("securitypolicyviolation", onCSPError);
2463
- }
2554
+ function writeCSSFetchCache(url, cssText) {
2555
+ const key = `${STORAGE_KEY_CSS_FETCH_PREFIX}${url}`;
2556
+ try {
2557
+ sessionStorage.setItem(key, cssText);
2558
+ } catch (err) {}
2464
2559
  }
2465
- document.addEventListener("securitypolicyviolation", onCSPError);
2466
- const objectURLs = new Set();
2467
- function getFilteredImageURL({dataURL, width, height}, theme) {
2468
- if (dataURL.startsWith("data:image/svg+xml")) {
2469
- dataURL = escapeXML(dataURL);
2470
- }
2471
- const matrix = getSVGFilterMatrixValue(theme);
2472
- const svg = [
2473
- `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}">`,
2474
- "<defs>",
2475
- '<filter id="darkreader-image-filter">',
2476
- `<feColorMatrix type="matrix" values="${matrix}" />`,
2477
- "</filter>",
2478
- "</defs>",
2479
- `<image width="${width}" height="${height}" filter="url(#darkreader-image-filter)" xlink:href="${dataURL}" />`,
2480
- "</svg>"
2481
- ].join("");
2482
- if (!isBlobURLSupported) {
2483
- return `data:image/svg+xml;base64,${btoa(svg)}`;
2484
- }
2485
- const bytes = new Uint8Array(svg.length);
2486
- for (let i = 0; i < svg.length; i++) {
2487
- bytes[i] = svg.charCodeAt(i);
2488
- }
2489
- const blob = new Blob([bytes], {type: "image/svg+xml"});
2490
- const objectURL = URL.createObjectURL(blob);
2491
- objectURLs.add(objectURL);
2492
- return objectURL;
2560
+ function readCSSFetchCache(url) {
2561
+ const key = `${STORAGE_KEY_CSS_FETCH_PREFIX}${url}`;
2562
+ try {
2563
+ return sessionStorage.getItem(key) ?? null;
2564
+ } catch (err) {}
2565
+ return null;
2493
2566
  }
2494
- const xmlEscapeChars = {
2495
- "<": "&lt;",
2496
- ">": "&gt;",
2497
- "&": "&amp;",
2498
- "'": "&apos;",
2499
- '"': "&quot;"
2500
- };
2501
- function escapeXML(str) {
2502
- return str.replace(/[<>&'"]/g, (c) => xmlEscapeChars[c] ?? c);
2567
+
2568
+ function toSVGMatrix(matrix) {
2569
+ return matrix
2570
+ .slice(0, 4)
2571
+ .map((m) => m.map((m) => m.toFixed(3)).join(" "))
2572
+ .join(" ");
2503
2573
  }
2504
- const dataURLBlobURLs = new Map();
2505
- function tryConvertDataURLToBlobSync(dataURL) {
2506
- const colonIndex = dataURL.indexOf(":");
2507
- const semicolonIndex = dataURL.indexOf(";", colonIndex + 1);
2508
- const commaIndex = dataURL.indexOf(",", semicolonIndex + 1);
2509
- const encoding = dataURL
2510
- .substring(semicolonIndex + 1, commaIndex)
2511
- .toLocaleLowerCase();
2512
- const mediaType = dataURL.substring(colonIndex + 1, semicolonIndex);
2513
- if (encoding !== "base64" || !mediaType) {
2514
- return null;
2515
- }
2516
- const characters = atob(dataURL.substring(commaIndex + 1));
2517
- const bytes = new Uint8Array(characters.length);
2518
- for (let i = 0; i < characters.length; i++) {
2519
- bytes[i] = characters.charCodeAt(i);
2520
- }
2521
- return new Blob([bytes], {type: mediaType});
2574
+ function getSVGFilterMatrixValue(config) {
2575
+ return toSVGMatrix(createFilterMatrix(config));
2522
2576
  }
2523
- async function tryConvertDataURLToBlobURL(dataURL) {
2524
- if (!isBlobURLSupported) {
2525
- return null;
2577
+
2578
+ const MAX_FRAME_DURATION = 1000 / 60;
2579
+ class AsyncQueue {
2580
+ constructor() {
2581
+ this.queue = [];
2582
+ this.timerId = null;
2526
2583
  }
2527
- const hash = getHashCode(dataURL);
2528
- let blobURL = dataURLBlobURLs.get(hash);
2529
- if (blobURL) {
2530
- return blobURL;
2584
+ addTask(task) {
2585
+ this.queue.push(task);
2586
+ this.scheduleFrame();
2531
2587
  }
2532
- let blob = tryConvertDataURLToBlobSync(dataURL);
2533
- if (!blob) {
2534
- const response = await fetch(dataURL);
2535
- blob = await response.blob();
2588
+ stop() {
2589
+ if (this.timerId !== null) {
2590
+ cancelAnimationFrame(this.timerId);
2591
+ this.timerId = null;
2592
+ }
2593
+ this.queue = [];
2536
2594
  }
2537
- blobURL = URL.createObjectURL(blob);
2538
- dataURLBlobURLs.set(hash, blobURL);
2539
- return blobURL;
2540
- }
2541
- function cleanImageProcessingCache() {
2542
- imageManager && imageManager.stop();
2543
- removeCanvas();
2544
- objectURLs.forEach((u) => URL.revokeObjectURL(u));
2545
- objectURLs.clear();
2546
- dataURLBlobURLs.forEach((u) => URL.revokeObjectURL(u));
2547
- dataURLBlobURLs.clear();
2548
- }
2549
-
2550
- let variablesSheet;
2551
- const registeredColors = new Map();
2552
- function registerVariablesSheet(sheet) {
2553
- variablesSheet = sheet;
2554
- const types = ["background", "text", "border"];
2555
- registeredColors.forEach((registered) => {
2556
- types.forEach((type) => {
2557
- if (registered[type]) {
2558
- const {variable, value} = registered[type];
2559
- variablesSheet?.cssRules[0].style.setProperty(variable, value);
2595
+ scheduleFrame() {
2596
+ if (this.timerId) {
2597
+ return;
2598
+ }
2599
+ this.timerId = requestAnimationFrame(() => {
2600
+ this.timerId = null;
2601
+ const start = Date.now();
2602
+ let cb;
2603
+ while ((cb = this.queue.shift())) {
2604
+ cb();
2605
+ if (Date.now() - start >= MAX_FRAME_DURATION) {
2606
+ this.scheduleFrame();
2607
+ break;
2608
+ }
2560
2609
  }
2561
2610
  });
2562
- });
2563
- }
2564
- function releaseVariablesSheet() {
2565
- variablesSheet = null;
2566
- clearColorPalette();
2567
- }
2568
- function getRegisteredVariableValue(type, registered) {
2569
- return `var(${registered[type].variable}, ${registered[type].value})`;
2570
- }
2571
- function getRegisteredColor(type, parsed) {
2572
- const hex = rgbToHexString(parsed);
2573
- const registered = registeredColors.get(hex);
2574
- if (registered?.[type]) {
2575
- return getRegisteredVariableValue(type, registered);
2576
2611
  }
2577
- return null;
2578
2612
  }
2579
- function registerColor(type, parsed, value) {
2580
- const hex = rgbToHexString(parsed);
2581
- let registered;
2582
- if (registeredColors.has(hex)) {
2583
- registered = registeredColors.get(hex);
2584
- } else {
2585
- const parsed = parseColorWithCache(hex);
2586
- registered = {parsed};
2587
- registeredColors.set(hex, registered);
2588
- }
2589
- const variable = `--darkreader-${type}-${hex.replace("#", "")}`;
2590
- registered[type] = {variable, value};
2591
- if (variablesSheet?.cssRules[0]?.style) {
2592
- variablesSheet?.cssRules[0].style.setProperty(variable, value);
2613
+
2614
+ const resolvers$1 = new Map();
2615
+ const rejectors = new Map();
2616
+ async function bgFetch(request) {
2617
+ if (window.DarkReader?.Plugins?.fetch) {
2618
+ return window.DarkReader.Plugins.fetch(request);
2593
2619
  }
2594
- return getRegisteredVariableValue(type, registered);
2620
+ return new Promise((resolve, reject) => {
2621
+ const id = generateUID();
2622
+ resolvers$1.set(id, resolve);
2623
+ rejectors.set(id, reject);
2624
+ chrome.runtime.sendMessage({
2625
+ type: MessageTypeCStoBG.FETCH,
2626
+ data: request,
2627
+ id
2628
+ });
2629
+ });
2595
2630
  }
2596
- function getColorPalette() {
2597
- const background = [];
2598
- const border = [];
2599
- const text = [];
2600
- registeredColors.forEach((registered) => {
2601
- if (registered.background) {
2602
- background.push(registered.parsed);
2603
- }
2604
- if (registered.border) {
2605
- border.push(registered.parsed);
2631
+ chrome.runtime.onMessage.addListener(({type, data, error, id}) => {
2632
+ if (type === MessageTypeBGtoCS.FETCH_RESPONSE) {
2633
+ const resolve = resolvers$1.get(id);
2634
+ const reject = rejectors.get(id);
2635
+ resolvers$1.delete(id);
2636
+ rejectors.delete(id);
2637
+ if (error) {
2638
+ reject &&
2639
+ reject(typeof error === "string" ? new Error(error) : error);
2640
+ } else {
2641
+ resolve && resolve(data);
2606
2642
  }
2607
- if (registered.text) {
2608
- text.push(registered.parsed);
2643
+ }
2644
+ });
2645
+
2646
+ const imageManager = new AsyncQueue();
2647
+ async function getImageDetails(url) {
2648
+ return new Promise(async (resolve, reject) => {
2649
+ try {
2650
+ const dataURL = url.startsWith("data:")
2651
+ ? url
2652
+ : await getDataURL(url);
2653
+ const blob =
2654
+ tryConvertDataURLToBlobSync(dataURL) ?? (await loadAsBlob(url));
2655
+ let image;
2656
+ if (dataURL.startsWith("data:image/svg+xml")) {
2657
+ image = await loadImage(dataURL);
2658
+ } else {
2659
+ image =
2660
+ (await tryCreateImageBitmap(blob)) ??
2661
+ (await loadImage(dataURL));
2662
+ }
2663
+ imageManager.addTask(() => {
2664
+ const analysis = analyzeImage(image);
2665
+ resolve({
2666
+ src: url,
2667
+ dataURL: analysis.isLarge ? "" : dataURL,
2668
+ width: image.width,
2669
+ height: image.height,
2670
+ ...analysis
2671
+ });
2672
+ });
2673
+ } catch (error) {
2674
+ reject(error);
2609
2675
  }
2610
2676
  });
2611
- return {background, border, text};
2612
- }
2613
- function clearColorPalette() {
2614
- registeredColors.clear();
2615
- }
2616
-
2617
- function getBgPole(theme) {
2618
- const isDarkScheme = theme.mode === 1;
2619
- const prop = isDarkScheme
2620
- ? "darkSchemeBackgroundColor"
2621
- : "lightSchemeBackgroundColor";
2622
- return theme[prop];
2623
2677
  }
2624
- function getFgPole(theme) {
2625
- const isDarkScheme = theme.mode === 1;
2626
- const prop = isDarkScheme ? "darkSchemeTextColor" : "lightSchemeTextColor";
2627
- return theme[prop];
2678
+ async function getDataURL(url) {
2679
+ const parsedURL = new URL(url);
2680
+ if (parsedURL.origin === location.origin) {
2681
+ return await loadAsDataURL(url);
2682
+ }
2683
+ return await bgFetch({url, responseType: "data-url"});
2628
2684
  }
2629
- const colorModificationCache = new Map();
2630
- function clearColorModificationCache() {
2631
- colorModificationCache.clear();
2685
+ async function tryCreateImageBitmap(blob) {
2686
+ try {
2687
+ return await createImageBitmap(blob);
2688
+ } catch (err) {
2689
+ logWarn(
2690
+ `Unable to create image bitmap for type ${blob.type}: ${String(err)}`
2691
+ );
2692
+ return null;
2693
+ }
2632
2694
  }
2633
- const rgbCacheKeys = ["r", "g", "b", "a"];
2634
- const themeCacheKeys$1 = [
2635
- "mode",
2636
- "brightness",
2637
- "contrast",
2638
- "grayscale",
2639
- "sepia",
2640
- "darkSchemeBackgroundColor",
2641
- "darkSchemeTextColor",
2642
- "lightSchemeBackgroundColor",
2643
- "lightSchemeTextColor"
2644
- ];
2645
- function getCacheId(rgb, theme) {
2646
- let resultId = "";
2647
- rgbCacheKeys.forEach((key) => {
2648
- resultId += `${rgb[key]};`;
2649
- });
2650
- themeCacheKeys$1.forEach((key) => {
2651
- resultId += `${theme[key]};`;
2695
+ const INCOMPLETE_DOC_LOADING_IMAGE_LIMIT = 256;
2696
+ let loadingImagesCount = 0;
2697
+ async function loadImage(url) {
2698
+ return new Promise((resolve, reject) => {
2699
+ const image = new Image();
2700
+ image.onload = () => resolve(image);
2701
+ image.onerror = () => reject(`Unable to load image ${url}`);
2702
+ if (
2703
+ ++loadingImagesCount <= INCOMPLETE_DOC_LOADING_IMAGE_LIMIT ||
2704
+ isReadyStateComplete()
2705
+ ) {
2706
+ image.src = url;
2707
+ } else {
2708
+ addReadyStateCompleteListener(() => (image.src = url));
2709
+ }
2652
2710
  });
2653
- return resultId;
2654
- }
2655
- function modifyColorWithCache(
2656
- rgb,
2657
- theme,
2658
- modifyHSL,
2659
- poleColor,
2660
- anotherPoleColor
2661
- ) {
2662
- let fnCache;
2663
- if (colorModificationCache.has(modifyHSL)) {
2664
- fnCache = colorModificationCache.get(modifyHSL);
2665
- } else {
2666
- fnCache = new Map();
2667
- colorModificationCache.set(modifyHSL, fnCache);
2668
- }
2669
- const id = getCacheId(rgb, theme);
2670
- if (fnCache.has(id)) {
2671
- return fnCache.get(id);
2672
- }
2673
- const hsl = rgbToHSL(rgb);
2674
- const pole = poleColor == null ? null : parseToHSLWithCache(poleColor);
2675
- const anotherPole =
2676
- anotherPoleColor == null ? null : parseToHSLWithCache(anotherPoleColor);
2677
- const modified = modifyHSL(hsl, pole, anotherPole);
2678
- const {r, g, b, a} = hslToRGB(modified);
2679
- const matrix = createFilterMatrix(theme);
2680
- const [rf, gf, bf] = applyColorMatrix([r, g, b], matrix);
2681
- const color =
2682
- a === 1
2683
- ? rgbToHexString({r: rf, g: gf, b: bf})
2684
- : rgbToString({r: rf, g: gf, b: bf, a});
2685
- fnCache.set(id, color);
2686
- return color;
2687
2711
  }
2688
- function modifyAndRegisterColor(type, rgb, theme, modifier) {
2689
- const registered = getRegisteredColor(type, rgb);
2690
- if (registered) {
2691
- return registered;
2692
- }
2693
- const value = modifier(rgb, theme);
2694
- return registerColor(type, rgb, value);
2712
+ const MAX_ANALYSIS_PIXELS_COUNT = 32 * 32;
2713
+ let canvas;
2714
+ let context;
2715
+ function createCanvas() {
2716
+ const maxWidth = MAX_ANALYSIS_PIXELS_COUNT;
2717
+ const maxHeight = MAX_ANALYSIS_PIXELS_COUNT;
2718
+ canvas = document.createElement("canvas");
2719
+ canvas.width = maxWidth;
2720
+ canvas.height = maxHeight;
2721
+ context = canvas.getContext("2d", {willReadFrequently: true});
2722
+ context.imageSmoothingEnabled = false;
2695
2723
  }
2696
- function modifyLightSchemeColor(rgb, theme) {
2697
- const poleBg = getBgPole(theme);
2698
- const poleFg = getFgPole(theme);
2699
- return modifyColorWithCache(rgb, theme, modifyLightModeHSL, poleFg, poleBg);
2724
+ function removeCanvas() {
2725
+ canvas = null;
2726
+ context = null;
2700
2727
  }
2701
- function modifyLightModeHSL({h, s, l, a}, poleFg, poleBg) {
2702
- const isDark = l < 0.5;
2703
- let isNeutral;
2704
- if (isDark) {
2705
- isNeutral = l < 0.2 || s < 0.12;
2728
+ const LARGE_IMAGE_PIXELS_COUNT = 512 * 512;
2729
+ function analyzeImage(image) {
2730
+ if (!canvas) {
2731
+ createCanvas();
2732
+ }
2733
+ let sw;
2734
+ let sh;
2735
+ if (image instanceof HTMLImageElement) {
2736
+ sw = image.naturalWidth;
2737
+ sh = image.naturalHeight;
2706
2738
  } else {
2707
- const isBlue = h > 200 && h < 280;
2708
- isNeutral = s < 0.24 || (l > 0.8 && isBlue);
2739
+ sw = image.width;
2740
+ sh = image.height;
2709
2741
  }
2710
- let hx = h;
2711
- let sx = l;
2712
- if (isNeutral) {
2713
- if (isDark) {
2714
- hx = poleFg.h;
2715
- sx = poleFg.s;
2716
- } else {
2717
- hx = poleBg.h;
2718
- sx = poleBg.s;
2719
- }
2742
+ if (sw === 0 || sh === 0) {
2743
+ logWarn("Image is empty");
2744
+ return {
2745
+ isDark: false,
2746
+ isLight: false,
2747
+ isTransparent: false,
2748
+ isLarge: false
2749
+ };
2720
2750
  }
2721
- const lx = scale(l, 0, 1, poleFg.l, poleBg.l);
2722
- return {h: hx, s: sx, l: lx, a};
2723
- }
2724
- const MAX_BG_LIGHTNESS = 0.4;
2725
- function modifyBgHSL({h, s, l, a}, pole) {
2726
- const isDark = l < 0.5;
2727
- const isBlue = h > 200 && h < 280;
2728
- const isNeutral = s < 0.12 || (l > 0.8 && isBlue);
2729
- if (isDark) {
2730
- const lx = scale(l, 0, 0.5, 0, MAX_BG_LIGHTNESS);
2731
- if (isNeutral) {
2732
- const hx = pole.h;
2733
- const sx = pole.s;
2734
- return {h: hx, s: sx, l: lx, a};
2751
+ const isLarge = sw * sh > LARGE_IMAGE_PIXELS_COUNT;
2752
+ const sourcePixelsCount = sw * sh;
2753
+ const k = Math.min(
2754
+ 1,
2755
+ Math.sqrt(MAX_ANALYSIS_PIXELS_COUNT / sourcePixelsCount)
2756
+ );
2757
+ const width = Math.ceil(sw * k);
2758
+ const height = Math.ceil(sh * k);
2759
+ context.clearRect(0, 0, width, height);
2760
+ context.drawImage(image, 0, 0, sw, sh, 0, 0, width, height);
2761
+ const imageData = context.getImageData(0, 0, width, height);
2762
+ const d = imageData.data;
2763
+ const TRANSPARENT_ALPHA_THRESHOLD = 0.05;
2764
+ const DARK_LIGHTNESS_THRESHOLD = 0.4;
2765
+ const LIGHT_LIGHTNESS_THRESHOLD = 0.7;
2766
+ let transparentPixelsCount = 0;
2767
+ let darkPixelsCount = 0;
2768
+ let lightPixelsCount = 0;
2769
+ let i, x, y;
2770
+ let r, g, b, a;
2771
+ let l;
2772
+ for (y = 0; y < height; y++) {
2773
+ for (x = 0; x < width; x++) {
2774
+ i = 4 * (y * width + x);
2775
+ r = d[i + 0];
2776
+ g = d[i + 1];
2777
+ b = d[i + 2];
2778
+ a = d[i + 3];
2779
+ if (a / 255 < TRANSPARENT_ALPHA_THRESHOLD) {
2780
+ transparentPixelsCount++;
2781
+ } else {
2782
+ l = getSRGBLightness(r, g, b);
2783
+ if (l < DARK_LIGHTNESS_THRESHOLD) {
2784
+ darkPixelsCount++;
2785
+ }
2786
+ if (l > LIGHT_LIGHTNESS_THRESHOLD) {
2787
+ lightPixelsCount++;
2788
+ }
2789
+ }
2735
2790
  }
2736
- return {h, s, l: lx, a};
2737
- }
2738
- let lx = scale(l, 0.5, 1, MAX_BG_LIGHTNESS, pole.l);
2739
- if (isNeutral) {
2740
- const hx = pole.h;
2741
- const sx = pole.s;
2742
- return {h: hx, s: sx, l: lx, a};
2743
2791
  }
2744
- let hx = h;
2745
- const isYellow = h > 60 && h < 180;
2746
- if (isYellow) {
2747
- const isCloserToGreen = h > 120;
2748
- if (isCloserToGreen) {
2749
- hx = scale(h, 120, 180, 135, 180);
2750
- } else {
2751
- hx = scale(h, 60, 120, 60, 105);
2752
- }
2792
+ const totalPixelsCount = width * height;
2793
+ const opaquePixelsCount = totalPixelsCount - transparentPixelsCount;
2794
+ const DARK_IMAGE_THRESHOLD = 0.7;
2795
+ const LIGHT_IMAGE_THRESHOLD = 0.7;
2796
+ const TRANSPARENT_IMAGE_THRESHOLD = 0.1;
2797
+ return {
2798
+ isDark: darkPixelsCount / opaquePixelsCount >= DARK_IMAGE_THRESHOLD,
2799
+ isLight: lightPixelsCount / opaquePixelsCount >= LIGHT_IMAGE_THRESHOLD,
2800
+ isTransparent:
2801
+ transparentPixelsCount / totalPixelsCount >=
2802
+ TRANSPARENT_IMAGE_THRESHOLD,
2803
+ isLarge
2804
+ };
2805
+ }
2806
+ let isBlobURLSupported = null;
2807
+ let canUseProxy = false;
2808
+ let blobURLCheckRequested = false;
2809
+ const blobURLCheckAwaiters = [];
2810
+ document.addEventListener(
2811
+ "__darkreader__inlineScriptsAllowed",
2812
+ () => (canUseProxy = true),
2813
+ {once: true}
2814
+ );
2815
+ async function requestBlobURLCheck() {
2816
+ if (!canUseProxy) {
2817
+ return;
2753
2818
  }
2754
- if (hx > 40 && hx < 80) {
2755
- lx *= 0.75;
2819
+ if (blobURLCheckRequested) {
2820
+ return await new Promise((resolve) =>
2821
+ blobURLCheckAwaiters.push(resolve)
2822
+ );
2756
2823
  }
2757
- return {h: hx, s, l: lx, a};
2824
+ blobURLCheckRequested = true;
2825
+ await new Promise((resolve) => {
2826
+ document.addEventListener(
2827
+ "__darkreader__blobURLCheckResponse",
2828
+ (e) => {
2829
+ isBlobURLSupported = e.detail.blobURLAllowed;
2830
+ resolve();
2831
+ blobURLCheckAwaiters.forEach((r) => r());
2832
+ blobURLCheckAwaiters.splice(0);
2833
+ },
2834
+ {once: true}
2835
+ );
2836
+ document.dispatchEvent(
2837
+ new CustomEvent("__darkreader__blobURLCheckRequest")
2838
+ );
2839
+ });
2758
2840
  }
2759
- function _modifyBackgroundColor(rgb, theme) {
2760
- if (theme.mode === 0) {
2761
- return modifyLightSchemeColor(rgb, theme);
2762
- }
2763
- const pole = getBgPole(theme);
2764
- return modifyColorWithCache(rgb, {...theme, mode: 0}, modifyBgHSL, pole);
2841
+ function isBlobURLCheckResultReady() {
2842
+ return isBlobURLSupported != null || !canUseProxy;
2765
2843
  }
2766
- function modifyBackgroundColor(rgb, theme, shouldRegisterColorVariable = true) {
2767
- if (!shouldRegisterColorVariable) {
2768
- return _modifyBackgroundColor(rgb, theme);
2844
+ function onCSPError(err) {
2845
+ if (err.blockedURI === "blob") {
2846
+ isBlobURLSupported = false;
2847
+ document.removeEventListener("securitypolicyviolation", onCSPError);
2769
2848
  }
2770
- return modifyAndRegisterColor(
2771
- "background",
2772
- rgb,
2773
- theme,
2774
- _modifyBackgroundColor
2775
- );
2776
- }
2777
- const MIN_FG_LIGHTNESS = 0.55;
2778
- function modifyBlueFgHue(hue) {
2779
- return scale(hue, 205, 245, 205, 220);
2780
2849
  }
2781
- function modifyFgHSL({h, s, l, a}, pole) {
2782
- const isLight = l > 0.5;
2783
- const isNeutral = l < 0.2 || s < 0.24;
2784
- const isBlue = !isNeutral && h > 205 && h < 245;
2785
- if (isLight) {
2786
- const lx = scale(l, 0.5, 1, MIN_FG_LIGHTNESS, pole.l);
2787
- if (isNeutral) {
2788
- const hx = pole.h;
2789
- const sx = pole.s;
2790
- return {h: hx, s: sx, l: lx, a};
2791
- }
2792
- let hx = h;
2793
- if (isBlue) {
2794
- hx = modifyBlueFgHue(h);
2795
- }
2796
- return {h: hx, s, l: lx, a};
2850
+ document.addEventListener("securitypolicyviolation", onCSPError);
2851
+ const objectURLs = new Set();
2852
+ function getFilteredImageURL({dataURL, width, height}, theme) {
2853
+ if (dataURL.startsWith("data:image/svg+xml")) {
2854
+ dataURL = escapeXML(dataURL);
2797
2855
  }
2798
- if (isNeutral) {
2799
- const hx = pole.h;
2800
- const sx = pole.s;
2801
- const lx = scale(l, 0, 0.5, pole.l, MIN_FG_LIGHTNESS);
2802
- return {h: hx, s: sx, l: lx, a};
2856
+ const matrix = getSVGFilterMatrixValue(theme);
2857
+ const svg = [
2858
+ `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}">`,
2859
+ "<defs>",
2860
+ '<filter id="darkreader-image-filter">',
2861
+ `<feColorMatrix type="matrix" values="${matrix}" />`,
2862
+ "</filter>",
2863
+ "</defs>",
2864
+ `<image width="${width}" height="${height}" filter="url(#darkreader-image-filter)" xlink:href="${dataURL}" />`,
2865
+ "</svg>"
2866
+ ].join("");
2867
+ if (!isBlobURLSupported) {
2868
+ return `data:image/svg+xml;base64,${btoa(svg)}`;
2803
2869
  }
2804
- let hx = h;
2805
- let lx;
2806
- if (isBlue) {
2807
- hx = modifyBlueFgHue(h);
2808
- lx = scale(l, 0, 0.5, pole.l, Math.min(1, MIN_FG_LIGHTNESS + 0.05));
2809
- } else {
2810
- lx = scale(l, 0, 0.5, pole.l, MIN_FG_LIGHTNESS);
2870
+ const bytes = new Uint8Array(svg.length);
2871
+ for (let i = 0; i < svg.length; i++) {
2872
+ bytes[i] = svg.charCodeAt(i);
2811
2873
  }
2812
- return {h: hx, s, l: lx, a};
2874
+ const blob = new Blob([bytes], {type: "image/svg+xml"});
2875
+ const objectURL = URL.createObjectURL(blob);
2876
+ objectURLs.add(objectURL);
2877
+ return objectURL;
2813
2878
  }
2814
- function _modifyForegroundColor(rgb, theme) {
2815
- if (theme.mode === 0) {
2816
- return modifyLightSchemeColor(rgb, theme);
2817
- }
2818
- const pole = getFgPole(theme);
2819
- return modifyColorWithCache(rgb, {...theme, mode: 0}, modifyFgHSL, pole);
2879
+ const xmlEscapeChars = {
2880
+ "<": "&lt;",
2881
+ ">": "&gt;",
2882
+ "&": "&amp;",
2883
+ "'": "&apos;",
2884
+ '"': "&quot;"
2885
+ };
2886
+ function escapeXML(str) {
2887
+ return str.replace(/[<>&'"]/g, (c) => xmlEscapeChars[c] ?? c);
2820
2888
  }
2821
- function modifyForegroundColor(rgb, theme, shouldRegisterColorVariable = true) {
2822
- if (!shouldRegisterColorVariable) {
2823
- return _modifyForegroundColor(rgb, theme);
2889
+ const dataURLBlobURLs = new Map();
2890
+ function tryConvertDataURLToBlobSync(dataURL) {
2891
+ const colonIndex = dataURL.indexOf(":");
2892
+ const semicolonIndex = dataURL.indexOf(";", colonIndex + 1);
2893
+ const commaIndex = dataURL.indexOf(",", semicolonIndex + 1);
2894
+ const encoding = dataURL
2895
+ .substring(semicolonIndex + 1, commaIndex)
2896
+ .toLocaleLowerCase();
2897
+ const mediaType = dataURL.substring(colonIndex + 1, semicolonIndex);
2898
+ if (encoding !== "base64" || !mediaType) {
2899
+ return null;
2824
2900
  }
2825
- return modifyAndRegisterColor("text", rgb, theme, _modifyForegroundColor);
2826
- }
2827
- function modifyBorderHSL({h, s, l, a}, poleFg, poleBg) {
2828
- const isDark = l < 0.5;
2829
- const isNeutral = l < 0.2 || s < 0.24;
2830
- let hx = h;
2831
- let sx = s;
2832
- if (isNeutral) {
2833
- if (isDark) {
2834
- hx = poleFg.h;
2835
- sx = poleFg.s;
2836
- } else {
2837
- hx = poleBg.h;
2838
- sx = poleBg.s;
2839
- }
2901
+ const characters = atob(dataURL.substring(commaIndex + 1));
2902
+ const bytes = new Uint8Array(characters.length);
2903
+ for (let i = 0; i < characters.length; i++) {
2904
+ bytes[i] = characters.charCodeAt(i);
2840
2905
  }
2841
- const lx = scale(l, 0, 1, 0.5, 0.2);
2842
- return {h: hx, s: sx, l: lx, a};
2906
+ return new Blob([bytes], {type: mediaType});
2843
2907
  }
2844
- function _modifyBorderColor(rgb, theme) {
2845
- if (theme.mode === 0) {
2846
- return modifyLightSchemeColor(rgb, theme);
2908
+ async function tryConvertDataURLToBlobURL(dataURL) {
2909
+ if (!isBlobURLSupported) {
2910
+ return null;
2847
2911
  }
2848
- const poleFg = getFgPole(theme);
2849
- const poleBg = getBgPole(theme);
2850
- return modifyColorWithCache(
2851
- rgb,
2852
- {...theme, mode: 0},
2853
- modifyBorderHSL,
2854
- poleFg,
2855
- poleBg
2856
- );
2857
- }
2858
- function modifyBorderColor(rgb, theme, shouldRegisterColorVariable = true) {
2859
- if (!shouldRegisterColorVariable) {
2860
- return _modifyBorderColor(rgb, theme);
2912
+ const hash = getHashCode(dataURL);
2913
+ let blobURL = dataURLBlobURLs.get(hash);
2914
+ if (blobURL) {
2915
+ return blobURL;
2861
2916
  }
2862
- return modifyAndRegisterColor("border", rgb, theme, _modifyBorderColor);
2863
- }
2864
- function modifyShadowColor(rgb, theme) {
2865
- return modifyBackgroundColor(rgb, theme);
2917
+ let blob = tryConvertDataURLToBlobSync(dataURL);
2918
+ if (!blob) {
2919
+ const response = await fetch(dataURL);
2920
+ blob = await response.blob();
2921
+ }
2922
+ blobURL = URL.createObjectURL(blob);
2923
+ dataURLBlobURLs.set(hash, blobURL);
2924
+ return blobURL;
2866
2925
  }
2867
- function modifyGradientColor(rgb, theme) {
2868
- return modifyBackgroundColor(rgb, theme);
2926
+ function cleanImageProcessingCache() {
2927
+ imageManager && imageManager.stop();
2928
+ removeCanvas();
2929
+ objectURLs.forEach((u) => URL.revokeObjectURL(u));
2930
+ objectURLs.clear();
2931
+ dataURLBlobURLs.forEach((u) => URL.revokeObjectURL(u));
2932
+ dataURLBlobURLs.clear();
2869
2933
  }
2870
2934
 
2871
2935
  function getPriority(ruleStyle, property) {
@@ -3126,10 +3190,18 @@ const unparsableColors = new Set([
3126
3190
  "auto"
3127
3191
  ]);
3128
3192
  function getColorModifier(prop, value, rule) {
3129
- if (unparsableColors.has(value.toLowerCase())) {
3193
+ if (
3194
+ unparsableColors.has(value.toLowerCase()) &&
3195
+ !(prop === "color" && value === "initial")
3196
+ ) {
3130
3197
  return value;
3131
3198
  }
3132
- const rgb = parseColorWithCache(value);
3199
+ let rgb = null;
3200
+ if (prop === "color" && value === "initial") {
3201
+ rgb = {r: 0, g: 0, b: 0, a: 1};
3202
+ } else {
3203
+ rgb = parseColorWithCache(value);
3204
+ }
3133
3205
  if (!rgb) {
3134
3206
  logWarn("Couldn't parse color", value);
3135
3207
  return null;
@@ -3531,7 +3603,7 @@ function getScrollbarColorModifier(value) {
3531
3603
  return null;
3532
3604
  }
3533
3605
  return (theme) =>
3534
- `${modifyForegroundColor(thumb, theme)} ${modifyBackgroundColor(thumb, theme)}`;
3606
+ `${modifyForegroundColor(thumb, theme)} ${modifyBackgroundColor(track, theme)}`;
3535
3607
  }
3536
3608
  function getColorSchemeModifier() {
3537
3609
  return (theme) => (theme.mode === 0 ? "dark light" : "dark");
@@ -3882,7 +3954,7 @@ class VariablesStore {
3882
3954
  const modified = modify();
3883
3955
  if (unknownVars.size > 0) {
3884
3956
  const isFallbackResolved = modified.match(
3885
- /^var\(.*?, (var\(--darkreader-bg--.*\))|(#[0-9A-Fa-f]+)|([a-z]+)|(rgba?\(.+\))|(hsla?\(.+\))\)$/
3957
+ /^var\(.*?, ((var\(--darkreader-bg--.*\))|(#[0-9A-Fa-f]+)|([a-z]+)|(rgba?\(.+\))|(hsla?\(.+\)))\)$/
3886
3958
  );
3887
3959
  if (isFallbackResolved) {
3888
3960
  return modified;
@@ -4352,17 +4424,6 @@ function insertVarValues(source, varValues, fullStack = new Set()) {
4352
4424
  return replaced;
4353
4425
  }
4354
4426
 
4355
- const themeCacheKeys = [
4356
- "mode",
4357
- "brightness",
4358
- "contrast",
4359
- "grayscale",
4360
- "sepia",
4361
- "darkSchemeBackgroundColor",
4362
- "darkSchemeTextColor",
4363
- "lightSchemeBackgroundColor",
4364
- "lightSchemeTextColor"
4365
- ];
4366
4427
  function getThemeKey(theme) {
4367
4428
  let resultKey = "";
4368
4429
  themeCacheKeys.forEach((key) => {
@@ -4673,6 +4734,12 @@ function createStyleSheetModifier() {
4673
4734
  function buildStyleSheet() {
4674
4735
  function createTarget(group, parent) {
4675
4736
  const {rule} = group;
4737
+ if (isStyleRule(rule)) {
4738
+ const {selectorText} = rule;
4739
+ const index = parent.cssRules.length;
4740
+ parent.insertRule(`${selectorText} {}`, index);
4741
+ return parent.cssRules[index];
4742
+ }
4676
4743
  if (isMediaRule(rule)) {
4677
4744
  const {media} = rule;
4678
4745
  const index = parent.cssRules.length;
@@ -4973,6 +5040,25 @@ function createAdoptedStyleSheetFallback() {
4973
5040
  return {render, destroy, commands};
4974
5041
  }
4975
5042
 
5043
+ const hostsBreakingOnStylePosition = ["www.diffusioneshop.com", "zhale.me"];
5044
+ const mode = hostsBreakingOnStylePosition.includes(location.hostname)
5045
+ ? "away"
5046
+ : "next";
5047
+ function getStyleInjectionMode() {
5048
+ return mode;
5049
+ }
5050
+ function injectStyleAway(styleElement) {
5051
+ let container = document.body.querySelector(".darkreader-style-container");
5052
+ if (!container) {
5053
+ container = document.createElement("div");
5054
+ container.classList.add("darkreader");
5055
+ container.classList.add("darkreader-style-container");
5056
+ container.style.display = "none";
5057
+ document.body.append(container);
5058
+ }
5059
+ container.append(styleElement);
5060
+ }
5061
+
4976
5062
  const overrides = {
4977
5063
  "background-color": {
4978
5064
  customProp: "--darkreader-inline-bgcolor",
@@ -5738,19 +5824,27 @@ function cleanLoadingLinks() {
5738
5824
  rejectorsForLoadingLinks.clear();
5739
5825
  }
5740
5826
  function manageStyle(element, {update, loadingStart, loadingEnd}) {
5741
- const prevStyles = [];
5742
- let next = element;
5743
- while ((next = next.nextElementSibling) && next.matches(".darkreader")) {
5744
- prevStyles.push(next);
5745
- }
5746
- let corsCopy =
5747
- prevStyles.find(
5748
- (el) => el.matches(".darkreader--cors") && !corsStyleSet.has(el)
5749
- ) || null;
5750
- let syncStyle =
5751
- prevStyles.find(
5752
- (el) => el.matches(".darkreader--sync") && !syncStyleSet.has(el)
5753
- ) || null;
5827
+ const inMode = getStyleInjectionMode();
5828
+ let corsCopy = null;
5829
+ let syncStyle = null;
5830
+ if (inMode === "next") {
5831
+ const prevStyles = [];
5832
+ let next = element;
5833
+ while (
5834
+ (next = next.nextElementSibling) &&
5835
+ next.matches(".darkreader")
5836
+ ) {
5837
+ prevStyles.push(next);
5838
+ }
5839
+ corsCopy =
5840
+ prevStyles.find(
5841
+ (el) => el.matches(".darkreader--cors") && !corsStyleSet.has(el)
5842
+ ) || null;
5843
+ syncStyle =
5844
+ prevStyles.find(
5845
+ (el) => el.matches(".darkreader--sync") && !syncStyleSet.has(el)
5846
+ ) || null;
5847
+ }
5754
5848
  let corsCopyPositionWatcher = null;
5755
5849
  let syncStylePositionWatcher = null;
5756
5850
  let cancelAsyncOperations = false;
@@ -5830,18 +5924,28 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
5830
5924
  return cssRules;
5831
5925
  }
5832
5926
  function insertStyle() {
5833
- if (corsCopy) {
5834
- if (element.nextSibling !== corsCopy) {
5835
- element.parentNode.insertBefore(corsCopy, element.nextSibling);
5927
+ if (inMode === "next") {
5928
+ if (corsCopy) {
5929
+ if (element.nextSibling !== corsCopy) {
5930
+ element.parentNode.insertBefore(
5931
+ corsCopy,
5932
+ element.nextSibling
5933
+ );
5934
+ }
5935
+ if (corsCopy.nextSibling !== syncStyle) {
5936
+ element.parentNode.insertBefore(
5937
+ syncStyle,
5938
+ corsCopy.nextSibling
5939
+ );
5940
+ }
5941
+ } else if (element.nextSibling !== syncStyle) {
5942
+ element.parentNode.insertBefore(syncStyle, element.nextSibling);
5836
5943
  }
5837
- if (corsCopy.nextSibling !== syncStyle) {
5838
- element.parentNode.insertBefore(
5839
- syncStyle,
5840
- corsCopy.nextSibling
5841
- );
5944
+ } else if (inMode === "away") {
5945
+ if (corsCopy && !corsCopy.parentNode) {
5946
+ injectStyleAway(corsCopy);
5842
5947
  }
5843
- } else if (element.nextSibling !== syncStyle) {
5844
- element.parentNode.insertBefore(syncStyle, element.nextSibling);
5948
+ injectStyleAway(syncStyle);
5845
5949
  }
5846
5950
  }
5847
5951
  function createSyncStyle() {
@@ -5899,7 +6003,12 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
5899
6003
  return cssRules;
5900
6004
  }
5901
6005
  }
5902
- cssText = await loadText(element.href);
6006
+ try {
6007
+ cssText = await loadText(element.href);
6008
+ } catch (err) {
6009
+ logWarn(err);
6010
+ cssText = "";
6011
+ }
5903
6012
  cssBasePath = getCSSBaseBath(element.href);
5904
6013
  if (cancelAsyncOperations) {
5905
6014
  return null;
@@ -5930,12 +6039,31 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
5930
6039
  corsCopy.textContent = fullCSSText;
5931
6040
  }
5932
6041
  } else {
5933
- corsCopy = createCORSCopy(element, fullCSSText);
6042
+ corsCopy = createCORSCopy(
6043
+ fullCSSText,
6044
+ inMode === "next"
6045
+ ? (cc) =>
6046
+ element.parentNode.insertBefore(
6047
+ cc,
6048
+ element.nextSibling
6049
+ )
6050
+ : injectStyleAway
6051
+ );
6052
+ if (corsCopy) {
6053
+ if (inMode === "next") {
6054
+ element.parentNode.insertBefore(
6055
+ corsCopy,
6056
+ element.nextSibling
6057
+ );
6058
+ } else if (inMode === "away") {
6059
+ injectStyleAway(corsCopy);
6060
+ }
6061
+ }
5934
6062
  }
5935
6063
  } catch (err) {
5936
6064
  logWarn(err);
5937
6065
  }
5938
- if (corsCopy) {
6066
+ if (corsCopy && inMode === "next") {
5939
6067
  corsCopyPositionWatcher = watchForNodePosition(
5940
6068
  corsCopy,
5941
6069
  "prev-sibling"
@@ -6002,7 +6130,7 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
6002
6130
  removeCSSRulesFromSheet(sheet);
6003
6131
  if (syncStylePositionWatcher) {
6004
6132
  syncStylePositionWatcher.run();
6005
- } else {
6133
+ } else if (inMode === "next") {
6006
6134
  syncStylePositionWatcher = watchForNodePosition(
6007
6135
  syncStyle,
6008
6136
  "prev-sibling",
@@ -6223,7 +6351,7 @@ async function replaceCSSImports(cssText, basePath, cache = new Map()) {
6223
6351
  cssText = cssText.trim();
6224
6352
  return cssText;
6225
6353
  }
6226
- function createCORSCopy(srcElement, cssText) {
6354
+ function createCORSCopy(cssText, inject) {
6227
6355
  if (!cssText) {
6228
6356
  return null;
6229
6357
  }
@@ -6232,7 +6360,7 @@ function createCORSCopy(srcElement, cssText) {
6232
6360
  cors.classList.add("darkreader--cors");
6233
6361
  cors.media = "screen";
6234
6362
  cors.textContent = cssText;
6235
- srcElement.parentNode.insertBefore(cors, srcElement.nextSibling);
6363
+ inject(cors);
6236
6364
  cors.sheet.disabled = true;
6237
6365
  corsStyleSet.add(cors);
6238
6366
  return cors;
@@ -6470,7 +6598,7 @@ function injectProxy(enableStyleSheetsProxy, enableCustomElementRegistryProxy) {
6470
6598
  );
6471
6599
  }
6472
6600
  let blobURLAllowed = null;
6473
- async function checkBlobURLSupport() {
6601
+ function checkBlobURLSupport() {
6474
6602
  if (blobURLAllowed != null) {
6475
6603
  document.dispatchEvent(
6476
6604
  new CustomEvent("__darkreader__blobURLCheckResponse", {
@@ -6487,17 +6615,18 @@ function injectProxy(enableStyleSheetsProxy, enableCustomElementRegistryProxy) {
6487
6615
  }
6488
6616
  const blob = new Blob([bytes], {type: "image/svg+xml"});
6489
6617
  const objectURL = URL.createObjectURL(blob);
6490
- try {
6491
- const image = new Image();
6492
- await new Promise((resolve, reject) => {
6493
- image.onload = () => resolve();
6494
- image.onerror = () => reject();
6495
- image.src = objectURL;
6496
- });
6618
+ const image = new Image();
6619
+ image.onload = () => {
6497
6620
  blobURLAllowed = true;
6498
- } catch (err) {
6621
+ sendBlobURLCheckResponse();
6622
+ };
6623
+ image.onerror = () => {
6499
6624
  blobURLAllowed = false;
6500
- }
6625
+ sendBlobURLCheckResponse();
6626
+ };
6627
+ image.src = objectURL;
6628
+ }
6629
+ function sendBlobURLCheckResponse() {
6501
6630
  document.dispatchEvent(
6502
6631
  new CustomEvent("__darkreader__blobURLCheckResponse", {
6503
6632
  detail: {blobURLAllowed}
@@ -7006,20 +7135,24 @@ let fixes = null;
7006
7135
  let isIFrame$1 = null;
7007
7136
  let ignoredImageAnalysisSelectors = [];
7008
7137
  let ignoredInlineSelectors = [];
7009
- const staticStyleMap = new Map();
7138
+ let staticStyleMap = new WeakMap();
7010
7139
  function createOrUpdateStyle(className, root = document.head || document) {
7011
7140
  let element = root.querySelector(`.${className}`);
7141
+ if (!staticStyleMap.has(root)) {
7142
+ staticStyleMap.set(root, new Map());
7143
+ }
7144
+ const classMap = staticStyleMap.get(root);
7012
7145
  if (element) {
7013
- staticStyleMap.set(className, element);
7014
- } else if (staticStyleMap.has(className)) {
7015
- element = staticStyleMap.get(className);
7146
+ classMap.set(className, element);
7147
+ } else if (classMap.has(className)) {
7148
+ element = classMap.get(className);
7016
7149
  } else {
7017
7150
  element = document.createElement("style");
7018
7151
  element.classList.add("darkreader");
7019
7152
  element.classList.add(className);
7020
7153
  element.media = "screen";
7021
7154
  element.textContent = "";
7022
- staticStyleMap.set(className, element);
7155
+ classMap.set(className, element);
7023
7156
  }
7024
7157
  return element;
7025
7158
  }
@@ -7044,27 +7177,36 @@ function stopStylePositionWatchers() {
7044
7177
  forEach(nodePositionWatchers.values(), (watcher) => watcher.stop());
7045
7178
  nodePositionWatchers.clear();
7046
7179
  }
7180
+ function injectStaticStyle(style, prevNode, watchAlias, callback) {
7181
+ const mode = getStyleInjectionMode();
7182
+ if (mode === "next") {
7183
+ document.head.insertBefore(
7184
+ style,
7185
+ prevNode ? prevNode.nextSibling : document.head.firstChild
7186
+ );
7187
+ setupNodePositionWatcher(style, watchAlias, callback);
7188
+ } else if (mode === "away") {
7189
+ injectStyleAway(style);
7190
+ }
7191
+ }
7047
7192
  function createStaticStyleOverrides() {
7048
7193
  const fallbackStyle = createOrUpdateStyle("darkreader--fallback", document);
7049
7194
  fallbackStyle.textContent = getModifiedFallbackStyle(theme, {strict: true});
7050
- document.head.insertBefore(fallbackStyle, document.head.firstChild);
7051
- setupNodePositionWatcher(fallbackStyle, "fallback");
7195
+ injectStaticStyle(fallbackStyle, null, "fallback");
7052
7196
  const userAgentStyle = createOrUpdateStyle("darkreader--user-agent");
7053
7197
  userAgentStyle.textContent = getModifiedUserAgentStyle(
7054
7198
  theme,
7055
7199
  isIFrame$1,
7056
7200
  theme.styleSystemControls
7057
7201
  );
7058
- document.head.insertBefore(userAgentStyle, fallbackStyle.nextSibling);
7059
- setupNodePositionWatcher(userAgentStyle, "user-agent");
7202
+ injectStaticStyle(userAgentStyle, fallbackStyle, "user-agent");
7060
7203
  const textStyle = createOrUpdateStyle("darkreader--text");
7061
7204
  if (theme.useFont || theme.textStroke > 0) {
7062
7205
  textStyle.textContent = createTextStyle(theme);
7063
7206
  } else {
7064
7207
  textStyle.textContent = "";
7065
7208
  }
7066
- document.head.insertBefore(textStyle, fallbackStyle.nextSibling);
7067
- setupNodePositionWatcher(textStyle, "text");
7209
+ injectStaticStyle(textStyle, userAgentStyle, "text");
7068
7210
  const invertStyle = createOrUpdateStyle("darkreader--invert");
7069
7211
  if (fixes && Array.isArray(fixes.invert) && fixes.invert.length > 0) {
7070
7212
  invertStyle.textContent = [
@@ -7081,17 +7223,10 @@ function createStaticStyleOverrides() {
7081
7223
  } else {
7082
7224
  invertStyle.textContent = "";
7083
7225
  }
7084
- document.head.insertBefore(invertStyle, textStyle.nextSibling);
7085
- setupNodePositionWatcher(invertStyle, "invert");
7226
+ injectStaticStyle(invertStyle, textStyle, "invert");
7086
7227
  const inlineStyle = createOrUpdateStyle("darkreader--inline");
7087
7228
  inlineStyle.textContent = getInlineOverrideStyle();
7088
- document.head.insertBefore(inlineStyle, invertStyle.nextSibling);
7089
- setupNodePositionWatcher(inlineStyle, "inline");
7090
- const overrideStyle = createOrUpdateStyle("darkreader--override");
7091
- overrideStyle.textContent =
7092
- fixes && fixes.css ? replaceCSSTemplates(fixes.css) : "";
7093
- document.head.appendChild(overrideStyle);
7094
- setupNodePositionWatcher(overrideStyle, "override");
7229
+ injectStaticStyle(inlineStyle, invertStyle, "inline");
7095
7230
  const variableStyle = createOrUpdateStyle("darkreader--variables");
7096
7231
  const selectionColors = theme?.selectionColor
7097
7232
  ? getSelectionColor(theme)
@@ -7112,13 +7247,12 @@ function createStaticStyleOverrides() {
7112
7247
  ` --darkreader-selection-text: ${selectionColors?.foregroundColorSelection ?? "initial"};`,
7113
7248
  `}`
7114
7249
  ].join("\n");
7115
- document.head.insertBefore(variableStyle, inlineStyle.nextSibling);
7116
- setupNodePositionWatcher(variableStyle, "variables", () =>
7250
+ injectStaticStyle(variableStyle, inlineStyle, "variables", () =>
7117
7251
  registerVariablesSheet(variableStyle.sheet)
7118
7252
  );
7119
7253
  registerVariablesSheet(variableStyle.sheet);
7120
7254
  const rootVarsStyle = createOrUpdateStyle("darkreader--root-vars");
7121
- document.head.insertBefore(rootVarsStyle, variableStyle.nextSibling);
7255
+ injectStaticStyle(rootVarsStyle, variableStyle, "root-vars");
7122
7256
  const enableStyleSheetsProxy = !(fixes && fixes.disableStyleSheetsProxy);
7123
7257
  const enableCustomElementRegistryProxy = !(
7124
7258
  fixes && fixes.disableCustomElementRegistryProxy
@@ -7132,6 +7266,10 @@ function createStaticStyleOverrides() {
7132
7266
  document.head.insertBefore(proxyScript, rootVarsStyle.nextSibling);
7133
7267
  proxyScript.remove();
7134
7268
  }
7269
+ const overrideStyle = createOrUpdateStyle("darkreader--override");
7270
+ overrideStyle.textContent =
7271
+ fixes && fixes.css ? replaceCSSTemplates(fixes.css) : "";
7272
+ injectStaticStyle(overrideStyle, document.head.lastChild, "override");
7135
7273
  }
7136
7274
  const shadowRootsWithOverrides = new Set();
7137
7275
  function createShadowStaticStyleOverridesInner(root) {
@@ -7207,7 +7345,8 @@ function replaceCSSTemplates($cssText) {
7207
7345
  }
7208
7346
  function cleanFallbackStyle() {
7209
7347
  const fallback =
7210
- staticStyleMap.get("darkreader--fallback") ||
7348
+ staticStyleMap.get(document.head)?.get("darkreader--fallback") ||
7349
+ staticStyleMap.get(document)?.get("darkreader--fallback") ||
7211
7350
  document.querySelector(".darkreader--fallback");
7212
7351
  if (fallback) {
7213
7352
  fallback.textContent = "";
@@ -7736,7 +7875,7 @@ function removeDynamicTheme() {
7736
7875
  selectors.forEach((selector) =>
7737
7876
  removeNode(document.head.querySelector(selector))
7738
7877
  );
7739
- staticStyleMap.clear();
7878
+ staticStyleMap = new WeakMap();
7740
7879
  removeProxy();
7741
7880
  }
7742
7881
  shadowRootsWithOverrides.forEach((root) => {