darkreader 4.9.83 → 4.9.85

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.83
2
+ * Dark Reader v4.9.85
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]) {
@@ -1040,12 +1041,14 @@
1040
1041
  escapedSelector = escapedSelector.replaceAll(/\s+/g, "\\s*");
1041
1042
  escapedSelector = escapedSelector.replaceAll(/::/g, "::?");
1042
1043
  const regexp = new RegExp(
1043
- `${escapedSelector}\\s*{[^}]*?${shorthand}:\\s*([^;}]+)`
1044
+ `${escapedSelector}\\s*{[^}]*${shorthand}:\\s*([^;}]+)`
1044
1045
  );
1045
1046
  const match = sourceCSSText.match(regexp);
1046
1047
  if (match) {
1047
1048
  iterate(shorthand, match[1]);
1048
1049
  }
1050
+ } else if (shorthand === "background") {
1051
+ iterate("background-color", "#ffffff");
1049
1052
  }
1050
1053
  }
1051
1054
  }
@@ -1068,9 +1071,11 @@
1068
1071
  }
1069
1072
  function replaceCSSRelativeURLsWithAbsolute($css, cssBasePath) {
1070
1073
  return $css.replace(cssURLRegex, (match) => {
1071
- const pathValue = getCSSURLValue(match);
1072
1074
  try {
1073
- return `url('${getAbsoluteURL(cssBasePath, pathValue)}')`;
1075
+ const url = getCSSURLValue(match);
1076
+ const absoluteURL = getAbsoluteURL(cssBasePath, url);
1077
+ const escapedURL = absoluteURL.replaceAll("'", "\\'");
1078
+ return `url('${escapedURL}')`;
1074
1079
  } catch (err) {
1075
1080
  return match;
1076
1081
  }
@@ -1156,7 +1161,7 @@
1156
1161
  if (layerRules.has(rule)) {
1157
1162
  return true;
1158
1163
  }
1159
- if (rule instanceof CSSLayerBlockRule) {
1164
+ if (isLayerRuleSupported && rule instanceof CSSLayerBlockRule) {
1160
1165
  layerRules.add(rule);
1161
1166
  return true;
1162
1167
  }
@@ -2104,11 +2109,38 @@
2104
2109
  return modifyBackgroundColor(rgb, theme);
2105
2110
  }
2106
2111
 
2112
+ const excludedSelectors = [
2113
+ "pre",
2114
+ "pre *",
2115
+ "code",
2116
+ '[aria-hidden="true"]',
2117
+ '[class*="fa-"]',
2118
+ ".fa",
2119
+ ".fab",
2120
+ ".fad",
2121
+ ".fal",
2122
+ ".far",
2123
+ ".fas",
2124
+ ".fass",
2125
+ ".fasr",
2126
+ ".fat",
2127
+ ".icofont",
2128
+ '[style*="font-"]',
2129
+ '[class*="icon"]',
2130
+ '[class*="Icon"]',
2131
+ '[class*="symbol"]',
2132
+ '[class*="Symbol"]',
2133
+ ".glyphicon",
2134
+ '[class*="material-symbol"]',
2135
+ '[class*="material-icon"]',
2136
+ "mu",
2137
+ '[class*="mu-"]',
2138
+ ".typcn",
2139
+ '[class*="vjs-"]'
2140
+ ];
2107
2141
  function createTextStyle(config) {
2108
2142
  const lines = [];
2109
- lines.push(
2110
- '*:not(pre, pre *, code, .far, .fa, .glyphicon, [class*="vjs-"], .fab, .fa-github, .fas, .material-icons, .icofont, .typcn, mu, [class*="mu-"], .glyphicon, .icon) {'
2111
- );
2143
+ lines.push(`*:not(${excludedSelectors.join(", ")}) {`);
2112
2144
  if (config.useFont && config.fontFamily) {
2113
2145
  lines.push(` font-family: ${config.fontFamily} !important;`);
2114
2146
  }
@@ -2348,18 +2380,10 @@
2348
2380
  isDark: false,
2349
2381
  isLight: false,
2350
2382
  isTransparent: false,
2351
- isLarge: false,
2352
- isTooLarge: false
2353
- };
2354
- }
2355
- if (sw * sh > LARGE_IMAGE_PIXELS_COUNT) {
2356
- return {
2357
- isDark: false,
2358
- isLight: false,
2359
- isTransparent: false,
2360
- isLarge: true
2383
+ isLarge: false
2361
2384
  };
2362
2385
  }
2386
+ const isLarge = sw * sh > LARGE_IMAGE_PIXELS_COUNT;
2363
2387
  const sourcePixelsCount = sw * sh;
2364
2388
  const k = Math.min(
2365
2389
  1,
@@ -2412,7 +2436,7 @@
2412
2436
  isTransparent:
2413
2437
  transparentPixelsCount / totalPixelsCount >=
2414
2438
  TRANSPARENT_IMAGE_THRESHOLD,
2415
- isLarge: false
2439
+ isLarge
2416
2440
  };
2417
2441
  }
2418
2442
  let isBlobURLSupported = null;
@@ -2745,6 +2769,11 @@
2745
2769
  ` border-color: ${modifyBorderColor({r: 128, g: 128, b: 128}, theme)};`
2746
2770
  );
2747
2771
  lines.push("}");
2772
+ lines.push("mark {");
2773
+ lines.push(
2774
+ ` color: ${modifyForegroundColor({r: 0, g: 0, b: 0}, theme)};`
2775
+ );
2776
+ lines.push("}");
2748
2777
  lines.push("::placeholder {");
2749
2778
  lines.push(
2750
2779
  ` color: ${modifyForegroundColor({r: 169, g: 169, b: 169}, theme)};`
@@ -2766,6 +2795,10 @@
2766
2795
  if (theme.selectionColor) {
2767
2796
  lines.push(getModifiedSelectionStyle(theme));
2768
2797
  }
2798
+ if (isLayerRuleSupported) {
2799
+ lines.unshift("@layer {");
2800
+ lines.push("}");
2801
+ }
2769
2802
  return lines.join("\n");
2770
2803
  }
2771
2804
  function getSelectionColor(theme) {
@@ -3100,9 +3133,9 @@
3100
3133
  const logSrc = imageDetails.src.startsWith("data:")
3101
3134
  ? "data:"
3102
3135
  : imageDetails.src;
3103
- if (isLarge) {
3104
- logInfo(`Not modifying too large image ${logSrc}`);
3105
- result = null;
3136
+ if (isLarge && isLight && !isTransparent && theme.mode === 1) {
3137
+ logInfo(`Hiding large light image ${logSrc}`);
3138
+ result = "none";
3106
3139
  } else if (
3107
3140
  isDark &&
3108
3141
  isTransparent &&
@@ -3518,7 +3551,11 @@
3518
3551
  };
3519
3552
  }
3520
3553
  getModifierForVarDependant(property, sourceValue) {
3521
- if (sourceValue.match(/^\s*(rgb|hsl)a?\(/)) {
3554
+ const isConstructedColor = sourceValue.match(/^\s*(rgb|hsl)a?\(/);
3555
+ const isSimpleConstructedColor = sourceValue.match(
3556
+ /^rgba?\(var\(--[\-_A-Za-z0-9]+\)(\s*,?\/?\s*0?\.\d+)?\)$/
3557
+ );
3558
+ if (isConstructedColor && !isSimpleConstructedColor) {
3522
3559
  const isBg = property.startsWith("background");
3523
3560
  const isText = isTextColorProperty(property);
3524
3561
  return (theme) => {
@@ -3537,21 +3574,34 @@
3537
3574
  return modifier(value, theme);
3538
3575
  };
3539
3576
  }
3540
- if (property === "background-color") {
3577
+ if (
3578
+ property === "background-color" ||
3579
+ (isSimpleConstructedColor && property === "background")
3580
+ ) {
3541
3581
  return (theme) => {
3582
+ const defaultFallback = tryModifyBgColor(
3583
+ isConstructedColor ? "255, 255, 255" : "#ffffff",
3584
+ theme
3585
+ );
3542
3586
  return replaceCSSVariablesNames(
3543
3587
  sourceValue,
3544
3588
  (v) => wrapBgColorVariableName(v),
3545
- (fallback) => tryModifyBgColor(fallback, theme)
3589
+ (fallback) => tryModifyBgColor(fallback, theme),
3590
+ defaultFallback
3546
3591
  );
3547
3592
  };
3548
3593
  }
3549
3594
  if (isTextColorProperty(property)) {
3550
3595
  return (theme) => {
3596
+ const defaultFallback = tryModifyTextColor(
3597
+ isConstructedColor ? "0, 0, 0" : "#000000",
3598
+ theme
3599
+ );
3551
3600
  return replaceCSSVariablesNames(
3552
3601
  sourceValue,
3553
3602
  (v) => wrapTextColorVariableName(v),
3554
- (fallback) => tryModifyTextColor(fallback, theme)
3603
+ (fallback) => tryModifyTextColor(fallback, theme),
3604
+ defaultFallback
3555
3605
  );
3556
3606
  };
3557
3607
  }
@@ -3904,7 +3954,9 @@
3904
3954
  return input;
3905
3955
  }
3906
3956
  const inputLength = input.length;
3907
- const replacements = matches.map((m) => replacer(m.value));
3957
+ const replacements = matches.map((m) =>
3958
+ replacer(m.value, matches.length)
3959
+ );
3908
3960
  const parts = [];
3909
3961
  parts.push(input.substring(0, matches[0].start));
3910
3962
  for (let i = 0; i < matchesCount; i++) {
@@ -3929,11 +3981,19 @@
3929
3981
  }
3930
3982
  return {name, fallback};
3931
3983
  }
3932
- function replaceCSSVariablesNames(value, nameReplacer, fallbackReplacer) {
3984
+ function replaceCSSVariablesNames(
3985
+ value,
3986
+ nameReplacer,
3987
+ fallbackReplacer,
3988
+ finalFallback
3989
+ ) {
3933
3990
  const matchReplacer = (match) => {
3934
3991
  const {name, fallback} = getVariableNameAndFallback(match);
3935
3992
  const newName = nameReplacer(name);
3936
3993
  if (!fallback) {
3994
+ if (finalFallback) {
3995
+ return `var(${newName}, ${finalFallback})`;
3996
+ }
3937
3997
  return `var(${newName})`;
3938
3998
  }
3939
3999
  let newFallback;
@@ -3977,7 +4037,10 @@
3977
4037
  return value.includes("var(");
3978
4038
  }
3979
4039
  function isConstructedColorVar(value) {
3980
- return value.match(/^\s*(rgb|hsl)a?\(/);
4040
+ return (
4041
+ value.match(/^\s*(rgb|hsl)a?\(/) ||
4042
+ value.match(/^(((\d{1,3})|(var\([\-_A-Za-z0-9]+\))),?\s*?){3}$/)
4043
+ );
3981
4044
  }
3982
4045
  function isTextColorProperty(property) {
3983
4046
  return (
@@ -4021,10 +4084,11 @@
4021
4084
  function tryModifyBorderColor(color, theme) {
4022
4085
  return handleRawColorValue(color, theme, modifyBorderColor);
4023
4086
  }
4024
- function insertVarValues(source, varValues, stack = new Set()) {
4087
+ function insertVarValues(source, varValues, fullStack = new Set()) {
4025
4088
  let containsUnresolvedVar = false;
4026
- const matchReplacer = (match) => {
4089
+ const matchReplacer = (match, count) => {
4027
4090
  const {name, fallback} = getVariableNameAndFallback(match);
4091
+ const stack = count > 1 ? new Set(fullStack) : fullStack;
4028
4092
  if (stack.has(name)) {
4029
4093
  containsUnresolvedVar = true;
4030
4094
  return null;
@@ -4155,6 +4219,11 @@
4155
4219
  "}"
4156
4220
  ].join("\n");
4157
4221
  })
4222
+ .concat([
4223
+ "[data-darkreader-inline-invert] {",
4224
+ " filter: invert(100%) hue-rotate(180deg);",
4225
+ "}"
4226
+ ])
4158
4227
  .join("\n");
4159
4228
  }
4160
4229
  function getInlineStyleElements(root) {
@@ -4292,7 +4361,21 @@
4292
4361
  attrObservers.clear();
4293
4362
  }
4294
4363
  const inlineStyleCache = new WeakMap();
4364
+ const svgInversionCache = new WeakSet();
4365
+ const svgAnalysisConditionCache = new WeakMap();
4295
4366
  const themeProps = ["brightness", "contrast", "grayscale", "sepia", "mode"];
4367
+ function shouldAnalyzeSVGAsImage(svg) {
4368
+ if (svgAnalysisConditionCache.has(svg)) {
4369
+ return svgAnalysisConditionCache.get(svg);
4370
+ }
4371
+ const shouldAnalyze = Boolean(
4372
+ svg &&
4373
+ (svg.getAttribute("class")?.includes("logo") ||
4374
+ svg.parentElement?.getAttribute("class")?.includes("logo"))
4375
+ );
4376
+ svgAnalysisConditionCache.set(svg, shouldAnalyze);
4377
+ return shouldAnalyze;
4378
+ }
4296
4379
  function getInlineStyleCacheKey(el, theme) {
4297
4380
  return INLINE_STYLE_ATTRS.map(
4298
4381
  (attr) => `${attr}="${el.getAttribute(attr)}"`
@@ -4401,6 +4484,49 @@
4401
4484
  return;
4402
4485
  }
4403
4486
  }
4487
+ const isSVGElement = element instanceof SVGElement;
4488
+ const svg = isSVGElement
4489
+ ? element.ownerSVGElement ??
4490
+ (element instanceof SVGSVGElement ? element : null)
4491
+ : null;
4492
+ if (isSVGElement && theme.mode === 1 && svg) {
4493
+ if (svgInversionCache.has(svg)) {
4494
+ return;
4495
+ }
4496
+ if (shouldAnalyzeSVGAsImage(svg)) {
4497
+ svgInversionCache.add(svg);
4498
+ const analyzeSVGAsImage = () => {
4499
+ let svgString = svg.outerHTML;
4500
+ svgString = svgString.replaceAll(
4501
+ '<style class="darkreader darkreader--sync" media="screen"></style>',
4502
+ ""
4503
+ );
4504
+ const dataURL = `data:image/svg+xml;base64,${btoa(svgString)}`;
4505
+ getImageDetails(dataURL).then((details) => {
4506
+ if (
4507
+ (details.isDark && details.isTransparent) ||
4508
+ (details.isLarge &&
4509
+ details.isLight &&
4510
+ !details.isTransparent)
4511
+ ) {
4512
+ svg.setAttribute(
4513
+ "data-darkreader-inline-invert",
4514
+ ""
4515
+ );
4516
+ } else {
4517
+ svg.removeAttribute(
4518
+ "data-darkreader-inline-invert"
4519
+ );
4520
+ }
4521
+ });
4522
+ };
4523
+ analyzeSVGAsImage();
4524
+ if (!isDOMReady()) {
4525
+ addDOMReadyListener(analyzeSVGAsImage);
4526
+ }
4527
+ return;
4528
+ }
4529
+ }
4404
4530
  if (element.hasAttribute("bgcolor")) {
4405
4531
  let value = element.getAttribute("bgcolor");
4406
4532
  if (
@@ -4433,7 +4559,7 @@
4433
4559
  }
4434
4560
  setCustomProp("color", "color", value);
4435
4561
  }
4436
- if (element instanceof SVGElement) {
4562
+ if (isSVGElement) {
4437
4563
  if (element.hasAttribute("fill")) {
4438
4564
  const SMALL_SVG_LIMIT = 32;
4439
4565
  const value = element.getAttribute("fill");
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.83",
3
+ "version": "4.9.85",
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",
@@ -9,6 +9,7 @@
9
9
  "code-style": "npm run lint",
10
10
  "debug": "node --max-old-space-size=3072 tasks/cli.js build --debug",
11
11
  "debug:watch": "node --max-old-space-size=3072 tasks/cli.js build --debug --watch --chrome --firefox",
12
+ "debug:watch:mv3": "node --max-old-space-size=3072 tasks/cli.js build --debug --watch --chrome-mv3",
12
13
  "dependencies:upgrade": "node tasks/dependencies.js",
13
14
  "lint": "eslint --ignore-pattern '!.eslint-plugin-local.js' -- 'src/**/*.ts' 'src/**/*.tsx' 'tasks/**/*.js' 'tests/[!coverage]**/*.js' 'tests/**/*.ts' '.eslintrc.js' 'index.d.ts'",
14
15
  "lint:bundle": "(node ./tasks/check-exists.js ./build/debug/chrome || node tasks/build.js --debug --api --chrome) && eslint -- 'build/debug/chrome/**/*.js' 'darkreader.js'",
@@ -58,21 +59,21 @@
58
59
  "@rollup/plugin-node-resolve": "15.2.3",
59
60
  "@rollup/plugin-replace": "5.0.5",
60
61
  "@rollup/plugin-typescript": "11.1.6",
61
- "@types/chrome": "0.0.266",
62
- "@types/eslint": "8.56.7",
62
+ "@types/chrome": "0.0.267",
63
+ "@types/eslint": "8.56.10",
63
64
  "@types/jasmine": "5.1.4",
64
65
  "@types/jest": "29.5.12",
65
66
  "@types/karma": "6.3.8",
66
67
  "@types/karma-coverage": "2.0.3",
67
- "@types/node": "20.12.6",
68
+ "@types/node": "20.12.7",
68
69
  "@types/ws": "8.5.10",
69
- "@typescript-eslint/eslint-plugin": "7.6.0",
70
- "@typescript-eslint/parser": "7.6.0",
70
+ "@typescript-eslint/eslint-plugin": "7.7.1",
71
+ "@typescript-eslint/parser": "7.7.1",
71
72
  "chokidar": "3.6.0",
72
73
  "eslint": "8.57.0",
73
74
  "eslint-plugin-compat": "4.2.0",
74
75
  "eslint-plugin-import": "2.29.1",
75
- "eslint-plugin-local": "4.2.1",
76
+ "eslint-plugin-local": "4.2.2",
76
77
  "globby": "14.0.1",
77
78
  "jasmine-core": "5.1.2",
78
79
  "jest": "29.7.0",
@@ -88,14 +89,14 @@
88
89
  "less": "4.2.0",
89
90
  "malevic": "0.20.1",
90
91
  "prettier": "3.2.5",
91
- "puppeteer-core": "22.6.3",
92
- "rollup": "4.14.1",
92
+ "puppeteer-core": "22.7.1",
93
+ "rollup": "4.17.1",
93
94
  "rollup-plugin-istanbul": "5.0.0",
94
95
  "ts-jest": "29.1.2",
95
96
  "tslib": "2.6.2",
96
- "typescript": "5.4.4",
97
+ "typescript": "5.4.5",
97
98
  "web-ext": "7.11.0",
98
- "ws": "8.16.0",
99
+ "ws": "8.17.0",
99
100
  "yazl": "2.5.1"
100
101
  }
101
102
  }