darkreader 4.9.114 → 4.9.117

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 (3) hide show
  1. package/darkreader.js +138 -90
  2. package/darkreader.mjs +130 -87
  3. package/package.json +17 -17
package/darkreader.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Dark Reader v4.9.114
2
+ * Dark Reader v4.9.117
3
3
  * https://darkreader.org/
4
4
  */
5
5
 
@@ -1945,7 +1945,8 @@
1945
1945
  }
1946
1946
  }
1947
1947
  if (
1948
- cssText.includes("background-color: ;") &&
1948
+ (cssText.includes("background-color: ;") ||
1949
+ cssText.includes("background-image: ;")) &&
1949
1950
  !style.getPropertyValue("background")
1950
1951
  ) {
1951
1952
  handleEmptyShorthand("background", style, iterate);
@@ -1978,6 +1979,7 @@
1978
1979
  }
1979
1980
  } else if (shorthand === "background") {
1980
1981
  iterate("background-color", "#ffffff");
1982
+ iterate("background-image", "none");
1981
1983
  }
1982
1984
  }
1983
1985
  }
@@ -2654,6 +2656,13 @@
2654
2656
  if (window.DarkReader?.Plugins?.fetch) {
2655
2657
  return window.DarkReader.Plugins.fetch(request);
2656
2658
  }
2659
+ const parsedURL = new URL(request.url);
2660
+ if (
2661
+ parsedURL.origin !== request.origin &&
2662
+ shouldIgnoreCors(parsedURL)
2663
+ ) {
2664
+ throw new Error("Cross-origin limit reached");
2665
+ }
2657
2666
  return new Promise((resolve, reject) => {
2658
2667
  const id = generateUID();
2659
2668
  resolvers$1.set(id, resolve);
@@ -2681,6 +2690,25 @@
2681
2690
  }
2682
2691
  }
2683
2692
  });
2693
+ const ipV4RegExp = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
2694
+ const MAX_CORS_DOMAINS = 16;
2695
+ const corsDomains = new Set();
2696
+ function shouldIgnoreCors(url) {
2697
+ const host = url.hostname;
2698
+ if (!corsDomains.has(host)) {
2699
+ corsDomains.add(host);
2700
+ }
2701
+ if (
2702
+ corsDomains.size >= MAX_CORS_DOMAINS ||
2703
+ host === "localhost" ||
2704
+ host.startsWith("[") ||
2705
+ host.endsWith(".local") ||
2706
+ host.match(ipV4RegExp)
2707
+ ) {
2708
+ return true;
2709
+ }
2710
+ return false;
2711
+ }
2684
2712
 
2685
2713
  const imageManager = new AsyncQueue();
2686
2714
  async function getImageDetails(url) {
@@ -2720,7 +2748,11 @@
2720
2748
  if (parsedURL.origin === location.origin) {
2721
2749
  return await loadAsDataURL(url);
2722
2750
  }
2723
- return await bgFetch({url, responseType: "data-url"});
2751
+ return await bgFetch({
2752
+ url,
2753
+ responseType: "data-url",
2754
+ origin: location.origin
2755
+ });
2724
2756
  }
2725
2757
  async function tryCreateImageBitmap(blob) {
2726
2758
  try {
@@ -3475,7 +3507,15 @@
3475
3507
  awaitingForImageLoading.set(url, []);
3476
3508
  imageDetails = await getImageDetails(url);
3477
3509
  imageDetailsCache.set(url, imageDetails);
3478
- writeImageDetailsCache(url, imageDetails);
3510
+ if (!url.startsWith("data:")) {
3511
+ const parsedURL = new URL(url);
3512
+ if (parsedURL.origin === location.origin) {
3513
+ writeImageDetailsCache(
3514
+ url,
3515
+ imageDetails
3516
+ );
3517
+ }
3518
+ }
3479
3519
  awaitingForImageLoading
3480
3520
  .get(url)
3481
3521
  .forEach((resolve) =>
@@ -3719,6 +3759,9 @@
3719
3759
  const VAR_TYPE_TEXT_COLOR = 1 << 1;
3720
3760
  const VAR_TYPE_BORDER_COLOR = 1 << 2;
3721
3761
  const VAR_TYPE_BG_IMG = 1 << 3;
3762
+ const shouldSetDefaultColor =
3763
+ !location.hostname.startsWith("www.ebay.") &&
3764
+ !location.hostname.includes(".ebay.");
3722
3765
  class VariablesStore {
3723
3766
  constructor() {
3724
3767
  this.varTypes = new Map();
@@ -3976,10 +4019,12 @@
3976
4019
  (isSimpleConstructedColor && property === "background")
3977
4020
  ) {
3978
4021
  return (theme) => {
3979
- const defaultFallback = tryModifyBgColor(
3980
- isConstructedColor ? "255, 255, 255" : "#ffffff",
3981
- theme
3982
- );
4022
+ const defaultFallback = shouldSetDefaultColor
4023
+ ? tryModifyBgColor(
4024
+ isConstructedColor ? "255, 255, 255" : "#ffffff",
4025
+ theme
4026
+ )
4027
+ : "transparent";
3983
4028
  return replaceCSSVariablesNames(
3984
4029
  sourceValue,
3985
4030
  (v) => wrapBgColorVariableName(v),
@@ -5188,6 +5233,8 @@
5188
5233
  }
5189
5234
 
5190
5235
  const hostsBreakingOnStylePosition = [
5236
+ "gogoprivate.com",
5237
+ "gprivate.com",
5191
5238
  "www.berlingske.dk",
5192
5239
  "www.bloomberg.com",
5193
5240
  "www.diffusioneshop.com",
@@ -5556,12 +5603,49 @@
5556
5603
  }
5557
5604
  return false;
5558
5605
  }
5606
+ const LOOP_DETECTION_THRESHOLD = 1000;
5607
+ const MAX_LOOP_CYCLES = 10;
5608
+ const elementsLastChanges = new WeakMap();
5609
+ const elementsLoopCycles = new WeakMap();
5610
+ const SMALL_SVG_THRESHOLD = 32;
5611
+ const svgNodesRoots = new WeakMap();
5612
+ const svgRootSizeTestResults = new WeakMap();
5613
+ function getSVGElementRoot(svgElement) {
5614
+ if (!svgElement) {
5615
+ return null;
5616
+ }
5617
+ if (svgNodesRoots.has(svgElement)) {
5618
+ return svgNodesRoots.get(svgElement);
5619
+ }
5620
+ if (svgElement instanceof SVGSVGElement) {
5621
+ return svgElement;
5622
+ }
5623
+ const parent = svgElement.parentNode;
5624
+ const root = getSVGElementRoot(parent);
5625
+ svgNodesRoots.set(svgElement, root);
5626
+ return root;
5627
+ }
5559
5628
  function overrideInlineStyle(
5560
5629
  element,
5561
5630
  theme,
5562
5631
  ignoreInlineSelectors,
5563
5632
  ignoreImageSelectors
5564
5633
  ) {
5634
+ if (elementsLastChanges.has(element)) {
5635
+ if (
5636
+ Date.now() - elementsLastChanges.get(element) <
5637
+ LOOP_DETECTION_THRESHOLD
5638
+ ) {
5639
+ const cycles = elementsLoopCycles.get(element) ?? 0;
5640
+ elementsLoopCycles.set(element, cycles + 1);
5641
+ }
5642
+ if ((elementsLoopCycles.get(element) ?? 0) >= MAX_LOOP_CYCLES) {
5643
+ return;
5644
+ }
5645
+ }
5646
+ if (element.parentElement?.dataset.nodeViewContent) {
5647
+ return;
5648
+ }
5565
5649
  const cacheKey = getInlineStyleCacheKey(element, theme);
5566
5650
  if (cacheKey === inlineStyleCache.get(element)) {
5567
5651
  return;
@@ -5725,16 +5809,34 @@
5725
5809
  }
5726
5810
  if (isSVGElement) {
5727
5811
  if (element.hasAttribute("fill")) {
5728
- const SMALL_SVG_LIMIT = 32;
5729
5812
  const value = element.getAttribute("fill");
5730
5813
  if (value !== "none") {
5731
5814
  if (!(element instanceof SVGTextElement)) {
5732
5815
  const handleSVGElement = () => {
5733
- const {width, height} =
5734
- element.getBoundingClientRect();
5735
- const isBg =
5736
- width > SMALL_SVG_LIMIT ||
5737
- height > SMALL_SVG_LIMIT;
5816
+ let isSVGSmall = false;
5817
+ const root = getSVGElementRoot(element);
5818
+ if (!root) {
5819
+ return;
5820
+ }
5821
+ if (svgRootSizeTestResults.has(root)) {
5822
+ isSVGSmall = svgRootSizeTestResults.get(root);
5823
+ } else {
5824
+ const svgBounds = root.getBoundingClientRect();
5825
+ isSVGSmall =
5826
+ svgBounds.width * svgBounds.height <=
5827
+ Math.pow(SMALL_SVG_THRESHOLD, 2);
5828
+ svgRootSizeTestResults.set(root, isSVGSmall);
5829
+ }
5830
+ let isBg;
5831
+ if (isSVGSmall) {
5832
+ isBg = false;
5833
+ } else {
5834
+ const {width, height} =
5835
+ element.getBoundingClientRect();
5836
+ isBg =
5837
+ width > SMALL_SVG_THRESHOLD ||
5838
+ height > SMALL_SVG_THRESHOLD;
5839
+ }
5738
5840
  setCustomProp(
5739
5841
  "fill",
5740
5842
  isBg ? "background-color" : "color",
@@ -5826,6 +5928,7 @@
5826
5928
  element.removeAttribute(overrides[cssProp].dataAttr);
5827
5929
  });
5828
5930
  inlineStyleCache.set(element, getInlineStyleCacheKey(element, theme));
5931
+ elementsLastChanges.set(element, Date.now());
5829
5932
  }
5830
5933
 
5831
5934
  const metaThemeColorName = "theme-color";
@@ -6048,7 +6151,8 @@
6048
6151
  return results;
6049
6152
  }
6050
6153
  const syncStyleSet = new WeakSet();
6051
- const corsStyleSet = new WeakSet();
6154
+ const corsCopies = new WeakMap();
6155
+ const corsCopiesTextLengths = new WeakMap();
6052
6156
  let loadingLinkCounter = 0;
6053
6157
  const rejectorsForLoadingLinks = new Map();
6054
6158
  function cleanLoadingLinks() {
@@ -6056,7 +6160,6 @@
6056
6160
  }
6057
6161
  function manageStyle(element, {update, loadingStart, loadingEnd}) {
6058
6162
  const inMode = getStyleInjectionMode();
6059
- let corsCopy = null;
6060
6163
  let syncStyle = null;
6061
6164
  if (inMode === "next") {
6062
6165
  const prevStyles = [];
@@ -6067,18 +6170,12 @@
6067
6170
  ) {
6068
6171
  prevStyles.push(next);
6069
6172
  }
6070
- corsCopy =
6071
- prevStyles.find(
6072
- (el) =>
6073
- el.matches(".darkreader--cors") && !corsStyleSet.has(el)
6074
- ) || null;
6075
6173
  syncStyle =
6076
6174
  prevStyles.find(
6077
6175
  (el) =>
6078
6176
  el.matches(".darkreader--sync") && !syncStyleSet.has(el)
6079
6177
  ) || null;
6080
6178
  }
6081
- let corsCopyPositionWatcher = null;
6082
6179
  let syncStylePositionWatcher = null;
6083
6180
  let cancelAsyncOperations = false;
6084
6181
  let isOverrideEmpty = true;
@@ -6140,8 +6237,8 @@
6140
6237
  return result;
6141
6238
  }
6142
6239
  function getRulesSync() {
6143
- if (corsCopy) {
6144
- return corsCopy.sheet.cssRules;
6240
+ if (corsCopies.has(element)) {
6241
+ return corsCopies.get(element).cssRules;
6145
6242
  }
6146
6243
  if (containsCSSImport()) {
6147
6244
  return null;
@@ -6163,29 +6260,13 @@
6163
6260
  }
6164
6261
  function insertStyle() {
6165
6262
  if (inMode === "next") {
6166
- if (corsCopy) {
6167
- if (element.nextSibling !== corsCopy) {
6168
- element.parentNode.insertBefore(
6169
- corsCopy,
6170
- element.nextSibling
6171
- );
6172
- }
6173
- if (corsCopy.nextSibling !== syncStyle) {
6174
- element.parentNode.insertBefore(
6175
- syncStyle,
6176
- corsCopy.nextSibling
6177
- );
6178
- }
6179
- } else if (element.nextSibling !== syncStyle) {
6263
+ if (element.nextSibling !== syncStyle) {
6180
6264
  element.parentNode.insertBefore(
6181
6265
  syncStyle,
6182
6266
  element.nextSibling
6183
6267
  );
6184
6268
  }
6185
6269
  } else if (inMode === "away") {
6186
- if (corsCopy && !corsCopy.parentNode) {
6187
- injectStyleAway(corsCopy);
6188
- }
6189
6270
  injectStyleAway(syncStyle);
6190
6271
  }
6191
6272
  }
@@ -6261,8 +6342,8 @@
6261
6342
  return null;
6262
6343
  }
6263
6344
  await createOrUpdateCORSCopy(cssText, cssBasePath);
6264
- if (corsCopy) {
6265
- return corsCopy.sheet.cssRules;
6345
+ if (corsCopies.has(element)) {
6346
+ return corsCopies.get(element).cssRules;
6266
6347
  }
6267
6348
  return null;
6268
6349
  }
@@ -6273,44 +6354,25 @@
6273
6354
  cssText,
6274
6355
  cssBasePath
6275
6356
  );
6276
- if (corsCopy) {
6357
+ if (corsCopies.has(element)) {
6277
6358
  if (
6278
- (corsCopy.textContent?.length ?? 0) <
6359
+ (corsCopiesTextLengths.get(element) ?? 0) <
6279
6360
  fullCSSText.length
6280
6361
  ) {
6281
- corsCopy.textContent = fullCSSText;
6362
+ corsCopies.get(element).replaceSync(fullCSSText);
6363
+ corsCopiesTextLengths.set(
6364
+ element,
6365
+ fullCSSText.length
6366
+ );
6282
6367
  }
6283
6368
  } else {
6284
- corsCopy = createCORSCopy(
6285
- fullCSSText,
6286
- inMode === "next"
6287
- ? (cc) =>
6288
- element.parentNode.insertBefore(
6289
- cc,
6290
- element.nextSibling
6291
- )
6292
- : injectStyleAway
6293
- );
6294
- if (corsCopy) {
6295
- if (inMode === "next") {
6296
- element.parentNode.insertBefore(
6297
- corsCopy,
6298
- element.nextSibling
6299
- );
6300
- } else if (inMode === "away") {
6301
- injectStyleAway(corsCopy);
6302
- }
6303
- }
6369
+ const corsCopy = new CSSStyleSheet();
6370
+ corsCopy.replaceSync(fullCSSText);
6371
+ corsCopies.set(element, corsCopy);
6304
6372
  }
6305
6373
  } catch (err) {
6306
6374
  logWarn(err);
6307
6375
  }
6308
- if (corsCopy && inMode === "next") {
6309
- corsCopyPositionWatcher = watchForNodePosition(
6310
- corsCopy,
6311
- "prev-sibling"
6312
- );
6313
- }
6314
6376
  }
6315
6377
  }
6316
6378
  function details(options) {
@@ -6433,13 +6495,12 @@
6433
6495
  function pause() {
6434
6496
  observer.disconnect();
6435
6497
  cancelAsyncOperations = true;
6436
- corsCopyPositionWatcher && corsCopyPositionWatcher.stop();
6437
6498
  syncStylePositionWatcher && syncStylePositionWatcher.stop();
6438
6499
  sheetChangeWatcher.stop();
6439
6500
  }
6440
6501
  function destroy() {
6441
6502
  pause();
6442
- removeNode(corsCopy);
6503
+ corsCopies.delete(element);
6443
6504
  removeNode(syncStyle);
6444
6505
  loadingEnd();
6445
6506
  if (rejectorsForLoadingLinks.has(loadingLinkId)) {
@@ -6467,7 +6528,6 @@
6467
6528
  }
6468
6529
  logWarn("Restore style", syncStyle, element);
6469
6530
  insertStyle();
6470
- corsCopyPositionWatcher && corsCopyPositionWatcher.skip();
6471
6531
  syncStylePositionWatcher && syncStylePositionWatcher.skip();
6472
6532
  if (!isOverrideEmpty) {
6473
6533
  forceRenderStyle = true;
@@ -6540,7 +6600,9 @@
6540
6600
  origin: location.origin
6541
6601
  });
6542
6602
  }
6543
- writeCSSFetchCache(url, text);
6603
+ if (parsedURL.origin === location.origin) {
6604
+ writeCSSFetchCache(url, text);
6605
+ }
6544
6606
  return text;
6545
6607
  }
6546
6608
  async function replaceCSSImports(cssText, basePath, cache = new Map()) {
@@ -6596,20 +6658,6 @@
6596
6658
  cssText = cssText.trim();
6597
6659
  return cssText;
6598
6660
  }
6599
- function createCORSCopy(cssText, inject) {
6600
- if (!cssText) {
6601
- return null;
6602
- }
6603
- const cors = document.createElement("style");
6604
- cors.classList.add("darkreader");
6605
- cors.classList.add("darkreader--cors");
6606
- cors.media = "screen";
6607
- cors.textContent = cssText;
6608
- inject(cors);
6609
- cors.sheet.disabled = true;
6610
- corsStyleSet.add(cors);
6611
- return cors;
6612
- }
6613
6661
 
6614
6662
  function injectProxy(
6615
6663
  enableStyleSheetsProxy,
package/darkreader.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Dark Reader v4.9.114
2
+ * Dark Reader v4.9.117
3
3
  * https://darkreader.org/
4
4
  */
5
5
 
@@ -1901,7 +1901,8 @@ function iterateCSSDeclarations(style, iterate) {
1901
1901
  }
1902
1902
  }
1903
1903
  if (
1904
- cssText.includes("background-color: ;") &&
1904
+ (cssText.includes("background-color: ;") ||
1905
+ cssText.includes("background-image: ;")) &&
1905
1906
  !style.getPropertyValue("background")
1906
1907
  ) {
1907
1908
  handleEmptyShorthand("background", style, iterate);
@@ -1934,6 +1935,7 @@ function handleEmptyShorthand(shorthand, style, iterate) {
1934
1935
  }
1935
1936
  } else if (shorthand === "background") {
1936
1937
  iterate("background-color", "#ffffff");
1938
+ iterate("background-image", "none");
1937
1939
  }
1938
1940
  }
1939
1941
  }
@@ -2571,6 +2573,10 @@ async function bgFetch(request) {
2571
2573
  if (window.DarkReader?.Plugins?.fetch) {
2572
2574
  return window.DarkReader.Plugins.fetch(request);
2573
2575
  }
2576
+ const parsedURL = new URL(request.url);
2577
+ if (parsedURL.origin !== request.origin && shouldIgnoreCors(parsedURL)) {
2578
+ throw new Error("Cross-origin limit reached");
2579
+ }
2574
2580
  return new Promise((resolve, reject) => {
2575
2581
  const id = generateUID();
2576
2582
  resolvers$1.set(id, resolve);
@@ -2596,6 +2602,25 @@ chrome.runtime.onMessage.addListener(({type, data, error, id}) => {
2596
2602
  }
2597
2603
  }
2598
2604
  });
2605
+ const ipV4RegExp = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
2606
+ const MAX_CORS_DOMAINS = 16;
2607
+ const corsDomains = new Set();
2608
+ function shouldIgnoreCors(url) {
2609
+ const host = url.hostname;
2610
+ if (!corsDomains.has(host)) {
2611
+ corsDomains.add(host);
2612
+ }
2613
+ if (
2614
+ corsDomains.size >= MAX_CORS_DOMAINS ||
2615
+ host === "localhost" ||
2616
+ host.startsWith("[") ||
2617
+ host.endsWith(".local") ||
2618
+ host.match(ipV4RegExp)
2619
+ ) {
2620
+ return true;
2621
+ }
2622
+ return false;
2623
+ }
2599
2624
 
2600
2625
  const imageManager = new AsyncQueue();
2601
2626
  async function getImageDetails(url) {
@@ -2634,7 +2659,11 @@ async function getDataURL(url) {
2634
2659
  if (parsedURL.origin === location.origin) {
2635
2660
  return await loadAsDataURL(url);
2636
2661
  }
2637
- return await bgFetch({url, responseType: "data-url"});
2662
+ return await bgFetch({
2663
+ url,
2664
+ responseType: "data-url",
2665
+ origin: location.origin
2666
+ });
2638
2667
  }
2639
2668
  async function tryCreateImageBitmap(blob) {
2640
2669
  try {
@@ -3361,7 +3390,12 @@ function getBgImageModifier(value, rule, ignoreImageSelectors, isCancelled) {
3361
3390
  awaitingForImageLoading.set(url, []);
3362
3391
  imageDetails = await getImageDetails(url);
3363
3392
  imageDetailsCache.set(url, imageDetails);
3364
- writeImageDetailsCache(url, imageDetails);
3393
+ if (!url.startsWith("data:")) {
3394
+ const parsedURL = new URL(url);
3395
+ if (parsedURL.origin === location.origin) {
3396
+ writeImageDetailsCache(url, imageDetails);
3397
+ }
3398
+ }
3365
3399
  awaitingForImageLoading
3366
3400
  .get(url)
3367
3401
  .forEach((resolve) => resolve(imageDetails));
@@ -3595,6 +3629,9 @@ const VAR_TYPE_BG_COLOR = 1 << 0;
3595
3629
  const VAR_TYPE_TEXT_COLOR = 1 << 1;
3596
3630
  const VAR_TYPE_BORDER_COLOR = 1 << 2;
3597
3631
  const VAR_TYPE_BG_IMG = 1 << 3;
3632
+ const shouldSetDefaultColor =
3633
+ !location.hostname.startsWith("www.ebay.") &&
3634
+ !location.hostname.includes(".ebay.");
3598
3635
  class VariablesStore {
3599
3636
  constructor() {
3600
3637
  this.varTypes = new Map();
@@ -3844,10 +3881,12 @@ class VariablesStore {
3844
3881
  (isSimpleConstructedColor && property === "background")
3845
3882
  ) {
3846
3883
  return (theme) => {
3847
- const defaultFallback = tryModifyBgColor(
3848
- isConstructedColor ? "255, 255, 255" : "#ffffff",
3849
- theme
3850
- );
3884
+ const defaultFallback = shouldSetDefaultColor
3885
+ ? tryModifyBgColor(
3886
+ isConstructedColor ? "255, 255, 255" : "#ffffff",
3887
+ theme
3888
+ )
3889
+ : "transparent";
3851
3890
  return replaceCSSVariablesNames(
3852
3891
  sourceValue,
3853
3892
  (v) => wrapBgColorVariableName(v),
@@ -5035,6 +5074,8 @@ function createAdoptedStyleSheetFallback() {
5035
5074
  }
5036
5075
 
5037
5076
  const hostsBreakingOnStylePosition = [
5077
+ "gogoprivate.com",
5078
+ "gprivate.com",
5038
5079
  "www.berlingske.dk",
5039
5080
  "www.bloomberg.com",
5040
5081
  "www.diffusioneshop.com",
@@ -5399,12 +5440,49 @@ function shouldIgnoreInlineStyle(element, selectors) {
5399
5440
  }
5400
5441
  return false;
5401
5442
  }
5443
+ const LOOP_DETECTION_THRESHOLD = 1000;
5444
+ const MAX_LOOP_CYCLES = 10;
5445
+ const elementsLastChanges = new WeakMap();
5446
+ const elementsLoopCycles = new WeakMap();
5447
+ const SMALL_SVG_THRESHOLD = 32;
5448
+ const svgNodesRoots = new WeakMap();
5449
+ const svgRootSizeTestResults = new WeakMap();
5450
+ function getSVGElementRoot(svgElement) {
5451
+ if (!svgElement) {
5452
+ return null;
5453
+ }
5454
+ if (svgNodesRoots.has(svgElement)) {
5455
+ return svgNodesRoots.get(svgElement);
5456
+ }
5457
+ if (svgElement instanceof SVGSVGElement) {
5458
+ return svgElement;
5459
+ }
5460
+ const parent = svgElement.parentNode;
5461
+ const root = getSVGElementRoot(parent);
5462
+ svgNodesRoots.set(svgElement, root);
5463
+ return root;
5464
+ }
5402
5465
  function overrideInlineStyle(
5403
5466
  element,
5404
5467
  theme,
5405
5468
  ignoreInlineSelectors,
5406
5469
  ignoreImageSelectors
5407
5470
  ) {
5471
+ if (elementsLastChanges.has(element)) {
5472
+ if (
5473
+ Date.now() - elementsLastChanges.get(element) <
5474
+ LOOP_DETECTION_THRESHOLD
5475
+ ) {
5476
+ const cycles = elementsLoopCycles.get(element) ?? 0;
5477
+ elementsLoopCycles.set(element, cycles + 1);
5478
+ }
5479
+ if ((elementsLoopCycles.get(element) ?? 0) >= MAX_LOOP_CYCLES) {
5480
+ return;
5481
+ }
5482
+ }
5483
+ if (element.parentElement?.dataset.nodeViewContent) {
5484
+ return;
5485
+ }
5408
5486
  const cacheKey = getInlineStyleCacheKey(element, theme);
5409
5487
  if (cacheKey === inlineStyleCache.get(element)) {
5410
5488
  return;
@@ -5555,14 +5633,34 @@ function overrideInlineStyle(
5555
5633
  }
5556
5634
  if (isSVGElement) {
5557
5635
  if (element.hasAttribute("fill")) {
5558
- const SMALL_SVG_LIMIT = 32;
5559
5636
  const value = element.getAttribute("fill");
5560
5637
  if (value !== "none") {
5561
5638
  if (!(element instanceof SVGTextElement)) {
5562
5639
  const handleSVGElement = () => {
5563
- const {width, height} = element.getBoundingClientRect();
5564
- const isBg =
5565
- width > SMALL_SVG_LIMIT || height > SMALL_SVG_LIMIT;
5640
+ let isSVGSmall = false;
5641
+ const root = getSVGElementRoot(element);
5642
+ if (!root) {
5643
+ return;
5644
+ }
5645
+ if (svgRootSizeTestResults.has(root)) {
5646
+ isSVGSmall = svgRootSizeTestResults.get(root);
5647
+ } else {
5648
+ const svgBounds = root.getBoundingClientRect();
5649
+ isSVGSmall =
5650
+ svgBounds.width * svgBounds.height <=
5651
+ Math.pow(SMALL_SVG_THRESHOLD, 2);
5652
+ svgRootSizeTestResults.set(root, isSVGSmall);
5653
+ }
5654
+ let isBg;
5655
+ if (isSVGSmall) {
5656
+ isBg = false;
5657
+ } else {
5658
+ const {width, height} =
5659
+ element.getBoundingClientRect();
5660
+ isBg =
5661
+ width > SMALL_SVG_THRESHOLD ||
5662
+ height > SMALL_SVG_THRESHOLD;
5663
+ }
5566
5664
  setCustomProp(
5567
5665
  "fill",
5568
5666
  isBg ? "background-color" : "color",
@@ -5647,6 +5745,7 @@ function overrideInlineStyle(
5647
5745
  element.removeAttribute(overrides[cssProp].dataAttr);
5648
5746
  });
5649
5747
  inlineStyleCache.set(element, getInlineStyleCacheKey(element, theme));
5748
+ elementsLastChanges.set(element, Date.now());
5650
5749
  }
5651
5750
 
5652
5751
  const metaThemeColorName = "theme-color";
@@ -5856,7 +5955,8 @@ function getManageableStyles(node, results = [], deep = true) {
5856
5955
  return results;
5857
5956
  }
5858
5957
  const syncStyleSet = new WeakSet();
5859
- const corsStyleSet = new WeakSet();
5958
+ const corsCopies = new WeakMap();
5959
+ const corsCopiesTextLengths = new WeakMap();
5860
5960
  let loadingLinkCounter = 0;
5861
5961
  const rejectorsForLoadingLinks = new Map();
5862
5962
  function cleanLoadingLinks() {
@@ -5864,7 +5964,6 @@ function cleanLoadingLinks() {
5864
5964
  }
5865
5965
  function manageStyle(element, {update, loadingStart, loadingEnd}) {
5866
5966
  const inMode = getStyleInjectionMode();
5867
- let corsCopy = null;
5868
5967
  let syncStyle = null;
5869
5968
  if (inMode === "next") {
5870
5969
  const prevStyles = [];
@@ -5875,16 +5974,11 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
5875
5974
  ) {
5876
5975
  prevStyles.push(next);
5877
5976
  }
5878
- corsCopy =
5879
- prevStyles.find(
5880
- (el) => el.matches(".darkreader--cors") && !corsStyleSet.has(el)
5881
- ) || null;
5882
5977
  syncStyle =
5883
5978
  prevStyles.find(
5884
5979
  (el) => el.matches(".darkreader--sync") && !syncStyleSet.has(el)
5885
5980
  ) || null;
5886
5981
  }
5887
- let corsCopyPositionWatcher = null;
5888
5982
  let syncStylePositionWatcher = null;
5889
5983
  let cancelAsyncOperations = false;
5890
5984
  let isOverrideEmpty = true;
@@ -5942,8 +6036,8 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
5942
6036
  return result;
5943
6037
  }
5944
6038
  function getRulesSync() {
5945
- if (corsCopy) {
5946
- return corsCopy.sheet.cssRules;
6039
+ if (corsCopies.has(element)) {
6040
+ return corsCopies.get(element).cssRules;
5947
6041
  }
5948
6042
  if (containsCSSImport()) {
5949
6043
  return null;
@@ -5964,26 +6058,10 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
5964
6058
  }
5965
6059
  function insertStyle() {
5966
6060
  if (inMode === "next") {
5967
- if (corsCopy) {
5968
- if (element.nextSibling !== corsCopy) {
5969
- element.parentNode.insertBefore(
5970
- corsCopy,
5971
- element.nextSibling
5972
- );
5973
- }
5974
- if (corsCopy.nextSibling !== syncStyle) {
5975
- element.parentNode.insertBefore(
5976
- syncStyle,
5977
- corsCopy.nextSibling
5978
- );
5979
- }
5980
- } else if (element.nextSibling !== syncStyle) {
6061
+ if (element.nextSibling !== syncStyle) {
5981
6062
  element.parentNode.insertBefore(syncStyle, element.nextSibling);
5982
6063
  }
5983
6064
  } else if (inMode === "away") {
5984
- if (corsCopy && !corsCopy.parentNode) {
5985
- injectStyleAway(corsCopy);
5986
- }
5987
6065
  injectStyleAway(syncStyle);
5988
6066
  }
5989
6067
  }
@@ -6059,8 +6137,8 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
6059
6137
  return null;
6060
6138
  }
6061
6139
  await createOrUpdateCORSCopy(cssText, cssBasePath);
6062
- if (corsCopy) {
6063
- return corsCopy.sheet.cssRules;
6140
+ if (corsCopies.has(element)) {
6141
+ return corsCopies.get(element).cssRules;
6064
6142
  }
6065
6143
  return null;
6066
6144
  }
@@ -6071,43 +6149,22 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
6071
6149
  cssText,
6072
6150
  cssBasePath
6073
6151
  );
6074
- if (corsCopy) {
6152
+ if (corsCopies.has(element)) {
6075
6153
  if (
6076
- (corsCopy.textContent?.length ?? 0) < fullCSSText.length
6154
+ (corsCopiesTextLengths.get(element) ?? 0) <
6155
+ fullCSSText.length
6077
6156
  ) {
6078
- corsCopy.textContent = fullCSSText;
6157
+ corsCopies.get(element).replaceSync(fullCSSText);
6158
+ corsCopiesTextLengths.set(element, fullCSSText.length);
6079
6159
  }
6080
6160
  } else {
6081
- corsCopy = createCORSCopy(
6082
- fullCSSText,
6083
- inMode === "next"
6084
- ? (cc) =>
6085
- element.parentNode.insertBefore(
6086
- cc,
6087
- element.nextSibling
6088
- )
6089
- : injectStyleAway
6090
- );
6091
- if (corsCopy) {
6092
- if (inMode === "next") {
6093
- element.parentNode.insertBefore(
6094
- corsCopy,
6095
- element.nextSibling
6096
- );
6097
- } else if (inMode === "away") {
6098
- injectStyleAway(corsCopy);
6099
- }
6100
- }
6161
+ const corsCopy = new CSSStyleSheet();
6162
+ corsCopy.replaceSync(fullCSSText);
6163
+ corsCopies.set(element, corsCopy);
6101
6164
  }
6102
6165
  } catch (err) {
6103
6166
  logWarn(err);
6104
6167
  }
6105
- if (corsCopy && inMode === "next") {
6106
- corsCopyPositionWatcher = watchForNodePosition(
6107
- corsCopy,
6108
- "prev-sibling"
6109
- );
6110
- }
6111
6168
  }
6112
6169
  }
6113
6170
  function details(options) {
@@ -6230,13 +6287,12 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
6230
6287
  function pause() {
6231
6288
  observer.disconnect();
6232
6289
  cancelAsyncOperations = true;
6233
- corsCopyPositionWatcher && corsCopyPositionWatcher.stop();
6234
6290
  syncStylePositionWatcher && syncStylePositionWatcher.stop();
6235
6291
  sheetChangeWatcher.stop();
6236
6292
  }
6237
6293
  function destroy() {
6238
6294
  pause();
6239
- removeNode(corsCopy);
6295
+ corsCopies.delete(element);
6240
6296
  removeNode(syncStyle);
6241
6297
  loadingEnd();
6242
6298
  if (rejectorsForLoadingLinks.has(loadingLinkId)) {
@@ -6264,7 +6320,6 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
6264
6320
  }
6265
6321
  logWarn("Restore style", syncStyle, element);
6266
6322
  insertStyle();
6267
- corsCopyPositionWatcher && corsCopyPositionWatcher.skip();
6268
6323
  syncStylePositionWatcher && syncStylePositionWatcher.skip();
6269
6324
  if (!isOverrideEmpty) {
6270
6325
  forceRenderStyle = true;
@@ -6335,7 +6390,9 @@ async function loadText(url) {
6335
6390
  origin: location.origin
6336
6391
  });
6337
6392
  }
6338
- writeCSSFetchCache(url, text);
6393
+ if (parsedURL.origin === location.origin) {
6394
+ writeCSSFetchCache(url, text);
6395
+ }
6339
6396
  return text;
6340
6397
  }
6341
6398
  async function replaceCSSImports(cssText, basePath, cache = new Map()) {
@@ -6391,20 +6448,6 @@ async function replaceCSSImports(cssText, basePath, cache = new Map()) {
6391
6448
  cssText = cssText.trim();
6392
6449
  return cssText;
6393
6450
  }
6394
- function createCORSCopy(cssText, inject) {
6395
- if (!cssText) {
6396
- return null;
6397
- }
6398
- const cors = document.createElement("style");
6399
- cors.classList.add("darkreader");
6400
- cors.classList.add("darkreader--cors");
6401
- cors.media = "screen";
6402
- cors.textContent = cssText;
6403
- inject(cors);
6404
- cors.sheet.disabled = true;
6405
- corsStyleSet.add(cors);
6406
- return cors;
6407
- }
6408
6451
 
6409
6452
  function injectProxy(enableStyleSheetsProxy, enableCustomElementRegistryProxy) {
6410
6453
  document.dispatchEvent(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "darkreader",
3
- "version": "4.9.114",
3
+ "version": "4.9.117",
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,29 +66,29 @@
66
66
  "malevic": "0.20.2"
67
67
  },
68
68
  "devDependencies": {
69
- "@eslint/compat": "1.4.0",
69
+ "@eslint/compat": "2.0.0",
70
70
  "@eslint/eslintrc": "3.3.1",
71
- "@eslint/js": "9.38.0",
71
+ "@eslint/js": "9.39.1",
72
72
  "@rollup/plugin-node-resolve": "16.0.3",
73
- "@rollup/plugin-replace": "6.0.2",
73
+ "@rollup/plugin-replace": "6.0.3",
74
74
  "@rollup/plugin-typescript": "12.3.0",
75
- "@stylistic/eslint-plugin": "5.5.0",
76
- "@types/chrome": "0.1.26",
75
+ "@stylistic/eslint-plugin": "5.6.1",
76
+ "@types/chrome": "0.1.31",
77
77
  "@types/eslint": "9.6.1",
78
- "@types/jasmine": "5.1.12",
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.9.1",
82
+ "@types/node": "24.10.1",
83
83
  "@types/ws": "8.18.1",
84
84
  "chokidar": "4.0.3",
85
85
  "eslint-plugin-compat": "6.0.2",
86
86
  "eslint-plugin-import": "2.32.0",
87
- "globals": "16.4.0",
88
- "globby": "15.0.0",
89
- "jasmine-core": "5.12.0",
87
+ "globals": "16.5.0",
88
+ "globby": "16.0.0",
89
+ "jasmine-core": "5.12.1",
90
90
  "jest": "30.2.0",
91
- "jest-extended": "6.0.0",
91
+ "jest-extended": "7.0.0",
92
92
  "karma": "6.4.4",
93
93
  "karma-chrome-launcher": "3.2.0",
94
94
  "karma-coverage": "2.2.1",
@@ -99,18 +99,18 @@
99
99
  "karma-spec-reporter": "0.0.36",
100
100
  "less": "4.4.2",
101
101
  "prettier": "3.6.2",
102
- "puppeteer-core": "24.26.1",
103
- "rollup": "4.52.5",
102
+ "puppeteer-core": "24.30.0",
103
+ "rollup": "4.53.3",
104
104
  "rollup-plugin-istanbul": "5.0.0",
105
105
  "ts-jest": "29.4.5",
106
106
  "tslib": "2.8.1",
107
107
  "typescript": "5.9.3",
108
- "typescript-eslint": "8.46.2",
108
+ "typescript-eslint": "8.47.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.52.5",
114
- "@rollup/rollup-win32-x64-msvc": "4.52.5"
113
+ "@rollup/rollup-linux-x64-gnu": "4.53.3",
114
+ "@rollup/rollup-win32-x64-msvc": "4.53.3"
115
115
  }
116
116
  }