darkreader 4.9.119 → 4.9.120

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/darkreader.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Dark Reader v4.9.119
2
+ * Dark Reader v4.9.120
3
3
  * https://darkreader.org/
4
4
  */
5
5
 
@@ -1041,54 +1041,155 @@
1041
1041
  }
1042
1042
  return null;
1043
1043
  }
1044
- function getNumbers($color) {
1044
+ const C_0 = "0".charCodeAt(0);
1045
+ const C_9 = "9".charCodeAt(0);
1046
+ const C_e = "e".charCodeAt(0);
1047
+ const C_DOT = ".".charCodeAt(0);
1048
+ const C_PLUS = "+".charCodeAt(0);
1049
+ const C_MINUS = "-".charCodeAt(0);
1050
+ const C_SPACE = " ".charCodeAt(0);
1051
+ const C_COMMA = ",".charCodeAt(0);
1052
+ const C_SLASH = "/".charCodeAt(0);
1053
+ const C_PERCENT = "%".charCodeAt(0);
1054
+ function getNumbersFromString(input, range, units) {
1045
1055
  const numbers = [];
1046
- let prevPos = 0;
1047
- let isMining = false;
1048
- const startIndex = $color.indexOf("(");
1049
- $color = $color.substring(startIndex + 1, $color.length - 1);
1050
- for (let i = 0; i < $color.length; i++) {
1051
- const c = $color[i];
1052
- if ((c >= "0" && c <= "9") || c === "." || c === "+" || c === "-") {
1053
- isMining = true;
1054
- } else if (isMining && (c === " " || c === "," || c === "/")) {
1055
- numbers.push($color.substring(prevPos, i));
1056
- isMining = false;
1057
- prevPos = i + 1;
1058
- } else if (!isMining) {
1059
- prevPos = i + 1;
1060
- }
1061
- }
1062
- if (isMining) {
1063
- numbers.push($color.substring(prevPos, $color.length));
1056
+ const searchStart = input.indexOf("(") + 1;
1057
+ const searchEnd = input.length - 1;
1058
+ let numStart = -1;
1059
+ let unitStart = -1;
1060
+ const push = (matchEnd) => {
1061
+ const numEnd = unitStart > -1 ? unitStart : matchEnd;
1062
+ const $num = input.slice(numStart, numEnd);
1063
+ let n = parseFloat($num);
1064
+ const r = range[numbers.length];
1065
+ if (unitStart > -1) {
1066
+ const unit = input.slice(unitStart, matchEnd);
1067
+ const u = units[unit];
1068
+ if (u != null) {
1069
+ n *= r / u;
1070
+ }
1071
+ }
1072
+ if (r > 1) {
1073
+ n = Math.round(n);
1074
+ }
1075
+ numbers.push(n);
1076
+ numStart = -1;
1077
+ unitStart = -1;
1078
+ };
1079
+ for (let i = searchStart; i < searchEnd; i++) {
1080
+ const c = input.charCodeAt(i);
1081
+ const isNumChar =
1082
+ (c >= C_0 && c <= C_9) ||
1083
+ c === C_DOT ||
1084
+ c === C_PLUS ||
1085
+ c === C_MINUS ||
1086
+ c === C_e;
1087
+ const isDelimiter = c === C_SPACE || c === C_COMMA || c === C_SLASH;
1088
+ if (isNumChar) {
1089
+ if (numStart === -1) {
1090
+ numStart = i;
1091
+ }
1092
+ } else if (numStart > -1) {
1093
+ if (isDelimiter) {
1094
+ push(i);
1095
+ } else if (unitStart === -1) {
1096
+ unitStart = i;
1097
+ }
1098
+ }
1099
+ }
1100
+ if (numStart > -1) {
1101
+ push(searchEnd);
1064
1102
  }
1065
1103
  return numbers;
1066
1104
  }
1067
- function getNumbersFromString(str, range, units) {
1068
- const raw = getNumbers(str);
1069
- const unitsList = Object.entries(units);
1070
- const numbers = raw
1071
- .map((r) => r.trim())
1072
- .map((r, i) => {
1073
- let n;
1074
- const unit = unitsList.find(([u]) => r.endsWith(u));
1075
- if (unit) {
1076
- n =
1077
- (parseFloat(r.substring(0, r.length - unit[0].length)) /
1078
- unit[1]) *
1079
- range[i];
1105
+ const rgbRange = [255, 255, 255, 1];
1106
+ const rgbUnits = {"%": 100};
1107
+ function getRGBValues(input) {
1108
+ const CHAR_CODE_0 = 48;
1109
+ const length = input.length;
1110
+ let i = 0;
1111
+ let digitsCount = 0;
1112
+ let digitSequence = false;
1113
+ let floatDigitsCount = -1;
1114
+ let delimiter = C_SPACE;
1115
+ let channel = -1;
1116
+ let result = null;
1117
+ while (i < length) {
1118
+ const c = input.charCodeAt(i);
1119
+ if ((c >= C_0 && c <= C_9) || c === C_DOT) {
1120
+ if (!digitSequence) {
1121
+ digitSequence = true;
1122
+ digitsCount = 0;
1123
+ floatDigitsCount = -1;
1124
+ channel++;
1125
+ if (channel === 3 && result) {
1126
+ result[3] = 0;
1127
+ }
1128
+ if (channel > 3) {
1129
+ return null;
1130
+ }
1131
+ }
1132
+ if (c === C_DOT) {
1133
+ if (floatDigitsCount > 0) {
1134
+ return null;
1135
+ }
1136
+ floatDigitsCount = 0;
1080
1137
  } else {
1081
- n = parseFloat(r);
1138
+ const d = c - CHAR_CODE_0;
1139
+ if (!result) {
1140
+ result = [0, 0, 0, 1];
1141
+ }
1142
+ if (floatDigitsCount > -1) {
1143
+ floatDigitsCount++;
1144
+ result[channel] += d / 10 ** floatDigitsCount;
1145
+ } else {
1146
+ digitsCount++;
1147
+ if (digitsCount > 3) {
1148
+ return null;
1149
+ }
1150
+ result[channel] = result[channel] * 10 + d;
1151
+ }
1082
1152
  }
1083
- if (range[i] > 1) {
1084
- return Math.round(n);
1153
+ } else if (c === C_PERCENT) {
1154
+ if (
1155
+ channel < 0 ||
1156
+ channel > 3 ||
1157
+ delimiter !== C_SPACE ||
1158
+ !result
1159
+ ) {
1160
+ return null;
1085
1161
  }
1086
- return n;
1087
- });
1088
- return numbers;
1162
+ result[channel] =
1163
+ channel < 3
1164
+ ? Math.round((result[channel] * 255) / 100)
1165
+ : result[channel] / 100;
1166
+ digitSequence = false;
1167
+ } else {
1168
+ digitSequence = false;
1169
+ if (c === C_SPACE) {
1170
+ if (channel === 0) {
1171
+ delimiter = c;
1172
+ }
1173
+ } else if (c === C_COMMA) {
1174
+ if (channel === -1) {
1175
+ return null;
1176
+ }
1177
+ delimiter = C_COMMA;
1178
+ } else if (c === C_SLASH) {
1179
+ if (channel !== 2 || delimiter !== C_SPACE) {
1180
+ return null;
1181
+ }
1182
+ } else {
1183
+ return null;
1184
+ }
1185
+ }
1186
+ i++;
1187
+ }
1188
+ if (channel < 2 || channel > 3) {
1189
+ return null;
1190
+ }
1191
+ return result;
1089
1192
  }
1090
- const rgbRange = [255, 255, 255, 1];
1091
- const rgbUnits = {"%": 100};
1092
1193
  function parseRGB($rgb) {
1093
1194
  const [r, g, b, a = 1] = getNumbersFromString($rgb, rgbRange, rgbUnits);
1094
1195
  if (r == null || g == null || b == null || a == null) {
@@ -1105,29 +1206,48 @@
1105
1206
  }
1106
1207
  return hslToRGB({h, s, l, a});
1107
1208
  }
1209
+ const C_A = "A".charCodeAt(0);
1210
+ const C_F = "F".charCodeAt(0);
1211
+ const C_a = "a".charCodeAt(0);
1212
+ const C_f = "f".charCodeAt(0);
1108
1213
  function parseHex($hex) {
1109
- const h = $hex.substring(1);
1110
- switch (h.length) {
1111
- case 3:
1112
- case 4: {
1113
- const [r, g, b] = [0, 1, 2].map((i) =>
1114
- parseInt(`${h[i]}${h[i]}`, 16)
1115
- );
1116
- const a =
1117
- h.length === 3 ? 1 : parseInt(`${h[3]}${h[3]}`, 16) / 255;
1118
- return {r, g, b, a};
1119
- }
1120
- case 6:
1121
- case 8: {
1122
- const [r, g, b] = [0, 2, 4].map((i) =>
1123
- parseInt(h.substring(i, i + 2), 16)
1124
- );
1125
- const a =
1126
- h.length === 6 ? 1 : parseInt(h.substring(6, 8), 16) / 255;
1127
- return {r, g, b, a};
1214
+ const length = $hex.length;
1215
+ const digitCount = length - 1;
1216
+ const isShort = digitCount === 3 || digitCount === 4;
1217
+ const isLong = digitCount === 6 || digitCount === 8;
1218
+ if (!isShort && !isLong) {
1219
+ return null;
1220
+ }
1221
+ const hex = (i) => {
1222
+ const c = $hex.charCodeAt(i);
1223
+ if (c >= C_A && c <= C_F) {
1224
+ return c + 10 - C_A;
1225
+ }
1226
+ if (c >= C_a && c <= C_f) {
1227
+ return c + 10 - C_a;
1228
+ }
1229
+ return c - C_0;
1230
+ };
1231
+ let r;
1232
+ let g;
1233
+ let b;
1234
+ let a = 1;
1235
+ if (isShort) {
1236
+ r = hex(1) * 17;
1237
+ g = hex(2) * 17;
1238
+ b = hex(3) * 17;
1239
+ if (digitCount === 4) {
1240
+ a = (hex(4) * 17) / 255;
1241
+ }
1242
+ } else {
1243
+ r = hex(1) * 16 + hex(2);
1244
+ g = hex(3) * 16 + hex(4);
1245
+ b = hex(5) * 16 + hex(6);
1246
+ if (digitCount === 8) {
1247
+ a = (hex(7) * 16 + hex(8)) / 255;
1128
1248
  }
1129
1249
  }
1130
- return null;
1250
+ return {r, g, b, a};
1131
1251
  }
1132
1252
  function getColorByName($color) {
1133
1253
  const n = knownColors.get($color);
@@ -1921,13 +2041,6 @@
1921
2041
  })
1922
2042
  : null;
1923
2043
  function iterateCSSDeclarations(style, iterate) {
1924
- forEach(style, (property) => {
1925
- const value = style.getPropertyValue(property).trim();
1926
- if (!value) {
1927
- return;
1928
- }
1929
- iterate(property, value);
1930
- });
1931
2044
  const cssText = style.cssText;
1932
2045
  if (cssText.includes("var(")) {
1933
2046
  if (isSafari) {
@@ -1961,6 +2074,13 @@
1961
2074
  ) {
1962
2075
  handleEmptyShorthand("border", style, iterate);
1963
2076
  }
2077
+ forEach(style, (property) => {
2078
+ const value = style.getPropertyValue(property).trim();
2079
+ if (!value) {
2080
+ return;
2081
+ }
2082
+ iterate(property, value);
2083
+ });
1964
2084
  }
1965
2085
  function handleEmptyShorthand(shorthand, style, iterate) {
1966
2086
  const parentRule = style.parentRule;
@@ -2775,7 +2895,12 @@
2775
2895
  .slice(0, commaIndex)
2776
2896
  .split(";")[1];
2777
2897
  if (encoding === "base64") {
2898
+ if (svgText.includes("%")) {
2899
+ svgText = decodeURIComponent(svgText);
2900
+ }
2778
2901
  svgText = atob(svgText);
2902
+ } else if (svgText.startsWith("%3c")) {
2903
+ svgText = decodeURIComponent(svgText);
2779
2904
  }
2780
2905
  if (svgText.startsWith("<svg ")) {
2781
2906
  const closingIndex = svgText.indexOf(">");
@@ -2783,22 +2908,20 @@
2783
2908
  .slice(0, closingIndex + 1)
2784
2909
  .toLocaleLowerCase();
2785
2910
  if (
2786
- svgOpening.includes("viewbox") &&
2787
- !svgOpening.includes("width") &&
2788
- !svgOpening.includes("height")
2911
+ svgOpening.includes("viewbox=") &&
2912
+ !svgOpening.includes("width=") &&
2913
+ !svgOpening.includes("height=")
2789
2914
  ) {
2790
2915
  useViewBox = true;
2791
2916
  const viewboxIndex =
2792
- svgOpening.indexOf('viewbox="');
2917
+ svgOpening.indexOf("viewbox=");
2918
+ const quote = svgOpening[viewboxIndex + 8];
2793
2919
  const viewboxCloseIndex = svgOpening.indexOf(
2794
- 'viewbox="',
2920
+ quote,
2795
2921
  viewboxIndex + 9
2796
2922
  );
2797
2923
  const viewBox = svgOpening
2798
- .slice(
2799
- viewboxIndex + 9,
2800
- viewboxCloseIndex - 1
2801
- )
2924
+ .slice(viewboxIndex + 9, viewboxCloseIndex)
2802
2925
  .split(" ")
2803
2926
  .map((x) => parseFloat(x));
2804
2927
  if (
@@ -3065,7 +3188,11 @@
3065
3188
  if (encoding !== "base64" || !mediaType) {
3066
3189
  return null;
3067
3190
  }
3068
- const characters = atob(dataURL.substring(commaIndex + 1));
3191
+ let base64Content = dataURL.substring(commaIndex + 1);
3192
+ if (base64Content.includes("%")) {
3193
+ base64Content = decodeURIComponent(base64Content);
3194
+ }
3195
+ const characters = atob(base64Content);
3069
3196
  const bytes = new Uint8Array(characters.length);
3070
3197
  for (let i = 0; i < characters.length; i++) {
3071
3198
  bytes[i] = characters.charCodeAt(i);
@@ -3102,6 +3229,12 @@
3102
3229
  function getPriority(ruleStyle, property) {
3103
3230
  return Boolean(ruleStyle && ruleStyle.getPropertyPriority(property));
3104
3231
  }
3232
+ const bgPropsToCopy = [
3233
+ "background-clip",
3234
+ "background-position",
3235
+ "background-repeat",
3236
+ "background-size"
3237
+ ];
3105
3238
  function getModifiableCSSDeclaration(
3106
3239
  property,
3107
3240
  value,
@@ -3172,6 +3305,8 @@
3172
3305
  );
3173
3306
  } else if (property.includes("shadow")) {
3174
3307
  modifier = getShadowModifier(value);
3308
+ } else if (bgPropsToCopy.includes(property) && value !== "initial") {
3309
+ modifier = value;
3175
3310
  }
3176
3311
  if (!modifier) {
3177
3312
  return null;
@@ -4283,9 +4418,7 @@
4283
4418
  }
4284
4419
  this.definedVars.add(varName);
4285
4420
  const isColor = Boolean(
4286
- value.match(rawRGBSpaceRegex) ||
4287
- value.match(rawRGBCommaRegex) ||
4288
- parseColorWithCache(value)
4421
+ getRGBValues(value) || parseColorWithCache(value)
4289
4422
  );
4290
4423
  if (isColor) {
4291
4424
  this.unknownColorVars.add(varName);
@@ -4624,16 +4757,13 @@
4624
4757
  function isTextColorProperty(property) {
4625
4758
  return textColorProps.includes(property);
4626
4759
  }
4627
- const rawRGBSpaceRegex =
4628
- /^(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})\s*(\/\s*\d+\.?\d*)?$/;
4629
- const rawRGBCommaRegex = /^(\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})$/;
4630
4760
  function parseRawColorValue(input) {
4631
- const match =
4632
- input.match(rawRGBSpaceRegex) ?? input.match(rawRGBCommaRegex);
4633
- if (match) {
4634
- const color = match[4]
4635
- ? `rgb(${match[1]} ${match[2]} ${match[3]} / ${match[4]})`
4636
- : `rgb(${match[1]}, ${match[2]}, ${match[3]})`;
4761
+ const v = getRGBValues(input);
4762
+ if (v) {
4763
+ const color =
4764
+ v[3] < 1
4765
+ ? `rgb(${v[0]} ${v[1]} ${v[2]} / ${v[3]})`
4766
+ : `rgb(${v[0]} ${v[1]} ${v[2]})`;
4637
4767
  return {isRaw: true, color};
4638
4768
  }
4639
4769
  return {isRaw: false, color: input};
@@ -4646,7 +4776,9 @@
4646
4776
  if (isRaw) {
4647
4777
  const outputInRGB = parseColorWithCache(outputColor);
4648
4778
  return outputInRGB
4649
- ? `${outputInRGB.r}, ${outputInRGB.g}, ${outputInRGB.b}`
4779
+ ? Number.isNaN(outputInRGB.a) || outputInRGB.a === 1
4780
+ ? `${outputInRGB.r}, ${outputInRGB.g}, ${outputInRGB.b}`
4781
+ : `${outputInRGB.r}, ${outputInRGB.g}, ${outputInRGB.b}, ${outputInRGB.a}`
4650
4782
  : outputColor;
4651
4783
  }
4652
4784
  return outputColor;
@@ -6205,17 +6337,28 @@
6205
6337
  }
6206
6338
 
6207
6339
  const STYLE_SELECTOR = 'style, link[rel*="stylesheet" i]:not([disabled])';
6208
- function isFontsGoogleApiStyle(element) {
6209
- if (!element.href) {
6340
+ let ignoredCSSURLPatterns = [];
6341
+ function setIgnoredCSSURLs(patterns) {
6342
+ ignoredCSSURLPatterns = patterns || [];
6343
+ }
6344
+ function shouldIgnoreCSSURL(url) {
6345
+ if (!url || ignoredCSSURLPatterns.length === 0) {
6210
6346
  return false;
6211
6347
  }
6212
- try {
6213
- const elementURL = new URL(element.href);
6214
- return elementURL.hostname === "fonts.googleapis.com";
6215
- } catch (err) {
6216
- logInfo(`Couldn't construct ${element.href} as URL`);
6217
- return false;
6348
+ for (const pattern of ignoredCSSURLPatterns) {
6349
+ if (pattern.startsWith("^")) {
6350
+ if (url.startsWith(pattern.slice(1))) {
6351
+ return true;
6352
+ }
6353
+ } else if (pattern.endsWith("$")) {
6354
+ if (url.endsWith(pattern.slice(0, -1))) {
6355
+ return true;
6356
+ }
6357
+ } else if (url.includes(pattern)) {
6358
+ return true;
6359
+ }
6218
6360
  }
6361
+ return false;
6219
6362
  }
6220
6363
  const hostsBreakingOnSVGStyleOverride = [
6221
6364
  "account.containerstore.com",
@@ -6237,7 +6380,7 @@
6237
6380
  (isFirefox
6238
6381
  ? !element.href.startsWith("moz-extension://")
6239
6382
  : true) &&
6240
- !isFontsGoogleApiStyle(element))) &&
6383
+ !shouldIgnoreCSSURL(element.href))) &&
6241
6384
  !element.classList.contains("darkreader") &&
6242
6385
  !ignoredMedia.includes(element.media.toLowerCase()) &&
6243
6386
  !element.classList.contains("stylus")
@@ -8267,9 +8410,13 @@
8267
8410
  ignoredInlineSelectors = Array.isArray(fixes.ignoreInlineStyle)
8268
8411
  ? fixes.ignoreInlineStyle
8269
8412
  : [];
8413
+ setIgnoredCSSURLs(
8414
+ Array.isArray(fixes.ignoreCSSUrl) ? fixes.ignoreCSSUrl : []
8415
+ );
8270
8416
  } else {
8271
8417
  ignoredImageAnalysisSelectors = [];
8272
8418
  ignoredInlineSelectors = [];
8419
+ setIgnoredCSSURLs([]);
8273
8420
  }
8274
8421
  if (theme.immediateModify) {
8275
8422
  setIsDOMReady(() => {
package/darkreader.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Dark Reader v4.9.119
2
+ * Dark Reader v4.9.120
3
3
  * https://darkreader.org/
4
4
  */
5
5
 
@@ -1010,54 +1010,155 @@ function parse($color) {
1010
1010
  }
1011
1011
  return null;
1012
1012
  }
1013
- function getNumbers($color) {
1013
+ const C_0 = "0".charCodeAt(0);
1014
+ const C_9 = "9".charCodeAt(0);
1015
+ const C_e = "e".charCodeAt(0);
1016
+ const C_DOT = ".".charCodeAt(0);
1017
+ const C_PLUS = "+".charCodeAt(0);
1018
+ const C_MINUS = "-".charCodeAt(0);
1019
+ const C_SPACE = " ".charCodeAt(0);
1020
+ const C_COMMA = ",".charCodeAt(0);
1021
+ const C_SLASH = "/".charCodeAt(0);
1022
+ const C_PERCENT = "%".charCodeAt(0);
1023
+ function getNumbersFromString(input, range, units) {
1014
1024
  const numbers = [];
1015
- let prevPos = 0;
1016
- let isMining = false;
1017
- const startIndex = $color.indexOf("(");
1018
- $color = $color.substring(startIndex + 1, $color.length - 1);
1019
- for (let i = 0; i < $color.length; i++) {
1020
- const c = $color[i];
1021
- if ((c >= "0" && c <= "9") || c === "." || c === "+" || c === "-") {
1022
- isMining = true;
1023
- } else if (isMining && (c === " " || c === "," || c === "/")) {
1024
- numbers.push($color.substring(prevPos, i));
1025
- isMining = false;
1026
- prevPos = i + 1;
1027
- } else if (!isMining) {
1028
- prevPos = i + 1;
1029
- }
1030
- }
1031
- if (isMining) {
1032
- numbers.push($color.substring(prevPos, $color.length));
1025
+ const searchStart = input.indexOf("(") + 1;
1026
+ const searchEnd = input.length - 1;
1027
+ let numStart = -1;
1028
+ let unitStart = -1;
1029
+ const push = (matchEnd) => {
1030
+ const numEnd = unitStart > -1 ? unitStart : matchEnd;
1031
+ const $num = input.slice(numStart, numEnd);
1032
+ let n = parseFloat($num);
1033
+ const r = range[numbers.length];
1034
+ if (unitStart > -1) {
1035
+ const unit = input.slice(unitStart, matchEnd);
1036
+ const u = units[unit];
1037
+ if (u != null) {
1038
+ n *= r / u;
1039
+ }
1040
+ }
1041
+ if (r > 1) {
1042
+ n = Math.round(n);
1043
+ }
1044
+ numbers.push(n);
1045
+ numStart = -1;
1046
+ unitStart = -1;
1047
+ };
1048
+ for (let i = searchStart; i < searchEnd; i++) {
1049
+ const c = input.charCodeAt(i);
1050
+ const isNumChar =
1051
+ (c >= C_0 && c <= C_9) ||
1052
+ c === C_DOT ||
1053
+ c === C_PLUS ||
1054
+ c === C_MINUS ||
1055
+ c === C_e;
1056
+ const isDelimiter = c === C_SPACE || c === C_COMMA || c === C_SLASH;
1057
+ if (isNumChar) {
1058
+ if (numStart === -1) {
1059
+ numStart = i;
1060
+ }
1061
+ } else if (numStart > -1) {
1062
+ if (isDelimiter) {
1063
+ push(i);
1064
+ } else if (unitStart === -1) {
1065
+ unitStart = i;
1066
+ }
1067
+ }
1068
+ }
1069
+ if (numStart > -1) {
1070
+ push(searchEnd);
1033
1071
  }
1034
1072
  return numbers;
1035
1073
  }
1036
- function getNumbersFromString(str, range, units) {
1037
- const raw = getNumbers(str);
1038
- const unitsList = Object.entries(units);
1039
- const numbers = raw
1040
- .map((r) => r.trim())
1041
- .map((r, i) => {
1042
- let n;
1043
- const unit = unitsList.find(([u]) => r.endsWith(u));
1044
- if (unit) {
1045
- n =
1046
- (parseFloat(r.substring(0, r.length - unit[0].length)) /
1047
- unit[1]) *
1048
- range[i];
1074
+ const rgbRange = [255, 255, 255, 1];
1075
+ const rgbUnits = {"%": 100};
1076
+ function getRGBValues(input) {
1077
+ const CHAR_CODE_0 = 48;
1078
+ const length = input.length;
1079
+ let i = 0;
1080
+ let digitsCount = 0;
1081
+ let digitSequence = false;
1082
+ let floatDigitsCount = -1;
1083
+ let delimiter = C_SPACE;
1084
+ let channel = -1;
1085
+ let result = null;
1086
+ while (i < length) {
1087
+ const c = input.charCodeAt(i);
1088
+ if ((c >= C_0 && c <= C_9) || c === C_DOT) {
1089
+ if (!digitSequence) {
1090
+ digitSequence = true;
1091
+ digitsCount = 0;
1092
+ floatDigitsCount = -1;
1093
+ channel++;
1094
+ if (channel === 3 && result) {
1095
+ result[3] = 0;
1096
+ }
1097
+ if (channel > 3) {
1098
+ return null;
1099
+ }
1100
+ }
1101
+ if (c === C_DOT) {
1102
+ if (floatDigitsCount > 0) {
1103
+ return null;
1104
+ }
1105
+ floatDigitsCount = 0;
1049
1106
  } else {
1050
- n = parseFloat(r);
1107
+ const d = c - CHAR_CODE_0;
1108
+ if (!result) {
1109
+ result = [0, 0, 0, 1];
1110
+ }
1111
+ if (floatDigitsCount > -1) {
1112
+ floatDigitsCount++;
1113
+ result[channel] += d / 10 ** floatDigitsCount;
1114
+ } else {
1115
+ digitsCount++;
1116
+ if (digitsCount > 3) {
1117
+ return null;
1118
+ }
1119
+ result[channel] = result[channel] * 10 + d;
1120
+ }
1051
1121
  }
1052
- if (range[i] > 1) {
1053
- return Math.round(n);
1122
+ } else if (c === C_PERCENT) {
1123
+ if (
1124
+ channel < 0 ||
1125
+ channel > 3 ||
1126
+ delimiter !== C_SPACE ||
1127
+ !result
1128
+ ) {
1129
+ return null;
1054
1130
  }
1055
- return n;
1056
- });
1057
- return numbers;
1131
+ result[channel] =
1132
+ channel < 3
1133
+ ? Math.round((result[channel] * 255) / 100)
1134
+ : result[channel] / 100;
1135
+ digitSequence = false;
1136
+ } else {
1137
+ digitSequence = false;
1138
+ if (c === C_SPACE) {
1139
+ if (channel === 0) {
1140
+ delimiter = c;
1141
+ }
1142
+ } else if (c === C_COMMA) {
1143
+ if (channel === -1) {
1144
+ return null;
1145
+ }
1146
+ delimiter = C_COMMA;
1147
+ } else if (c === C_SLASH) {
1148
+ if (channel !== 2 || delimiter !== C_SPACE) {
1149
+ return null;
1150
+ }
1151
+ } else {
1152
+ return null;
1153
+ }
1154
+ }
1155
+ i++;
1156
+ }
1157
+ if (channel < 2 || channel > 3) {
1158
+ return null;
1159
+ }
1160
+ return result;
1058
1161
  }
1059
- const rgbRange = [255, 255, 255, 1];
1060
- const rgbUnits = {"%": 100};
1061
1162
  function parseRGB($rgb) {
1062
1163
  const [r, g, b, a = 1] = getNumbersFromString($rgb, rgbRange, rgbUnits);
1063
1164
  if (r == null || g == null || b == null || a == null) {
@@ -1074,28 +1175,48 @@ function parseHSL($hsl) {
1074
1175
  }
1075
1176
  return hslToRGB({h, s, l, a});
1076
1177
  }
1178
+ const C_A = "A".charCodeAt(0);
1179
+ const C_F = "F".charCodeAt(0);
1180
+ const C_a = "a".charCodeAt(0);
1181
+ const C_f = "f".charCodeAt(0);
1077
1182
  function parseHex($hex) {
1078
- const h = $hex.substring(1);
1079
- switch (h.length) {
1080
- case 3:
1081
- case 4: {
1082
- const [r, g, b] = [0, 1, 2].map((i) =>
1083
- parseInt(`${h[i]}${h[i]}`, 16)
1084
- );
1085
- const a = h.length === 3 ? 1 : parseInt(`${h[3]}${h[3]}`, 16) / 255;
1086
- return {r, g, b, a};
1183
+ const length = $hex.length;
1184
+ const digitCount = length - 1;
1185
+ const isShort = digitCount === 3 || digitCount === 4;
1186
+ const isLong = digitCount === 6 || digitCount === 8;
1187
+ if (!isShort && !isLong) {
1188
+ return null;
1189
+ }
1190
+ const hex = (i) => {
1191
+ const c = $hex.charCodeAt(i);
1192
+ if (c >= C_A && c <= C_F) {
1193
+ return c + 10 - C_A;
1087
1194
  }
1088
- case 6:
1089
- case 8: {
1090
- const [r, g, b] = [0, 2, 4].map((i) =>
1091
- parseInt(h.substring(i, i + 2), 16)
1092
- );
1093
- const a =
1094
- h.length === 6 ? 1 : parseInt(h.substring(6, 8), 16) / 255;
1095
- return {r, g, b, a};
1195
+ if (c >= C_a && c <= C_f) {
1196
+ return c + 10 - C_a;
1197
+ }
1198
+ return c - C_0;
1199
+ };
1200
+ let r;
1201
+ let g;
1202
+ let b;
1203
+ let a = 1;
1204
+ if (isShort) {
1205
+ r = hex(1) * 17;
1206
+ g = hex(2) * 17;
1207
+ b = hex(3) * 17;
1208
+ if (digitCount === 4) {
1209
+ a = (hex(4) * 17) / 255;
1210
+ }
1211
+ } else {
1212
+ r = hex(1) * 16 + hex(2);
1213
+ g = hex(3) * 16 + hex(4);
1214
+ b = hex(5) * 16 + hex(6);
1215
+ if (digitCount === 8) {
1216
+ a = (hex(7) * 16 + hex(8)) / 255;
1096
1217
  }
1097
1218
  }
1098
- return null;
1219
+ return {r, g, b, a};
1099
1220
  }
1100
1221
  function getColorByName($color) {
1101
1222
  const n = knownColors.get($color);
@@ -1877,13 +1998,6 @@ const shorthandVarDepPropRegexps = isSafari
1877
1998
  })
1878
1999
  : null;
1879
2000
  function iterateCSSDeclarations(style, iterate) {
1880
- forEach(style, (property) => {
1881
- const value = style.getPropertyValue(property).trim();
1882
- if (!value) {
1883
- return;
1884
- }
1885
- iterate(property, value);
1886
- });
1887
2001
  const cssText = style.cssText;
1888
2002
  if (cssText.includes("var(")) {
1889
2003
  if (isSafari) {
@@ -1917,6 +2031,13 @@ function iterateCSSDeclarations(style, iterate) {
1917
2031
  ) {
1918
2032
  handleEmptyShorthand("border", style, iterate);
1919
2033
  }
2034
+ forEach(style, (property) => {
2035
+ const value = style.getPropertyValue(property).trim();
2036
+ if (!value) {
2037
+ return;
2038
+ }
2039
+ iterate(property, value);
2040
+ });
1920
2041
  }
1921
2042
  function handleEmptyShorthand(shorthand, style, iterate) {
1922
2043
  const parentRule = style.parentRule;
@@ -2682,7 +2803,12 @@ async function getImageDetails(url) {
2682
2803
  let svgText = dataURL.slice(commaIndex + 1);
2683
2804
  const encoding = dataURL.slice(0, commaIndex).split(";")[1];
2684
2805
  if (encoding === "base64") {
2806
+ if (svgText.includes("%")) {
2807
+ svgText = decodeURIComponent(svgText);
2808
+ }
2685
2809
  svgText = atob(svgText);
2810
+ } else if (svgText.startsWith("%3c")) {
2811
+ svgText = decodeURIComponent(svgText);
2686
2812
  }
2687
2813
  if (svgText.startsWith("<svg ")) {
2688
2814
  const closingIndex = svgText.indexOf(">");
@@ -2690,19 +2816,19 @@ async function getImageDetails(url) {
2690
2816
  .slice(0, closingIndex + 1)
2691
2817
  .toLocaleLowerCase();
2692
2818
  if (
2693
- svgOpening.includes("viewbox") &&
2694
- !svgOpening.includes("width") &&
2695
- !svgOpening.includes("height")
2819
+ svgOpening.includes("viewbox=") &&
2820
+ !svgOpening.includes("width=") &&
2821
+ !svgOpening.includes("height=")
2696
2822
  ) {
2697
2823
  useViewBox = true;
2698
- const viewboxIndex =
2699
- svgOpening.indexOf('viewbox="');
2824
+ const viewboxIndex = svgOpening.indexOf("viewbox=");
2825
+ const quote = svgOpening[viewboxIndex + 8];
2700
2826
  const viewboxCloseIndex = svgOpening.indexOf(
2701
- 'viewbox="',
2827
+ quote,
2702
2828
  viewboxIndex + 9
2703
2829
  );
2704
2830
  const viewBox = svgOpening
2705
- .slice(viewboxIndex + 9, viewboxCloseIndex - 1)
2831
+ .slice(viewboxIndex + 9, viewboxCloseIndex)
2706
2832
  .split(" ")
2707
2833
  .map((x) => parseFloat(x));
2708
2834
  if (
@@ -2968,7 +3094,11 @@ function tryConvertDataURLToBlobSync(dataURL) {
2968
3094
  if (encoding !== "base64" || !mediaType) {
2969
3095
  return null;
2970
3096
  }
2971
- const characters = atob(dataURL.substring(commaIndex + 1));
3097
+ let base64Content = dataURL.substring(commaIndex + 1);
3098
+ if (base64Content.includes("%")) {
3099
+ base64Content = decodeURIComponent(base64Content);
3100
+ }
3101
+ const characters = atob(base64Content);
2972
3102
  const bytes = new Uint8Array(characters.length);
2973
3103
  for (let i = 0; i < characters.length; i++) {
2974
3104
  bytes[i] = characters.charCodeAt(i);
@@ -3005,6 +3135,12 @@ function cleanImageProcessingCache() {
3005
3135
  function getPriority(ruleStyle, property) {
3006
3136
  return Boolean(ruleStyle && ruleStyle.getPropertyPriority(property));
3007
3137
  }
3138
+ const bgPropsToCopy = [
3139
+ "background-clip",
3140
+ "background-position",
3141
+ "background-repeat",
3142
+ "background-size"
3143
+ ];
3008
3144
  function getModifiableCSSDeclaration(
3009
3145
  property,
3010
3146
  value,
@@ -3068,6 +3204,8 @@ function getModifiableCSSDeclaration(
3068
3204
  );
3069
3205
  } else if (property.includes("shadow")) {
3070
3206
  modifier = getShadowModifier(value);
3207
+ } else if (bgPropsToCopy.includes(property) && value !== "initial") {
3208
+ modifier = value;
3071
3209
  }
3072
3210
  if (!modifier) {
3073
3211
  return null;
@@ -4135,9 +4273,7 @@ class VariablesStore {
4135
4273
  }
4136
4274
  this.definedVars.add(varName);
4137
4275
  const isColor = Boolean(
4138
- value.match(rawRGBSpaceRegex) ||
4139
- value.match(rawRGBCommaRegex) ||
4140
- parseColorWithCache(value)
4276
+ getRGBValues(value) || parseColorWithCache(value)
4141
4277
  );
4142
4278
  if (isColor) {
4143
4279
  this.unknownColorVars.add(varName);
@@ -4468,16 +4604,13 @@ const textColorProps = [
4468
4604
  function isTextColorProperty(property) {
4469
4605
  return textColorProps.includes(property);
4470
4606
  }
4471
- const rawRGBSpaceRegex =
4472
- /^(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})\s*(\/\s*\d+\.?\d*)?$/;
4473
- const rawRGBCommaRegex = /^(\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})$/;
4474
4607
  function parseRawColorValue(input) {
4475
- const match =
4476
- input.match(rawRGBSpaceRegex) ?? input.match(rawRGBCommaRegex);
4477
- if (match) {
4478
- const color = match[4]
4479
- ? `rgb(${match[1]} ${match[2]} ${match[3]} / ${match[4]})`
4480
- : `rgb(${match[1]}, ${match[2]}, ${match[3]})`;
4608
+ const v = getRGBValues(input);
4609
+ if (v) {
4610
+ const color =
4611
+ v[3] < 1
4612
+ ? `rgb(${v[0]} ${v[1]} ${v[2]} / ${v[3]})`
4613
+ : `rgb(${v[0]} ${v[1]} ${v[2]})`;
4481
4614
  return {isRaw: true, color};
4482
4615
  }
4483
4616
  return {isRaw: false, color: input};
@@ -4490,7 +4623,9 @@ function handleRawColorValue(input, theme, modifyFunction) {
4490
4623
  if (isRaw) {
4491
4624
  const outputInRGB = parseColorWithCache(outputColor);
4492
4625
  return outputInRGB
4493
- ? `${outputInRGB.r}, ${outputInRGB.g}, ${outputInRGB.b}`
4626
+ ? Number.isNaN(outputInRGB.a) || outputInRGB.a === 1
4627
+ ? `${outputInRGB.r}, ${outputInRGB.g}, ${outputInRGB.b}`
4628
+ : `${outputInRGB.r}, ${outputInRGB.g}, ${outputInRGB.b}, ${outputInRGB.a}`
4494
4629
  : outputColor;
4495
4630
  }
4496
4631
  return outputColor;
@@ -6004,17 +6139,28 @@ function createRAFSheetWatcher(
6004
6139
  }
6005
6140
 
6006
6141
  const STYLE_SELECTOR = 'style, link[rel*="stylesheet" i]:not([disabled])';
6007
- function isFontsGoogleApiStyle(element) {
6008
- if (!element.href) {
6142
+ let ignoredCSSURLPatterns = [];
6143
+ function setIgnoredCSSURLs(patterns) {
6144
+ ignoredCSSURLPatterns = patterns || [];
6145
+ }
6146
+ function shouldIgnoreCSSURL(url) {
6147
+ if (!url || ignoredCSSURLPatterns.length === 0) {
6009
6148
  return false;
6010
6149
  }
6011
- try {
6012
- const elementURL = new URL(element.href);
6013
- return elementURL.hostname === "fonts.googleapis.com";
6014
- } catch (err) {
6015
- logInfo(`Couldn't construct ${element.href} as URL`);
6016
- return false;
6150
+ for (const pattern of ignoredCSSURLPatterns) {
6151
+ if (pattern.startsWith("^")) {
6152
+ if (url.startsWith(pattern.slice(1))) {
6153
+ return true;
6154
+ }
6155
+ } else if (pattern.endsWith("$")) {
6156
+ if (url.endsWith(pattern.slice(0, -1))) {
6157
+ return true;
6158
+ }
6159
+ } else if (url.includes(pattern)) {
6160
+ return true;
6161
+ }
6017
6162
  }
6163
+ return false;
6018
6164
  }
6019
6165
  const hostsBreakingOnSVGStyleOverride = [
6020
6166
  "account.containerstore.com",
@@ -6034,7 +6180,7 @@ function shouldManageStyle(element) {
6034
6180
  (isFirefox
6035
6181
  ? !element.href.startsWith("moz-extension://")
6036
6182
  : true) &&
6037
- !isFontsGoogleApiStyle(element))) &&
6183
+ !shouldIgnoreCSSURL(element.href))) &&
6038
6184
  !element.classList.contains("darkreader") &&
6039
6185
  !ignoredMedia.includes(element.media.toLowerCase()) &&
6040
6186
  !element.classList.contains("stylus")
@@ -7996,9 +8142,13 @@ function createOrUpdateDynamicThemeInternal(
7996
8142
  ignoredInlineSelectors = Array.isArray(fixes.ignoreInlineStyle)
7997
8143
  ? fixes.ignoreInlineStyle
7998
8144
  : [];
8145
+ setIgnoredCSSURLs(
8146
+ Array.isArray(fixes.ignoreCSSUrl) ? fixes.ignoreCSSUrl : []
8147
+ );
7999
8148
  } else {
8000
8149
  ignoredImageAnalysisSelectors = [];
8001
8150
  ignoredInlineSelectors = [];
8151
+ setIgnoredCSSURLs([]);
8002
8152
  }
8003
8153
  if (theme.immediateModify) {
8004
8154
  setIsDOMReady(() => {
package/index.d.ts CHANGED
@@ -157,6 +157,14 @@ declare namespace DarkReader {
157
157
  * who are using the Dark Reader API.
158
158
  */
159
159
  disableStyleSheetsProxy: boolean;
160
+ /**
161
+ * List of stylesheet URL patterns to ignore.
162
+ * Stylesheets matching these patterns will not be processed by Dark Reader.
163
+ * - Simple string: matches if URL contains the string (e.g., "crisp")
164
+ * - Prefix with ^: matches if URL starts with pattern (e.g., "^https://client.crisp")
165
+ * - Suffix with $: matches if URL ends with pattern (e.g., ".css$")
166
+ */
167
+ ignoreCSSUrl: string[];
160
168
  }
161
169
  }
162
170
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "darkreader",
3
- "version": "4.9.119",
3
+ "version": "4.9.120",
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",
@@ -66,27 +66,27 @@
66
66
  "malevic": "0.20.2"
67
67
  },
68
68
  "devDependencies": {
69
- "@eslint/compat": "2.0.0",
69
+ "@eslint/compat": "2.0.1",
70
70
  "@eslint/eslintrc": "3.3.3",
71
71
  "@eslint/js": "9.39.2",
72
72
  "@rollup/plugin-node-resolve": "16.0.3",
73
73
  "@rollup/plugin-replace": "6.0.3",
74
74
  "@rollup/plugin-typescript": "12.3.0",
75
- "@stylistic/eslint-plugin": "5.6.1",
76
- "@types/chrome": "0.1.32",
75
+ "@stylistic/eslint-plugin": "5.7.0",
76
+ "@types/chrome": "0.1.36",
77
77
  "@types/eslint": "9.6.1",
78
- "@types/jasmine": "5.1.13",
78
+ "@types/jasmine": "6.0.0",
79
79
  "@types/jest": "30.0.0",
80
80
  "@types/karma": "6.3.9",
81
81
  "@types/karma-coverage": "2.0.3",
82
- "@types/node": "25.0.3",
82
+ "@types/node": "25.0.10",
83
83
  "@types/ws": "8.18.1",
84
84
  "chokidar": "5.0.0",
85
85
  "eslint-plugin-compat": "6.0.2",
86
86
  "eslint-plugin-import": "2.32.0",
87
- "globals": "17.0.0",
87
+ "globals": "17.1.0",
88
88
  "globby": "16.1.0",
89
- "jasmine-core": "5.13.0",
89
+ "jasmine-core": "6.0.1",
90
90
  "jest": "30.2.0",
91
91
  "jest-extended": "7.0.0",
92
92
  "karma": "6.4.4",
@@ -98,19 +98,19 @@
98
98
  "karma-safari-launcher": "1.0.0",
99
99
  "karma-spec-reporter": "0.0.36",
100
100
  "less": "4.5.1",
101
- "prettier": "3.7.4",
102
- "puppeteer-core": "24.34.0",
103
- "rollup": "4.55.1",
101
+ "prettier": "3.8.1",
102
+ "puppeteer-core": "24.36.0",
103
+ "rollup": "4.56.0",
104
104
  "rollup-plugin-istanbul": "5.0.0",
105
105
  "ts-jest": "29.4.6",
106
106
  "tslib": "2.8.1",
107
107
  "typescript": "5.9.3",
108
- "typescript-eslint": "8.51.0",
109
- "ws": "8.18.3",
108
+ "typescript-eslint": "8.53.1",
109
+ "ws": "8.19.0",
110
110
  "yazl": "3.3.1"
111
111
  },
112
112
  "optionalDependencies": {
113
- "@rollup/rollup-linux-x64-gnu": "4.55.1",
114
- "@rollup/rollup-win32-x64-msvc": "4.55.1"
113
+ "@rollup/rollup-linux-x64-gnu": "4.56.0",
114
+ "@rollup/rollup-win32-x64-msvc": "4.56.0"
115
115
  }
116
116
  }