darkreader 4.9.117 → 4.9.119

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Dark Reader Ltd.
3
+ Copyright (c) 2026 Dark Reader Ltd.
4
4
 
5
5
  All rights reserved.
6
6
 
package/darkreader.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Dark Reader v4.9.117
2
+ * Dark Reader v4.9.119
3
3
  * https://darkreader.org/
4
4
  */
5
5
 
@@ -189,7 +189,10 @@
189
189
  }
190
190
  if (
191
191
  mimeType &&
192
- !response.headers.get("Content-Type").startsWith(mimeType)
192
+ !(
193
+ response.headers.get("Content-Type") === mimeType ||
194
+ response.headers.get("Content-Type").startsWith(`${mimeType};`)
195
+ )
193
196
  ) {
194
197
  throw new Error(`Mime type mismatch when loading ${url}`);
195
198
  }
@@ -2691,22 +2694,64 @@
2691
2694
  }
2692
2695
  });
2693
2696
  const ipV4RegExp = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
2694
- const MAX_CORS_DOMAINS = 16;
2695
- const corsDomains = new Set();
2697
+ const MAX_CORS_HOSTS = 16;
2698
+ const corsHosts = new Set();
2699
+ const checkedHosts = new Set();
2700
+ const localAliases = [
2701
+ "127-0-0-1.org.uk",
2702
+ "42foo.com",
2703
+ "domaincontrol.com",
2704
+ "fbi.com",
2705
+ "fuf.me",
2706
+ "lacolhost.com",
2707
+ "local.sisteminha.com",
2708
+ "localfabriek.nl",
2709
+ "localhost",
2710
+ "localhst.co.uk",
2711
+ "localmachine.info",
2712
+ "localmachine.name",
2713
+ "localtest.me",
2714
+ "lvh.me",
2715
+ "mouse-potato.com",
2716
+ "nip.io",
2717
+ "sslip.io",
2718
+ "vcap.me",
2719
+ "xip.io",
2720
+ "yoogle.com"
2721
+ ];
2722
+ const localSubDomains = [
2723
+ ".corp",
2724
+ ".direct",
2725
+ ".home",
2726
+ ".internal",
2727
+ ".intranet",
2728
+ ".lan",
2729
+ ".local",
2730
+ ".localdomain",
2731
+ ".test",
2732
+ ".zz",
2733
+ ...localAliases.map((alias) => `.${alias}`)
2734
+ ];
2696
2735
  function shouldIgnoreCors(url) {
2697
- const host = url.hostname;
2698
- if (!corsDomains.has(host)) {
2699
- corsDomains.add(host);
2736
+ const {host, hostname, port, protocol} = url;
2737
+ if (!corsHosts.has(host)) {
2738
+ corsHosts.add(host);
2739
+ }
2740
+ if (checkedHosts.has(host)) {
2741
+ return false;
2700
2742
  }
2701
2743
  if (
2702
- corsDomains.size >= MAX_CORS_DOMAINS ||
2703
- host === "localhost" ||
2704
- host.startsWith("[") ||
2705
- host.endsWith(".local") ||
2706
- host.match(ipV4RegExp)
2744
+ corsHosts.size >= MAX_CORS_HOSTS ||
2745
+ protocol !== "https:" ||
2746
+ port !== "" ||
2747
+ localAliases.includes(hostname) ||
2748
+ localSubDomains.some((sub) => hostname.endsWith(sub)) ||
2749
+ hostname.startsWith("[") ||
2750
+ hostname.match(ipV4RegExp)
2707
2751
  ) {
2708
2752
  return true;
2709
2753
  }
2754
+ checkedHosts.add(host);
2710
2755
  return false;
2711
2756
  }
2712
2757
 
@@ -2714,14 +2759,59 @@
2714
2759
  async function getImageDetails(url) {
2715
2760
  return new Promise(async (resolve, reject) => {
2716
2761
  try {
2717
- const dataURL = url.startsWith("data:")
2762
+ let dataURL = url.startsWith("data:")
2718
2763
  ? url
2719
2764
  : await getDataURL(url);
2720
2765
  const blob =
2721
2766
  tryConvertDataURLToBlobSync(dataURL) ??
2722
2767
  (await loadAsBlob(url));
2723
2768
  let image;
2769
+ let useViewBox = false;
2724
2770
  if (dataURL.startsWith("data:image/svg+xml")) {
2771
+ const commaIndex = dataURL.indexOf(",");
2772
+ if (commaIndex >= 0) {
2773
+ let svgText = dataURL.slice(commaIndex + 1);
2774
+ const encoding = dataURL
2775
+ .slice(0, commaIndex)
2776
+ .split(";")[1];
2777
+ if (encoding === "base64") {
2778
+ svgText = atob(svgText);
2779
+ }
2780
+ if (svgText.startsWith("<svg ")) {
2781
+ const closingIndex = svgText.indexOf(">");
2782
+ const svgOpening = svgText
2783
+ .slice(0, closingIndex + 1)
2784
+ .toLocaleLowerCase();
2785
+ if (
2786
+ svgOpening.includes("viewbox") &&
2787
+ !svgOpening.includes("width") &&
2788
+ !svgOpening.includes("height")
2789
+ ) {
2790
+ useViewBox = true;
2791
+ const viewboxIndex =
2792
+ svgOpening.indexOf('viewbox="');
2793
+ const viewboxCloseIndex = svgOpening.indexOf(
2794
+ 'viewbox="',
2795
+ viewboxIndex + 9
2796
+ );
2797
+ const viewBox = svgOpening
2798
+ .slice(
2799
+ viewboxIndex + 9,
2800
+ viewboxCloseIndex - 1
2801
+ )
2802
+ .split(" ")
2803
+ .map((x) => parseFloat(x));
2804
+ if (
2805
+ viewBox.length === 4 &&
2806
+ !viewBox.some((x) => isNaN(x))
2807
+ ) {
2808
+ const width = viewBox[2] - viewBox[0];
2809
+ const height = viewBox[3] - viewBox[1];
2810
+ dataURL = `data:image/svg+xml;base64,${btoa(`<svg width="${width}" height="${height}" ${svgText.slice(5)}`)}`;
2811
+ }
2812
+ }
2813
+ }
2814
+ }
2725
2815
  image = await loadImage(dataURL);
2726
2816
  } else {
2727
2817
  image =
@@ -2735,6 +2825,7 @@
2735
2825
  dataURL: analysis.isLarge ? "" : dataURL,
2736
2826
  width: image.width,
2737
2827
  height: image.height,
2828
+ useViewBox,
2738
2829
  ...analysis
2739
2830
  });
2740
2831
  });
@@ -2922,13 +3013,16 @@
2922
3013
  }
2923
3014
  document.addEventListener("securitypolicyviolation", onCSPError);
2924
3015
  const objectURLs = new Set();
2925
- function getFilteredImageURL({dataURL, width, height}, theme) {
3016
+ function getFilteredImageURL({dataURL, width, height, useViewBox}, theme) {
2926
3017
  if (dataURL.startsWith("data:image/svg+xml")) {
2927
3018
  dataURL = escapeXML(dataURL);
2928
3019
  }
2929
3020
  const matrix = getSVGFilterMatrixValue(theme);
3021
+ const size = useViewBox
3022
+ ? `viewBox="0 0 ${width} ${height}"`
3023
+ : `width="${width}" height="${height}"`;
2930
3024
  const svg = [
2931
- `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}">`,
3025
+ `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ${size}>`,
2932
3026
  "<defs>",
2933
3027
  '<filter id="darkreader-image-filter">',
2934
3028
  `<feColorMatrix type="matrix" values="${matrix}" />`,
@@ -4190,8 +4284,8 @@
4190
4284
  this.definedVars.add(varName);
4191
4285
  const isColor = Boolean(
4192
4286
  value.match(rawRGBSpaceRegex) ||
4193
- value.match(rawRGBCommaRegex) ||
4194
- parseColorWithCache(value)
4287
+ value.match(rawRGBCommaRegex) ||
4288
+ parseColorWithCache(value)
4195
4289
  );
4196
4290
  if (isColor) {
4197
4291
  this.unknownColorVars.add(varName);
@@ -4530,13 +4624,16 @@
4530
4624
  function isTextColorProperty(property) {
4531
4625
  return textColorProps.includes(property);
4532
4626
  }
4533
- const rawRGBSpaceRegex = /^(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})$/;
4627
+ const rawRGBSpaceRegex =
4628
+ /^(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})\s*(\/\s*\d+\.?\d*)?$/;
4534
4629
  const rawRGBCommaRegex = /^(\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})$/;
4535
4630
  function parseRawColorValue(input) {
4536
4631
  const match =
4537
4632
  input.match(rawRGBSpaceRegex) ?? input.match(rawRGBCommaRegex);
4538
4633
  if (match) {
4539
- const color = `rgb(${match[1]}, ${match[2]}, ${match[3]})`;
4634
+ const color = match[4]
4635
+ ? `rgb(${match[1]} ${match[2]} ${match[3]} / ${match[4]})`
4636
+ : `rgb(${match[1]}, ${match[2]}, ${match[3]})`;
4540
4637
  return {isRaw: true, color};
4541
4638
  }
4542
4639
  return {isRaw: false, color: input};
@@ -5581,8 +5678,8 @@
5581
5678
  }
5582
5679
  const shouldAnalyze = Boolean(
5583
5680
  svg &&
5584
- (svg.getAttribute("class")?.includes("logo") ||
5585
- svg.parentElement?.getAttribute("class")?.includes("logo"))
5681
+ (svg.getAttribute("class")?.includes("logo") ||
5682
+ svg.parentElement?.getAttribute("class")?.includes("logo"))
5586
5683
  );
5587
5684
  svgAnalysisConditionCache.set(svg, shouldAnalyze);
5588
5685
  return shouldAnalyze;
@@ -5625,6 +5722,7 @@
5625
5722
  svgNodesRoots.set(svgElement, root);
5626
5723
  return root;
5627
5724
  }
5725
+ const inlineStringValueCache = new Map();
5628
5726
  function overrideInlineStyle(
5629
5727
  element,
5630
5728
  theme,
@@ -5652,6 +5750,13 @@
5652
5750
  }
5653
5751
  const unsetProps = new Set(Object.keys(overrides));
5654
5752
  function setCustomProp(targetCSSProp, modifierCSSProp, cssVal) {
5753
+ const cachedStringValue = inlineStringValueCache
5754
+ .get(modifierCSSProp)
5755
+ ?.get(cssVal);
5756
+ if (cachedStringValue) {
5757
+ setStaticValue(cachedStringValue);
5758
+ return;
5759
+ }
5655
5760
  const mod = getModifiableCSSDeclaration(
5656
5761
  modifierCSSProp,
5657
5762
  cssVal,
@@ -5718,6 +5823,10 @@
5718
5823
  typeof mod.value === "function" ? mod.value(theme) : mod.value;
5719
5824
  if (typeof value === "string") {
5720
5825
  setStaticValue(value);
5826
+ if (!inlineStringValueCache.has(modifierCSSProp)) {
5827
+ inlineStringValueCache.set(modifierCSSProp, new Map());
5828
+ }
5829
+ inlineStringValueCache.get(modifierCSSProp).set(cssVal, value);
5721
5830
  } else if (value instanceof Promise) {
5722
5831
  setAsyncValue(value, cssVal);
5723
5832
  } else if (typeof value === "object") {
@@ -5804,13 +5913,16 @@
5804
5913
  value.match(/^[0-9a-f]{6}$/i)
5805
5914
  ) {
5806
5915
  value = `#${value}`;
5916
+ } else if (value.match(/^#?[0-9a-f]{4}$/i)) {
5917
+ const hex = value.startsWith("#") ? value.substring(1) : value;
5918
+ value = `#${hex}00`;
5807
5919
  }
5808
5920
  setCustomProp("color", "color", value);
5809
5921
  }
5810
5922
  if (isSVGElement) {
5811
5923
  if (element.hasAttribute("fill")) {
5812
5924
  const value = element.getAttribute("fill");
5813
- if (value !== "none") {
5925
+ if (value !== "none" && value !== "currentColor") {
5814
5926
  if (!(element instanceof SVGTextElement)) {
5815
5927
  const handleSVGElement = () => {
5816
5928
  let isSVGSmall = false;
@@ -6142,7 +6254,7 @@
6142
6254
  forEach(node.querySelectorAll(STYLE_SELECTOR), (style) =>
6143
6255
  getManageableStyles(style, results, false)
6144
6256
  );
6145
- if (deep) {
6257
+ if (deep && (node.children?.length > 0 || node.shadowRoot)) {
6146
6258
  iterateShadowHosts(node, (host) =>
6147
6259
  getManageableStyles(host.shadowRoot, results, false)
6148
6260
  );
@@ -7310,10 +7422,23 @@
7310
7422
  removedStyles,
7311
7423
  movedStyles
7312
7424
  });
7425
+ const potentialHosts = new Set();
7313
7426
  additions.forEach((n) => {
7427
+ if (n.parentElement) {
7428
+ potentialHosts.add(n.parentElement);
7429
+ }
7430
+ if (n.previousElementSibling) {
7431
+ potentialHosts.add(n.previousElementSibling);
7432
+ }
7314
7433
  deepObserve(n);
7315
7434
  collectUndefinedElements(n);
7316
7435
  });
7436
+ potentialHosts.forEach((el) => {
7437
+ if (el.shadowRoot && !observedRoots.has(el)) {
7438
+ subscribeForShadowRootChanges(el);
7439
+ deepObserve(el.shadowRoot);
7440
+ }
7441
+ });
7317
7442
  additions.forEach(
7318
7443
  (node) => isCustomElement(node) && recordUndefinedElement(node)
7319
7444
  );
@@ -7427,6 +7552,14 @@
7427
7552
  });
7428
7553
  document.addEventListener("__darkreader__isDefined", handleIsDefined);
7429
7554
  collectUndefinedElements(document);
7555
+ addDOMReadyListener(() => {
7556
+ forEach(document.body.children, (el) => {
7557
+ if (el.shadowRoot && !observedRoots.has(el)) {
7558
+ subscribeForShadowRootChanges(el);
7559
+ deepObserve(el.shadowRoot);
7560
+ }
7561
+ });
7562
+ });
7430
7563
  }
7431
7564
  function resetObservers() {
7432
7565
  observers.forEach((o) => o.disconnect());
package/darkreader.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Dark Reader v4.9.117
2
+ * Dark Reader v4.9.119
3
3
  * https://darkreader.org/
4
4
  */
5
5
 
@@ -172,7 +172,10 @@ async function getOKResponse(url, mimeType, origin) {
172
172
  }
173
173
  if (
174
174
  mimeType &&
175
- !response.headers.get("Content-Type").startsWith(mimeType)
175
+ !(
176
+ response.headers.get("Content-Type") === mimeType ||
177
+ response.headers.get("Content-Type").startsWith(`${mimeType};`)
178
+ )
176
179
  ) {
177
180
  throw new Error(`Mime type mismatch when loading ${url}`);
178
181
  }
@@ -2603,22 +2606,64 @@ chrome.runtime.onMessage.addListener(({type, data, error, id}) => {
2603
2606
  }
2604
2607
  });
2605
2608
  const ipV4RegExp = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
2606
- const MAX_CORS_DOMAINS = 16;
2607
- const corsDomains = new Set();
2609
+ const MAX_CORS_HOSTS = 16;
2610
+ const corsHosts = new Set();
2611
+ const checkedHosts = new Set();
2612
+ const localAliases = [
2613
+ "127-0-0-1.org.uk",
2614
+ "42foo.com",
2615
+ "domaincontrol.com",
2616
+ "fbi.com",
2617
+ "fuf.me",
2618
+ "lacolhost.com",
2619
+ "local.sisteminha.com",
2620
+ "localfabriek.nl",
2621
+ "localhost",
2622
+ "localhst.co.uk",
2623
+ "localmachine.info",
2624
+ "localmachine.name",
2625
+ "localtest.me",
2626
+ "lvh.me",
2627
+ "mouse-potato.com",
2628
+ "nip.io",
2629
+ "sslip.io",
2630
+ "vcap.me",
2631
+ "xip.io",
2632
+ "yoogle.com"
2633
+ ];
2634
+ const localSubDomains = [
2635
+ ".corp",
2636
+ ".direct",
2637
+ ".home",
2638
+ ".internal",
2639
+ ".intranet",
2640
+ ".lan",
2641
+ ".local",
2642
+ ".localdomain",
2643
+ ".test",
2644
+ ".zz",
2645
+ ...localAliases.map((alias) => `.${alias}`)
2646
+ ];
2608
2647
  function shouldIgnoreCors(url) {
2609
- const host = url.hostname;
2610
- if (!corsDomains.has(host)) {
2611
- corsDomains.add(host);
2648
+ const {host, hostname, port, protocol} = url;
2649
+ if (!corsHosts.has(host)) {
2650
+ corsHosts.add(host);
2651
+ }
2652
+ if (checkedHosts.has(host)) {
2653
+ return false;
2612
2654
  }
2613
2655
  if (
2614
- corsDomains.size >= MAX_CORS_DOMAINS ||
2615
- host === "localhost" ||
2616
- host.startsWith("[") ||
2617
- host.endsWith(".local") ||
2618
- host.match(ipV4RegExp)
2656
+ corsHosts.size >= MAX_CORS_HOSTS ||
2657
+ protocol !== "https:" ||
2658
+ port !== "" ||
2659
+ localAliases.includes(hostname) ||
2660
+ localSubDomains.some((sub) => hostname.endsWith(sub)) ||
2661
+ hostname.startsWith("[") ||
2662
+ hostname.match(ipV4RegExp)
2619
2663
  ) {
2620
2664
  return true;
2621
2665
  }
2666
+ checkedHosts.add(host);
2622
2667
  return false;
2623
2668
  }
2624
2669
 
@@ -2626,13 +2671,51 @@ const imageManager = new AsyncQueue();
2626
2671
  async function getImageDetails(url) {
2627
2672
  return new Promise(async (resolve, reject) => {
2628
2673
  try {
2629
- const dataURL = url.startsWith("data:")
2630
- ? url
2631
- : await getDataURL(url);
2674
+ let dataURL = url.startsWith("data:") ? url : await getDataURL(url);
2632
2675
  const blob =
2633
2676
  tryConvertDataURLToBlobSync(dataURL) ?? (await loadAsBlob(url));
2634
2677
  let image;
2678
+ let useViewBox = false;
2635
2679
  if (dataURL.startsWith("data:image/svg+xml")) {
2680
+ const commaIndex = dataURL.indexOf(",");
2681
+ if (commaIndex >= 0) {
2682
+ let svgText = dataURL.slice(commaIndex + 1);
2683
+ const encoding = dataURL.slice(0, commaIndex).split(";")[1];
2684
+ if (encoding === "base64") {
2685
+ svgText = atob(svgText);
2686
+ }
2687
+ if (svgText.startsWith("<svg ")) {
2688
+ const closingIndex = svgText.indexOf(">");
2689
+ const svgOpening = svgText
2690
+ .slice(0, closingIndex + 1)
2691
+ .toLocaleLowerCase();
2692
+ if (
2693
+ svgOpening.includes("viewbox") &&
2694
+ !svgOpening.includes("width") &&
2695
+ !svgOpening.includes("height")
2696
+ ) {
2697
+ useViewBox = true;
2698
+ const viewboxIndex =
2699
+ svgOpening.indexOf('viewbox="');
2700
+ const viewboxCloseIndex = svgOpening.indexOf(
2701
+ 'viewbox="',
2702
+ viewboxIndex + 9
2703
+ );
2704
+ const viewBox = svgOpening
2705
+ .slice(viewboxIndex + 9, viewboxCloseIndex - 1)
2706
+ .split(" ")
2707
+ .map((x) => parseFloat(x));
2708
+ if (
2709
+ viewBox.length === 4 &&
2710
+ !viewBox.some((x) => isNaN(x))
2711
+ ) {
2712
+ const width = viewBox[2] - viewBox[0];
2713
+ const height = viewBox[3] - viewBox[1];
2714
+ dataURL = `data:image/svg+xml;base64,${btoa(`<svg width="${width}" height="${height}" ${svgText.slice(5)}`)}`;
2715
+ }
2716
+ }
2717
+ }
2718
+ }
2636
2719
  image = await loadImage(dataURL);
2637
2720
  } else {
2638
2721
  image =
@@ -2646,6 +2729,7 @@ async function getImageDetails(url) {
2646
2729
  dataURL: analysis.isLarge ? "" : dataURL,
2647
2730
  width: image.width,
2648
2731
  height: image.height,
2732
+ useViewBox,
2649
2733
  ...analysis
2650
2734
  });
2651
2735
  });
@@ -2832,13 +2916,16 @@ function onCSPError(err) {
2832
2916
  }
2833
2917
  document.addEventListener("securitypolicyviolation", onCSPError);
2834
2918
  const objectURLs = new Set();
2835
- function getFilteredImageURL({dataURL, width, height}, theme) {
2919
+ function getFilteredImageURL({dataURL, width, height, useViewBox}, theme) {
2836
2920
  if (dataURL.startsWith("data:image/svg+xml")) {
2837
2921
  dataURL = escapeXML(dataURL);
2838
2922
  }
2839
2923
  const matrix = getSVGFilterMatrixValue(theme);
2924
+ const size = useViewBox
2925
+ ? `viewBox="0 0 ${width} ${height}"`
2926
+ : `width="${width}" height="${height}"`;
2840
2927
  const svg = [
2841
- `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}">`,
2928
+ `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ${size}>`,
2842
2929
  "<defs>",
2843
2930
  '<filter id="darkreader-image-filter">',
2844
2931
  `<feColorMatrix type="matrix" values="${matrix}" />`,
@@ -4049,8 +4136,8 @@ class VariablesStore {
4049
4136
  this.definedVars.add(varName);
4050
4137
  const isColor = Boolean(
4051
4138
  value.match(rawRGBSpaceRegex) ||
4052
- value.match(rawRGBCommaRegex) ||
4053
- parseColorWithCache(value)
4139
+ value.match(rawRGBCommaRegex) ||
4140
+ parseColorWithCache(value)
4054
4141
  );
4055
4142
  if (isColor) {
4056
4143
  this.unknownColorVars.add(varName);
@@ -4381,13 +4468,16 @@ const textColorProps = [
4381
4468
  function isTextColorProperty(property) {
4382
4469
  return textColorProps.includes(property);
4383
4470
  }
4384
- const rawRGBSpaceRegex = /^(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})$/;
4471
+ const rawRGBSpaceRegex =
4472
+ /^(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})\s*(\/\s*\d+\.?\d*)?$/;
4385
4473
  const rawRGBCommaRegex = /^(\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})$/;
4386
4474
  function parseRawColorValue(input) {
4387
4475
  const match =
4388
4476
  input.match(rawRGBSpaceRegex) ?? input.match(rawRGBCommaRegex);
4389
4477
  if (match) {
4390
- const color = `rgb(${match[1]}, ${match[2]}, ${match[3]})`;
4478
+ const color = match[4]
4479
+ ? `rgb(${match[1]} ${match[2]} ${match[3]} / ${match[4]})`
4480
+ : `rgb(${match[1]}, ${match[2]}, ${match[3]})`;
4391
4481
  return {isRaw: true, color};
4392
4482
  }
4393
4483
  return {isRaw: false, color: input};
@@ -5418,8 +5508,8 @@ function shouldAnalyzeSVGAsImage(svg) {
5418
5508
  }
5419
5509
  const shouldAnalyze = Boolean(
5420
5510
  svg &&
5421
- (svg.getAttribute("class")?.includes("logo") ||
5422
- svg.parentElement?.getAttribute("class")?.includes("logo"))
5511
+ (svg.getAttribute("class")?.includes("logo") ||
5512
+ svg.parentElement?.getAttribute("class")?.includes("logo"))
5423
5513
  );
5424
5514
  svgAnalysisConditionCache.set(svg, shouldAnalyze);
5425
5515
  return shouldAnalyze;
@@ -5462,6 +5552,7 @@ function getSVGElementRoot(svgElement) {
5462
5552
  svgNodesRoots.set(svgElement, root);
5463
5553
  return root;
5464
5554
  }
5555
+ const inlineStringValueCache = new Map();
5465
5556
  function overrideInlineStyle(
5466
5557
  element,
5467
5558
  theme,
@@ -5489,6 +5580,13 @@ function overrideInlineStyle(
5489
5580
  }
5490
5581
  const unsetProps = new Set(Object.keys(overrides));
5491
5582
  function setCustomProp(targetCSSProp, modifierCSSProp, cssVal) {
5583
+ const cachedStringValue = inlineStringValueCache
5584
+ .get(modifierCSSProp)
5585
+ ?.get(cssVal);
5586
+ if (cachedStringValue) {
5587
+ setStaticValue(cachedStringValue);
5588
+ return;
5589
+ }
5492
5590
  const mod = getModifiableCSSDeclaration(
5493
5591
  modifierCSSProp,
5494
5592
  cssVal,
@@ -5554,6 +5652,10 @@ function overrideInlineStyle(
5554
5652
  typeof mod.value === "function" ? mod.value(theme) : mod.value;
5555
5653
  if (typeof value === "string") {
5556
5654
  setStaticValue(value);
5655
+ if (!inlineStringValueCache.has(modifierCSSProp)) {
5656
+ inlineStringValueCache.set(modifierCSSProp, new Map());
5657
+ }
5658
+ inlineStringValueCache.get(modifierCSSProp).set(cssVal, value);
5557
5659
  } else if (value instanceof Promise) {
5558
5660
  setAsyncValue(value, cssVal);
5559
5661
  } else if (typeof value === "object") {
@@ -5628,13 +5730,16 @@ function overrideInlineStyle(
5628
5730
  let value = element.getAttribute("color");
5629
5731
  if (value.match(/^[0-9a-f]{3}$/i) || value.match(/^[0-9a-f]{6}$/i)) {
5630
5732
  value = `#${value}`;
5733
+ } else if (value.match(/^#?[0-9a-f]{4}$/i)) {
5734
+ const hex = value.startsWith("#") ? value.substring(1) : value;
5735
+ value = `#${hex}00`;
5631
5736
  }
5632
5737
  setCustomProp("color", "color", value);
5633
5738
  }
5634
5739
  if (isSVGElement) {
5635
5740
  if (element.hasAttribute("fill")) {
5636
5741
  const value = element.getAttribute("fill");
5637
- if (value !== "none") {
5742
+ if (value !== "none" && value !== "currentColor") {
5638
5743
  if (!(element instanceof SVGTextElement)) {
5639
5744
  const handleSVGElement = () => {
5640
5745
  let isSVGSmall = false;
@@ -5946,7 +6051,7 @@ function getManageableStyles(node, results = [], deep = true) {
5946
6051
  forEach(node.querySelectorAll(STYLE_SELECTOR), (style) =>
5947
6052
  getManageableStyles(style, results, false)
5948
6053
  );
5949
- if (deep) {
6054
+ if (deep && (node.children?.length > 0 || node.shadowRoot)) {
5950
6055
  iterateShadowHosts(node, (host) =>
5951
6056
  getManageableStyles(host.shadowRoot, results, false)
5952
6057
  );
@@ -7072,10 +7177,23 @@ function watchForStylePositions(currentStyles, update, shadowRootDiscovered) {
7072
7177
  removedStyles,
7073
7178
  movedStyles
7074
7179
  });
7180
+ const potentialHosts = new Set();
7075
7181
  additions.forEach((n) => {
7182
+ if (n.parentElement) {
7183
+ potentialHosts.add(n.parentElement);
7184
+ }
7185
+ if (n.previousElementSibling) {
7186
+ potentialHosts.add(n.previousElementSibling);
7187
+ }
7076
7188
  deepObserve(n);
7077
7189
  collectUndefinedElements(n);
7078
7190
  });
7191
+ potentialHosts.forEach((el) => {
7192
+ if (el.shadowRoot && !observedRoots.has(el)) {
7193
+ subscribeForShadowRootChanges(el);
7194
+ deepObserve(el.shadowRoot);
7195
+ }
7196
+ });
7079
7197
  additions.forEach(
7080
7198
  (node) => isCustomElement(node) && recordUndefinedElement(node)
7081
7199
  );
@@ -7189,6 +7307,14 @@ function watchForStylePositions(currentStyles, update, shadowRootDiscovered) {
7189
7307
  });
7190
7308
  document.addEventListener("__darkreader__isDefined", handleIsDefined);
7191
7309
  collectUndefinedElements(document);
7310
+ addDOMReadyListener(() => {
7311
+ forEach(document.body.children, (el) => {
7312
+ if (el.shadowRoot && !observedRoots.has(el)) {
7313
+ subscribeForShadowRootChanges(el);
7314
+ deepObserve(el.shadowRoot);
7315
+ }
7316
+ });
7317
+ });
7192
7318
  }
7193
7319
  function resetObservers() {
7194
7320
  observers.forEach((o) => o.disconnect());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "darkreader",
3
- "version": "4.9.117",
3
+ "version": "4.9.119",
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",
@@ -41,7 +41,7 @@
41
41
  "type": "module",
42
42
  "repository": {
43
43
  "type": "git",
44
- "url": "https://github.com/darkreader/darkreader.git"
44
+ "url": "git+https://github.com/darkreader/darkreader.git"
45
45
  },
46
46
  "author": "Alexander Shutau <darkreaderapp@gmail.com> (https://darkreader.org/)",
47
47
  "license": "MIT",
@@ -67,26 +67,26 @@
67
67
  },
68
68
  "devDependencies": {
69
69
  "@eslint/compat": "2.0.0",
70
- "@eslint/eslintrc": "3.3.1",
71
- "@eslint/js": "9.39.1",
70
+ "@eslint/eslintrc": "3.3.3",
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
75
  "@stylistic/eslint-plugin": "5.6.1",
76
- "@types/chrome": "0.1.31",
76
+ "@types/chrome": "0.1.32",
77
77
  "@types/eslint": "9.6.1",
78
78
  "@types/jasmine": "5.1.13",
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": "24.10.1",
82
+ "@types/node": "25.0.3",
83
83
  "@types/ws": "8.18.1",
84
- "chokidar": "4.0.3",
84
+ "chokidar": "5.0.0",
85
85
  "eslint-plugin-compat": "6.0.2",
86
86
  "eslint-plugin-import": "2.32.0",
87
- "globals": "16.5.0",
88
- "globby": "16.0.0",
89
- "jasmine-core": "5.12.1",
87
+ "globals": "17.0.0",
88
+ "globby": "16.1.0",
89
+ "jasmine-core": "5.13.0",
90
90
  "jest": "30.2.0",
91
91
  "jest-extended": "7.0.0",
92
92
  "karma": "6.4.4",
@@ -97,20 +97,20 @@
97
97
  "karma-rollup-preprocessor": "7.0.8",
98
98
  "karma-safari-launcher": "1.0.0",
99
99
  "karma-spec-reporter": "0.0.36",
100
- "less": "4.4.2",
101
- "prettier": "3.6.2",
102
- "puppeteer-core": "24.30.0",
103
- "rollup": "4.53.3",
100
+ "less": "4.5.1",
101
+ "prettier": "3.7.4",
102
+ "puppeteer-core": "24.34.0",
103
+ "rollup": "4.55.1",
104
104
  "rollup-plugin-istanbul": "5.0.0",
105
- "ts-jest": "29.4.5",
105
+ "ts-jest": "29.4.6",
106
106
  "tslib": "2.8.1",
107
107
  "typescript": "5.9.3",
108
- "typescript-eslint": "8.47.0",
108
+ "typescript-eslint": "8.51.0",
109
109
  "ws": "8.18.3",
110
110
  "yazl": "3.3.1"
111
111
  },
112
112
  "optionalDependencies": {
113
- "@rollup/rollup-linux-x64-gnu": "4.53.3",
114
- "@rollup/rollup-win32-x64-msvc": "4.53.3"
113
+ "@rollup/rollup-linux-x64-gnu": "4.55.1",
114
+ "@rollup/rollup-win32-x64-msvc": "4.55.1"
115
115
  }
116
116
  }