darkreader 4.9.82 → 4.9.84

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.
package/README.md CHANGED
@@ -43,7 +43,7 @@ Please note that if you encounter error `Too many open files (os error 24)`, the
43
43
 
44
44
  ### Bundling with official Firefox store signatures (experimental)
45
45
 
46
- Prior to publication, extension stores provide digital signatures for extensions. These digital signatures certify the integrity of the archive (that extension bundle did not get corrupted or bit-rotted) and that extension store preformed very basic extension validation.
46
+ Prior to publication, extension stores provide digital signatures for extensions. These digital signatures certify the integrity of the archive (that extension bundle did not get corrupted or bit-rotted) and that extension store performed very basic extension validation.
47
47
 
48
48
  Dark Reader repository contains these digital signatures and you can add them to the extension bundle. The following will build Dark Reader for Firefox version 4.9.63:
49
49
  ```
@@ -147,7 +147,7 @@ Step 1: change Dark Reader's settings.
147
147
  - Click on the Dark Reader icon.
148
148
  - Click on the `Dev tools` button (in the bottom-right corner).
149
149
  - Click on the `Preview new design button`.
150
- - Enable the `Enable on restricted pages` setting under `Settings` -> `Site list`.
150
+ - Enable the `Enable on restricted pages` setting under `Settings` -> `Advanced`.
151
151
 
152
152
  Step 2: change Firefox's settings.
153
153
 
package/darkreader.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Dark Reader v4.9.82
2
+ * Dark Reader v4.9.84
3
3
  * https://darkreader.org/
4
4
  */
5
5
 
@@ -127,6 +127,7 @@
127
127
  const isMatchMediaChangeEventListenerSupported =
128
128
  typeof MediaQueryList === "function" &&
129
129
  typeof MediaQueryList.prototype.addEventListener === "function";
130
+ const isLayerRuleSupported = typeof CSSLayerBlockRule === "function";
130
131
  (() => {
131
132
  const m = userAgent.match(/chrom(?:e|ium)(?:\/| )([^ ]+)/);
132
133
  if (m && m[1]) {
@@ -212,6 +213,10 @@
212
213
  });
213
214
  return dataURL;
214
215
  }
216
+ async function loadAsText(url, mimeType, origin) {
217
+ const response = await getOKResponse(url, mimeType, origin);
218
+ return await response.text();
219
+ }
215
220
 
216
221
  const throwCORSError = async (url) => {
217
222
  return Promise.reject(
@@ -785,6 +790,15 @@
785
790
  }
786
791
  return matches;
787
792
  }
793
+ function getHashCode(text) {
794
+ const len = text.length;
795
+ let hash = 0;
796
+ for (let i = 0; i < len; i++) {
797
+ const c = text.charCodeAt(i);
798
+ hash = ((hash << 5) - hash + c) & 4294967295;
799
+ }
800
+ return hash;
801
+ }
788
802
  function escapeRegExpSpecialChars(input) {
789
803
  return input.replaceAll(/[\^$.*+?\(\)\[\]{}|\-\\]/g, "\\$&");
790
804
  }
@@ -1143,7 +1157,7 @@
1143
1157
  if (layerRules.has(rule)) {
1144
1158
  return true;
1145
1159
  }
1146
- if (rule instanceof CSSLayerBlockRule) {
1160
+ if (isLayerRuleSupported && rule instanceof CSSLayerBlockRule) {
1147
1161
  layerRules.add(rule);
1148
1162
  return true;
1149
1163
  }
@@ -2255,8 +2269,7 @@
2255
2269
  const analysis = analyzeImage(image);
2256
2270
  resolve({
2257
2271
  src: url,
2258
- blob,
2259
- dataURL,
2272
+ dataURL: analysis.isLarge ? "" : dataURL,
2260
2273
  width: image.width,
2261
2274
  height: image.height,
2262
2275
  ...analysis
@@ -2336,18 +2349,10 @@
2336
2349
  isDark: false,
2337
2350
  isLight: false,
2338
2351
  isTransparent: false,
2339
- isLarge: false,
2340
- isTooLarge: false
2341
- };
2342
- }
2343
- if (sw * sh > LARGE_IMAGE_PIXELS_COUNT) {
2344
- return {
2345
- isDark: false,
2346
- isLight: false,
2347
- isTransparent: false,
2348
- isLarge: true
2352
+ isLarge: false
2349
2353
  };
2350
2354
  }
2355
+ const isLarge = sw * sh > LARGE_IMAGE_PIXELS_COUNT;
2351
2356
  const sourcePixelsCount = sw * sh;
2352
2357
  const k = Math.min(
2353
2358
  1,
@@ -2400,7 +2405,7 @@
2400
2405
  isTransparent:
2401
2406
  transparentPixelsCount / totalPixelsCount >=
2402
2407
  TRANSPARENT_IMAGE_THRESHOLD,
2403
- isLarge: false
2408
+ isLarge
2404
2409
  };
2405
2410
  }
2406
2411
  let isBlobURLSupported = null;
@@ -2509,7 +2514,8 @@
2509
2514
  if (!isBlobURLSupported) {
2510
2515
  return null;
2511
2516
  }
2512
- let blobURL = dataURLBlobURLs.get(dataURL);
2517
+ const hash = getHashCode(dataURL);
2518
+ let blobURL = dataURLBlobURLs.get(hash);
2513
2519
  if (blobURL) {
2514
2520
  return blobURL;
2515
2521
  }
@@ -2519,7 +2525,7 @@
2519
2525
  blob = await response.blob();
2520
2526
  }
2521
2527
  blobURL = URL.createObjectURL(blob);
2522
- dataURLBlobURLs.set(dataURL, blobURL);
2528
+ dataURLBlobURLs.set(hash, blobURL);
2523
2529
  return blobURL;
2524
2530
  }
2525
2531
  function cleanImageProcessingCache() {
@@ -2693,7 +2699,7 @@
2693
2699
  );
2694
2700
  lines.push("}");
2695
2701
  }
2696
- if (isCSSColorSchemePropSupported && !isIFrame && theme.mode === 1) {
2702
+ if (isCSSColorSchemePropSupported && theme.mode === 1) {
2697
2703
  lines.push("html {");
2698
2704
  lines.push(` color-scheme: dark !important;`);
2699
2705
  lines.push("}");
@@ -2732,6 +2738,11 @@
2732
2738
  ` border-color: ${modifyBorderColor({r: 128, g: 128, b: 128}, theme)};`
2733
2739
  );
2734
2740
  lines.push("}");
2741
+ lines.push("mark {");
2742
+ lines.push(
2743
+ ` color: ${modifyForegroundColor({r: 0, g: 0, b: 0}, theme)};`
2744
+ );
2745
+ lines.push("}");
2735
2746
  lines.push("::placeholder {");
2736
2747
  lines.push(
2737
2748
  ` color: ${modifyForegroundColor({r: 169, g: 169, b: 169}, theme)};`
@@ -2753,6 +2764,10 @@
2753
2764
  if (theme.selectionColor) {
2754
2765
  lines.push(getModifiedSelectionStyle(theme));
2755
2766
  }
2767
+ if (isLayerRuleSupported) {
2768
+ lines.unshift("@layer {");
2769
+ lines.push("}");
2770
+ }
2756
2771
  return lines.join("\n");
2757
2772
  }
2758
2773
  function getSelectionColor(theme) {
@@ -3087,9 +3102,9 @@
3087
3102
  const logSrc = imageDetails.src.startsWith("data:")
3088
3103
  ? "data:"
3089
3104
  : imageDetails.src;
3090
- if (isLarge) {
3091
- logInfo(`Not modifying too large image ${logSrc}`);
3092
- result = null;
3105
+ if (isLarge && isLight && !isTransparent && theme.mode === 1) {
3106
+ logInfo(`Hiding large light image ${logSrc}`);
3107
+ result = "none";
3093
3108
  } else if (
3094
3109
  isDark &&
3095
3110
  isTransparent &&
@@ -4142,6 +4157,11 @@
4142
4157
  "}"
4143
4158
  ].join("\n");
4144
4159
  })
4160
+ .concat([
4161
+ "[data-darkreader-inline-invert] {",
4162
+ " filter: invert(100%) hue-rotate(180deg);",
4163
+ "}"
4164
+ ])
4145
4165
  .join("\n");
4146
4166
  }
4147
4167
  function getInlineStyleElements(root) {
@@ -4279,7 +4299,23 @@
4279
4299
  attrObservers.clear();
4280
4300
  }
4281
4301
  const inlineStyleCache = new WeakMap();
4302
+ const svgInversionCache = new WeakSet();
4303
+ const svgAnalysisConditionCache = new WeakMap();
4282
4304
  const themeProps = ["brightness", "contrast", "grayscale", "sepia", "mode"];
4305
+ function shouldAnalyzeSVGAsImage(svg) {
4306
+ if (svgAnalysisConditionCache.has(svg)) {
4307
+ return svgAnalysisConditionCache.get(svg);
4308
+ }
4309
+ const shouldAnalyze = Boolean(
4310
+ svg &&
4311
+ (svg.role === "img" ||
4312
+ svg.parentElement?.role === "img" ||
4313
+ svg.getAttribute("class")?.includes("logo") ||
4314
+ svg.parentElement?.getAttribute("class")?.includes("logo"))
4315
+ );
4316
+ svgAnalysisConditionCache.set(svg, shouldAnalyze);
4317
+ return shouldAnalyze;
4318
+ }
4283
4319
  function getInlineStyleCacheKey(el, theme) {
4284
4320
  return INLINE_STYLE_ATTRS.map(
4285
4321
  (attr) => `${attr}="${el.getAttribute(attr)}"`
@@ -4388,6 +4424,46 @@
4388
4424
  return;
4389
4425
  }
4390
4426
  }
4427
+ const isSVGElement = element instanceof SVGElement;
4428
+ const svg = isSVGElement
4429
+ ? element.ownerSVGElement ??
4430
+ (element instanceof SVGSVGElement ? element : null)
4431
+ : null;
4432
+ if (isSVGElement && theme.mode === 1 && svg) {
4433
+ if (svgInversionCache.has(svg)) {
4434
+ return;
4435
+ }
4436
+ if (shouldAnalyzeSVGAsImage(svg)) {
4437
+ svgInversionCache.add(svg);
4438
+ const handleSVGRoot = () => {
4439
+ let svgString = svg.outerHTML;
4440
+ svgString = svgString.replaceAll(
4441
+ '<style class="darkreader darkreader--sync" media="screen"></style>',
4442
+ ""
4443
+ );
4444
+ const dataURL = `data:image/svg+xml;base64,${btoa(svgString)}`;
4445
+ getImageDetails(dataURL).then((details) => {
4446
+ if (
4447
+ (details.isDark && details.isTransparent) ||
4448
+ (details.isLarge &&
4449
+ details.isLight &&
4450
+ !details.isTransparent)
4451
+ ) {
4452
+ svg.setAttribute(
4453
+ "data-darkreader-inline-invert",
4454
+ ""
4455
+ );
4456
+ }
4457
+ });
4458
+ };
4459
+ if (isReadyStateComplete()) {
4460
+ handleSVGRoot();
4461
+ } else {
4462
+ addReadyStateCompleteListener(handleSVGRoot);
4463
+ }
4464
+ return;
4465
+ }
4466
+ }
4391
4467
  if (element.hasAttribute("bgcolor")) {
4392
4468
  let value = element.getAttribute("bgcolor");
4393
4469
  if (
@@ -4420,7 +4496,7 @@
4420
4496
  }
4421
4497
  setCustomProp("color", "color", value);
4422
4498
  }
4423
- if (element instanceof SVGElement) {
4499
+ if (isSVGElement) {
4424
4500
  if (element.hasAttribute("fill")) {
4425
4501
  const SMALL_SVG_LIMIT = 32;
4426
4502
  const value = element.getAttribute("fill");
@@ -4602,6 +4678,13 @@
4602
4678
  const asyncQueue = createAsyncTasksQueue();
4603
4679
  function createStyleSheetModifier() {
4604
4680
  let renderId = 0;
4681
+ function getStyleRuleHash(rule) {
4682
+ let cssText = rule.cssText;
4683
+ if (isMediaRule(rule.parentRule)) {
4684
+ cssText = `${rule.parentRule.media.mediaText} { ${cssText} }`;
4685
+ }
4686
+ return getHashCode(cssText);
4687
+ }
4605
4688
  const rulesTextCache = new Set();
4606
4689
  const rulesModCache = new Map();
4607
4690
  const varTypeChangeCleaners = new Set();
@@ -4631,23 +4714,17 @@
4631
4714
  iterateCSSRules(
4632
4715
  rules,
4633
4716
  (rule) => {
4634
- let cssText = rule.cssText;
4717
+ const hash = getStyleRuleHash(rule);
4635
4718
  let textDiffersFromPrev = false;
4636
- notFoundCacheKeys.delete(cssText);
4637
- if (isMediaRule(rule.parentRule)) {
4638
- cssText += `;${rule.parentRule.media.mediaText}`;
4639
- }
4640
- if (isLayerRule(rule.parentRule)) {
4641
- cssText += `;${rule.parentRule.name}`;
4642
- }
4643
- if (!rulesTextCache.has(cssText)) {
4644
- rulesTextCache.add(cssText);
4719
+ notFoundCacheKeys.delete(hash);
4720
+ if (!rulesTextCache.has(hash)) {
4721
+ rulesTextCache.add(hash);
4645
4722
  textDiffersFromPrev = true;
4646
4723
  }
4647
4724
  if (textDiffersFromPrev) {
4648
4725
  rulesChanged = true;
4649
4726
  } else {
4650
- modRules.push(rulesModCache.get(cssText));
4727
+ modRules.push(rulesModCache.get(hash));
4651
4728
  return;
4652
4729
  }
4653
4730
  if (rule.style.all === "revert") {
@@ -4681,7 +4758,7 @@
4681
4758
  };
4682
4759
  modRules.push(modRule);
4683
4760
  }
4684
- rulesModCache.set(cssText, modRule);
4761
+ rulesModCache.set(hash, modRule);
4685
4762
  },
4686
4763
  () => {
4687
4764
  hasNonLoadedLink = true;
@@ -5539,11 +5616,15 @@
5539
5616
  if (url.startsWith("data:")) {
5540
5617
  return await (await fetch(url)).text();
5541
5618
  }
5619
+ const parsedURL = new URL(url);
5620
+ if (parsedURL.origin === location.origin) {
5621
+ return await loadAsText(url, "text/css", location.origin);
5622
+ }
5542
5623
  return await bgFetch({
5543
5624
  url,
5544
5625
  responseType: "text",
5545
5626
  mimeType: "text/css",
5546
- origin: window.location.origin
5627
+ origin: location.origin
5547
5628
  });
5548
5629
  }
5549
5630
  async function replaceCSSImports(cssText, basePath, cache = new Map()) {
package/index.d.ts CHANGED
@@ -153,7 +153,7 @@ declare namespace DarkReader {
153
153
 
154
154
  /**
155
155
  * A toggle to disable the proxying of `document.styleSheets`.
156
- * This is a API-Exclusive option, as it can break legitmate websites,
156
+ * This is a API-Exclusive option, as it can break legitimate websites,
157
157
  * who are using the Dark Reader API.
158
158
  */
159
159
  disableStyleSheetsProxy: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "darkreader",
3
- "version": "4.9.82",
3
+ "version": "4.9.84",
4
4
  "description": "Dark mode for every website",
5
5
  "scripts": {
6
6
  "api": "node --max-old-space-size=3072 tasks/cli.js build --api",
@@ -58,16 +58,16 @@
58
58
  "@rollup/plugin-node-resolve": "15.2.3",
59
59
  "@rollup/plugin-replace": "5.0.5",
60
60
  "@rollup/plugin-typescript": "11.1.6",
61
- "@types/chrome": "0.0.263",
62
- "@types/eslint": "8.56.6",
61
+ "@types/chrome": "0.0.266",
62
+ "@types/eslint": "8.56.7",
63
63
  "@types/jasmine": "5.1.4",
64
64
  "@types/jest": "29.5.12",
65
65
  "@types/karma": "6.3.8",
66
66
  "@types/karma-coverage": "2.0.3",
67
- "@types/node": "20.12.2",
67
+ "@types/node": "20.12.6",
68
68
  "@types/ws": "8.5.10",
69
- "@typescript-eslint/eslint-plugin": "7.4.0",
70
- "@typescript-eslint/parser": "7.4.0",
69
+ "@typescript-eslint/eslint-plugin": "7.6.0",
70
+ "@typescript-eslint/parser": "7.6.0",
71
71
  "chokidar": "3.6.0",
72
72
  "eslint": "8.57.0",
73
73
  "eslint-plugin-compat": "4.2.0",
@@ -88,12 +88,12 @@
88
88
  "less": "4.2.0",
89
89
  "malevic": "0.20.1",
90
90
  "prettier": "3.2.5",
91
- "puppeteer-core": "22.6.1",
92
- "rollup": "4.13.2",
91
+ "puppeteer-core": "22.6.3",
92
+ "rollup": "4.14.1",
93
93
  "rollup-plugin-istanbul": "5.0.0",
94
94
  "ts-jest": "29.1.2",
95
95
  "tslib": "2.6.2",
96
- "typescript": "5.4.3",
96
+ "typescript": "5.4.4",
97
97
  "web-ext": "7.11.0",
98
98
  "ws": "8.16.0",
99
99
  "yazl": "2.5.1"