darkreader 4.9.114 → 4.9.118

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 +189 -93
  2. package/darkreader.mjs +181 -90
  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.118
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
  }
@@ -1945,7 +1948,8 @@
1945
1948
  }
1946
1949
  }
1947
1950
  if (
1948
- cssText.includes("background-color: ;") &&
1951
+ (cssText.includes("background-color: ;") ||
1952
+ cssText.includes("background-image: ;")) &&
1949
1953
  !style.getPropertyValue("background")
1950
1954
  ) {
1951
1955
  handleEmptyShorthand("background", style, iterate);
@@ -1978,6 +1982,7 @@
1978
1982
  }
1979
1983
  } else if (shorthand === "background") {
1980
1984
  iterate("background-color", "#ffffff");
1985
+ iterate("background-image", "none");
1981
1986
  }
1982
1987
  }
1983
1988
  }
@@ -2654,6 +2659,13 @@
2654
2659
  if (window.DarkReader?.Plugins?.fetch) {
2655
2660
  return window.DarkReader.Plugins.fetch(request);
2656
2661
  }
2662
+ const parsedURL = new URL(request.url);
2663
+ if (
2664
+ parsedURL.origin !== request.origin &&
2665
+ shouldIgnoreCors(parsedURL)
2666
+ ) {
2667
+ throw new Error("Cross-origin limit reached");
2668
+ }
2657
2669
  return new Promise((resolve, reject) => {
2658
2670
  const id = generateUID();
2659
2671
  resolvers$1.set(id, resolve);
@@ -2681,6 +2693,67 @@
2681
2693
  }
2682
2694
  }
2683
2695
  });
2696
+ const ipV4RegExp = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
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
+ ];
2735
+ function shouldIgnoreCors(url) {
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;
2742
+ }
2743
+ if (
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)
2751
+ ) {
2752
+ return true;
2753
+ }
2754
+ checkedHosts.add(host);
2755
+ return false;
2756
+ }
2684
2757
 
2685
2758
  const imageManager = new AsyncQueue();
2686
2759
  async function getImageDetails(url) {
@@ -2720,7 +2793,11 @@
2720
2793
  if (parsedURL.origin === location.origin) {
2721
2794
  return await loadAsDataURL(url);
2722
2795
  }
2723
- return await bgFetch({url, responseType: "data-url"});
2796
+ return await bgFetch({
2797
+ url,
2798
+ responseType: "data-url",
2799
+ origin: location.origin
2800
+ });
2724
2801
  }
2725
2802
  async function tryCreateImageBitmap(blob) {
2726
2803
  try {
@@ -3475,7 +3552,15 @@
3475
3552
  awaitingForImageLoading.set(url, []);
3476
3553
  imageDetails = await getImageDetails(url);
3477
3554
  imageDetailsCache.set(url, imageDetails);
3478
- writeImageDetailsCache(url, imageDetails);
3555
+ if (!url.startsWith("data:")) {
3556
+ const parsedURL = new URL(url);
3557
+ if (parsedURL.origin === location.origin) {
3558
+ writeImageDetailsCache(
3559
+ url,
3560
+ imageDetails
3561
+ );
3562
+ }
3563
+ }
3479
3564
  awaitingForImageLoading
3480
3565
  .get(url)
3481
3566
  .forEach((resolve) =>
@@ -3719,6 +3804,9 @@
3719
3804
  const VAR_TYPE_TEXT_COLOR = 1 << 1;
3720
3805
  const VAR_TYPE_BORDER_COLOR = 1 << 2;
3721
3806
  const VAR_TYPE_BG_IMG = 1 << 3;
3807
+ const shouldSetDefaultColor =
3808
+ !location.hostname.startsWith("www.ebay.") &&
3809
+ !location.hostname.includes(".ebay.");
3722
3810
  class VariablesStore {
3723
3811
  constructor() {
3724
3812
  this.varTypes = new Map();
@@ -3976,10 +4064,12 @@
3976
4064
  (isSimpleConstructedColor && property === "background")
3977
4065
  ) {
3978
4066
  return (theme) => {
3979
- const defaultFallback = tryModifyBgColor(
3980
- isConstructedColor ? "255, 255, 255" : "#ffffff",
3981
- theme
3982
- );
4067
+ const defaultFallback = shouldSetDefaultColor
4068
+ ? tryModifyBgColor(
4069
+ isConstructedColor ? "255, 255, 255" : "#ffffff",
4070
+ theme
4071
+ )
4072
+ : "transparent";
3983
4073
  return replaceCSSVariablesNames(
3984
4074
  sourceValue,
3985
4075
  (v) => wrapBgColorVariableName(v),
@@ -4485,13 +4575,16 @@
4485
4575
  function isTextColorProperty(property) {
4486
4576
  return textColorProps.includes(property);
4487
4577
  }
4488
- const rawRGBSpaceRegex = /^(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})$/;
4578
+ const rawRGBSpaceRegex =
4579
+ /^(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})\s*(\/\s*\d+\.?\d*)?$/;
4489
4580
  const rawRGBCommaRegex = /^(\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})$/;
4490
4581
  function parseRawColorValue(input) {
4491
4582
  const match =
4492
4583
  input.match(rawRGBSpaceRegex) ?? input.match(rawRGBCommaRegex);
4493
4584
  if (match) {
4494
- const color = `rgb(${match[1]}, ${match[2]}, ${match[3]})`;
4585
+ const color = match[4]
4586
+ ? `rgb(${match[1]} ${match[2]} ${match[3]} / ${match[4]})`
4587
+ : `rgb(${match[1]}, ${match[2]}, ${match[3]})`;
4495
4588
  return {isRaw: true, color};
4496
4589
  }
4497
4590
  return {isRaw: false, color: input};
@@ -5188,6 +5281,8 @@
5188
5281
  }
5189
5282
 
5190
5283
  const hostsBreakingOnStylePosition = [
5284
+ "gogoprivate.com",
5285
+ "gprivate.com",
5191
5286
  "www.berlingske.dk",
5192
5287
  "www.bloomberg.com",
5193
5288
  "www.diffusioneshop.com",
@@ -5556,12 +5651,49 @@
5556
5651
  }
5557
5652
  return false;
5558
5653
  }
5654
+ const LOOP_DETECTION_THRESHOLD = 1000;
5655
+ const MAX_LOOP_CYCLES = 10;
5656
+ const elementsLastChanges = new WeakMap();
5657
+ const elementsLoopCycles = new WeakMap();
5658
+ const SMALL_SVG_THRESHOLD = 32;
5659
+ const svgNodesRoots = new WeakMap();
5660
+ const svgRootSizeTestResults = new WeakMap();
5661
+ function getSVGElementRoot(svgElement) {
5662
+ if (!svgElement) {
5663
+ return null;
5664
+ }
5665
+ if (svgNodesRoots.has(svgElement)) {
5666
+ return svgNodesRoots.get(svgElement);
5667
+ }
5668
+ if (svgElement instanceof SVGSVGElement) {
5669
+ return svgElement;
5670
+ }
5671
+ const parent = svgElement.parentNode;
5672
+ const root = getSVGElementRoot(parent);
5673
+ svgNodesRoots.set(svgElement, root);
5674
+ return root;
5675
+ }
5559
5676
  function overrideInlineStyle(
5560
5677
  element,
5561
5678
  theme,
5562
5679
  ignoreInlineSelectors,
5563
5680
  ignoreImageSelectors
5564
5681
  ) {
5682
+ if (elementsLastChanges.has(element)) {
5683
+ if (
5684
+ Date.now() - elementsLastChanges.get(element) <
5685
+ LOOP_DETECTION_THRESHOLD
5686
+ ) {
5687
+ const cycles = elementsLoopCycles.get(element) ?? 0;
5688
+ elementsLoopCycles.set(element, cycles + 1);
5689
+ }
5690
+ if ((elementsLoopCycles.get(element) ?? 0) >= MAX_LOOP_CYCLES) {
5691
+ return;
5692
+ }
5693
+ }
5694
+ if (element.parentElement?.dataset.nodeViewContent) {
5695
+ return;
5696
+ }
5565
5697
  const cacheKey = getInlineStyleCacheKey(element, theme);
5566
5698
  if (cacheKey === inlineStyleCache.get(element)) {
5567
5699
  return;
@@ -5725,16 +5857,34 @@
5725
5857
  }
5726
5858
  if (isSVGElement) {
5727
5859
  if (element.hasAttribute("fill")) {
5728
- const SMALL_SVG_LIMIT = 32;
5729
5860
  const value = element.getAttribute("fill");
5730
5861
  if (value !== "none") {
5731
5862
  if (!(element instanceof SVGTextElement)) {
5732
5863
  const handleSVGElement = () => {
5733
- const {width, height} =
5734
- element.getBoundingClientRect();
5735
- const isBg =
5736
- width > SMALL_SVG_LIMIT ||
5737
- height > SMALL_SVG_LIMIT;
5864
+ let isSVGSmall = false;
5865
+ const root = getSVGElementRoot(element);
5866
+ if (!root) {
5867
+ return;
5868
+ }
5869
+ if (svgRootSizeTestResults.has(root)) {
5870
+ isSVGSmall = svgRootSizeTestResults.get(root);
5871
+ } else {
5872
+ const svgBounds = root.getBoundingClientRect();
5873
+ isSVGSmall =
5874
+ svgBounds.width * svgBounds.height <=
5875
+ Math.pow(SMALL_SVG_THRESHOLD, 2);
5876
+ svgRootSizeTestResults.set(root, isSVGSmall);
5877
+ }
5878
+ let isBg;
5879
+ if (isSVGSmall) {
5880
+ isBg = false;
5881
+ } else {
5882
+ const {width, height} =
5883
+ element.getBoundingClientRect();
5884
+ isBg =
5885
+ width > SMALL_SVG_THRESHOLD ||
5886
+ height > SMALL_SVG_THRESHOLD;
5887
+ }
5738
5888
  setCustomProp(
5739
5889
  "fill",
5740
5890
  isBg ? "background-color" : "color",
@@ -5826,6 +5976,7 @@
5826
5976
  element.removeAttribute(overrides[cssProp].dataAttr);
5827
5977
  });
5828
5978
  inlineStyleCache.set(element, getInlineStyleCacheKey(element, theme));
5979
+ elementsLastChanges.set(element, Date.now());
5829
5980
  }
5830
5981
 
5831
5982
  const metaThemeColorName = "theme-color";
@@ -6048,7 +6199,8 @@
6048
6199
  return results;
6049
6200
  }
6050
6201
  const syncStyleSet = new WeakSet();
6051
- const corsStyleSet = new WeakSet();
6202
+ const corsCopies = new WeakMap();
6203
+ const corsCopiesTextLengths = new WeakMap();
6052
6204
  let loadingLinkCounter = 0;
6053
6205
  const rejectorsForLoadingLinks = new Map();
6054
6206
  function cleanLoadingLinks() {
@@ -6056,7 +6208,6 @@
6056
6208
  }
6057
6209
  function manageStyle(element, {update, loadingStart, loadingEnd}) {
6058
6210
  const inMode = getStyleInjectionMode();
6059
- let corsCopy = null;
6060
6211
  let syncStyle = null;
6061
6212
  if (inMode === "next") {
6062
6213
  const prevStyles = [];
@@ -6067,18 +6218,12 @@
6067
6218
  ) {
6068
6219
  prevStyles.push(next);
6069
6220
  }
6070
- corsCopy =
6071
- prevStyles.find(
6072
- (el) =>
6073
- el.matches(".darkreader--cors") && !corsStyleSet.has(el)
6074
- ) || null;
6075
6221
  syncStyle =
6076
6222
  prevStyles.find(
6077
6223
  (el) =>
6078
6224
  el.matches(".darkreader--sync") && !syncStyleSet.has(el)
6079
6225
  ) || null;
6080
6226
  }
6081
- let corsCopyPositionWatcher = null;
6082
6227
  let syncStylePositionWatcher = null;
6083
6228
  let cancelAsyncOperations = false;
6084
6229
  let isOverrideEmpty = true;
@@ -6140,8 +6285,8 @@
6140
6285
  return result;
6141
6286
  }
6142
6287
  function getRulesSync() {
6143
- if (corsCopy) {
6144
- return corsCopy.sheet.cssRules;
6288
+ if (corsCopies.has(element)) {
6289
+ return corsCopies.get(element).cssRules;
6145
6290
  }
6146
6291
  if (containsCSSImport()) {
6147
6292
  return null;
@@ -6163,29 +6308,13 @@
6163
6308
  }
6164
6309
  function insertStyle() {
6165
6310
  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) {
6311
+ if (element.nextSibling !== syncStyle) {
6180
6312
  element.parentNode.insertBefore(
6181
6313
  syncStyle,
6182
6314
  element.nextSibling
6183
6315
  );
6184
6316
  }
6185
6317
  } else if (inMode === "away") {
6186
- if (corsCopy && !corsCopy.parentNode) {
6187
- injectStyleAway(corsCopy);
6188
- }
6189
6318
  injectStyleAway(syncStyle);
6190
6319
  }
6191
6320
  }
@@ -6261,8 +6390,8 @@
6261
6390
  return null;
6262
6391
  }
6263
6392
  await createOrUpdateCORSCopy(cssText, cssBasePath);
6264
- if (corsCopy) {
6265
- return corsCopy.sheet.cssRules;
6393
+ if (corsCopies.has(element)) {
6394
+ return corsCopies.get(element).cssRules;
6266
6395
  }
6267
6396
  return null;
6268
6397
  }
@@ -6273,44 +6402,25 @@
6273
6402
  cssText,
6274
6403
  cssBasePath
6275
6404
  );
6276
- if (corsCopy) {
6405
+ if (corsCopies.has(element)) {
6277
6406
  if (
6278
- (corsCopy.textContent?.length ?? 0) <
6407
+ (corsCopiesTextLengths.get(element) ?? 0) <
6279
6408
  fullCSSText.length
6280
6409
  ) {
6281
- corsCopy.textContent = fullCSSText;
6410
+ corsCopies.get(element).replaceSync(fullCSSText);
6411
+ corsCopiesTextLengths.set(
6412
+ element,
6413
+ fullCSSText.length
6414
+ );
6282
6415
  }
6283
6416
  } 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
- }
6417
+ const corsCopy = new CSSStyleSheet();
6418
+ corsCopy.replaceSync(fullCSSText);
6419
+ corsCopies.set(element, corsCopy);
6304
6420
  }
6305
6421
  } catch (err) {
6306
6422
  logWarn(err);
6307
6423
  }
6308
- if (corsCopy && inMode === "next") {
6309
- corsCopyPositionWatcher = watchForNodePosition(
6310
- corsCopy,
6311
- "prev-sibling"
6312
- );
6313
- }
6314
6424
  }
6315
6425
  }
6316
6426
  function details(options) {
@@ -6433,13 +6543,12 @@
6433
6543
  function pause() {
6434
6544
  observer.disconnect();
6435
6545
  cancelAsyncOperations = true;
6436
- corsCopyPositionWatcher && corsCopyPositionWatcher.stop();
6437
6546
  syncStylePositionWatcher && syncStylePositionWatcher.stop();
6438
6547
  sheetChangeWatcher.stop();
6439
6548
  }
6440
6549
  function destroy() {
6441
6550
  pause();
6442
- removeNode(corsCopy);
6551
+ corsCopies.delete(element);
6443
6552
  removeNode(syncStyle);
6444
6553
  loadingEnd();
6445
6554
  if (rejectorsForLoadingLinks.has(loadingLinkId)) {
@@ -6467,7 +6576,6 @@
6467
6576
  }
6468
6577
  logWarn("Restore style", syncStyle, element);
6469
6578
  insertStyle();
6470
- corsCopyPositionWatcher && corsCopyPositionWatcher.skip();
6471
6579
  syncStylePositionWatcher && syncStylePositionWatcher.skip();
6472
6580
  if (!isOverrideEmpty) {
6473
6581
  forceRenderStyle = true;
@@ -6540,7 +6648,9 @@
6540
6648
  origin: location.origin
6541
6649
  });
6542
6650
  }
6543
- writeCSSFetchCache(url, text);
6651
+ if (parsedURL.origin === location.origin) {
6652
+ writeCSSFetchCache(url, text);
6653
+ }
6544
6654
  return text;
6545
6655
  }
6546
6656
  async function replaceCSSImports(cssText, basePath, cache = new Map()) {
@@ -6596,20 +6706,6 @@
6596
6706
  cssText = cssText.trim();
6597
6707
  return cssText;
6598
6708
  }
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
6709
 
6614
6710
  function injectProxy(
6615
6711
  enableStyleSheetsProxy,
package/darkreader.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Dark Reader v4.9.114
2
+ * Dark Reader v4.9.118
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
  }
@@ -1901,7 +1904,8 @@ function iterateCSSDeclarations(style, iterate) {
1901
1904
  }
1902
1905
  }
1903
1906
  if (
1904
- cssText.includes("background-color: ;") &&
1907
+ (cssText.includes("background-color: ;") ||
1908
+ cssText.includes("background-image: ;")) &&
1905
1909
  !style.getPropertyValue("background")
1906
1910
  ) {
1907
1911
  handleEmptyShorthand("background", style, iterate);
@@ -1934,6 +1938,7 @@ function handleEmptyShorthand(shorthand, style, iterate) {
1934
1938
  }
1935
1939
  } else if (shorthand === "background") {
1936
1940
  iterate("background-color", "#ffffff");
1941
+ iterate("background-image", "none");
1937
1942
  }
1938
1943
  }
1939
1944
  }
@@ -2571,6 +2576,10 @@ async function bgFetch(request) {
2571
2576
  if (window.DarkReader?.Plugins?.fetch) {
2572
2577
  return window.DarkReader.Plugins.fetch(request);
2573
2578
  }
2579
+ const parsedURL = new URL(request.url);
2580
+ if (parsedURL.origin !== request.origin && shouldIgnoreCors(parsedURL)) {
2581
+ throw new Error("Cross-origin limit reached");
2582
+ }
2574
2583
  return new Promise((resolve, reject) => {
2575
2584
  const id = generateUID();
2576
2585
  resolvers$1.set(id, resolve);
@@ -2596,6 +2605,67 @@ chrome.runtime.onMessage.addListener(({type, data, error, id}) => {
2596
2605
  }
2597
2606
  }
2598
2607
  });
2608
+ const ipV4RegExp = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
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
+ ];
2647
+ function shouldIgnoreCors(url) {
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;
2654
+ }
2655
+ if (
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)
2663
+ ) {
2664
+ return true;
2665
+ }
2666
+ checkedHosts.add(host);
2667
+ return false;
2668
+ }
2599
2669
 
2600
2670
  const imageManager = new AsyncQueue();
2601
2671
  async function getImageDetails(url) {
@@ -2634,7 +2704,11 @@ async function getDataURL(url) {
2634
2704
  if (parsedURL.origin === location.origin) {
2635
2705
  return await loadAsDataURL(url);
2636
2706
  }
2637
- return await bgFetch({url, responseType: "data-url"});
2707
+ return await bgFetch({
2708
+ url,
2709
+ responseType: "data-url",
2710
+ origin: location.origin
2711
+ });
2638
2712
  }
2639
2713
  async function tryCreateImageBitmap(blob) {
2640
2714
  try {
@@ -3361,7 +3435,12 @@ function getBgImageModifier(value, rule, ignoreImageSelectors, isCancelled) {
3361
3435
  awaitingForImageLoading.set(url, []);
3362
3436
  imageDetails = await getImageDetails(url);
3363
3437
  imageDetailsCache.set(url, imageDetails);
3364
- writeImageDetailsCache(url, imageDetails);
3438
+ if (!url.startsWith("data:")) {
3439
+ const parsedURL = new URL(url);
3440
+ if (parsedURL.origin === location.origin) {
3441
+ writeImageDetailsCache(url, imageDetails);
3442
+ }
3443
+ }
3365
3444
  awaitingForImageLoading
3366
3445
  .get(url)
3367
3446
  .forEach((resolve) => resolve(imageDetails));
@@ -3595,6 +3674,9 @@ const VAR_TYPE_BG_COLOR = 1 << 0;
3595
3674
  const VAR_TYPE_TEXT_COLOR = 1 << 1;
3596
3675
  const VAR_TYPE_BORDER_COLOR = 1 << 2;
3597
3676
  const VAR_TYPE_BG_IMG = 1 << 3;
3677
+ const shouldSetDefaultColor =
3678
+ !location.hostname.startsWith("www.ebay.") &&
3679
+ !location.hostname.includes(".ebay.");
3598
3680
  class VariablesStore {
3599
3681
  constructor() {
3600
3682
  this.varTypes = new Map();
@@ -3844,10 +3926,12 @@ class VariablesStore {
3844
3926
  (isSimpleConstructedColor && property === "background")
3845
3927
  ) {
3846
3928
  return (theme) => {
3847
- const defaultFallback = tryModifyBgColor(
3848
- isConstructedColor ? "255, 255, 255" : "#ffffff",
3849
- theme
3850
- );
3929
+ const defaultFallback = shouldSetDefaultColor
3930
+ ? tryModifyBgColor(
3931
+ isConstructedColor ? "255, 255, 255" : "#ffffff",
3932
+ theme
3933
+ )
3934
+ : "transparent";
3851
3935
  return replaceCSSVariablesNames(
3852
3936
  sourceValue,
3853
3937
  (v) => wrapBgColorVariableName(v),
@@ -4342,13 +4426,16 @@ const textColorProps = [
4342
4426
  function isTextColorProperty(property) {
4343
4427
  return textColorProps.includes(property);
4344
4428
  }
4345
- const rawRGBSpaceRegex = /^(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})$/;
4429
+ const rawRGBSpaceRegex =
4430
+ /^(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})\s*(\/\s*\d+\.?\d*)?$/;
4346
4431
  const rawRGBCommaRegex = /^(\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})$/;
4347
4432
  function parseRawColorValue(input) {
4348
4433
  const match =
4349
4434
  input.match(rawRGBSpaceRegex) ?? input.match(rawRGBCommaRegex);
4350
4435
  if (match) {
4351
- const color = `rgb(${match[1]}, ${match[2]}, ${match[3]})`;
4436
+ const color = match[4]
4437
+ ? `rgb(${match[1]} ${match[2]} ${match[3]} / ${match[4]})`
4438
+ : `rgb(${match[1]}, ${match[2]}, ${match[3]})`;
4352
4439
  return {isRaw: true, color};
4353
4440
  }
4354
4441
  return {isRaw: false, color: input};
@@ -5035,6 +5122,8 @@ function createAdoptedStyleSheetFallback() {
5035
5122
  }
5036
5123
 
5037
5124
  const hostsBreakingOnStylePosition = [
5125
+ "gogoprivate.com",
5126
+ "gprivate.com",
5038
5127
  "www.berlingske.dk",
5039
5128
  "www.bloomberg.com",
5040
5129
  "www.diffusioneshop.com",
@@ -5399,12 +5488,49 @@ function shouldIgnoreInlineStyle(element, selectors) {
5399
5488
  }
5400
5489
  return false;
5401
5490
  }
5491
+ const LOOP_DETECTION_THRESHOLD = 1000;
5492
+ const MAX_LOOP_CYCLES = 10;
5493
+ const elementsLastChanges = new WeakMap();
5494
+ const elementsLoopCycles = new WeakMap();
5495
+ const SMALL_SVG_THRESHOLD = 32;
5496
+ const svgNodesRoots = new WeakMap();
5497
+ const svgRootSizeTestResults = new WeakMap();
5498
+ function getSVGElementRoot(svgElement) {
5499
+ if (!svgElement) {
5500
+ return null;
5501
+ }
5502
+ if (svgNodesRoots.has(svgElement)) {
5503
+ return svgNodesRoots.get(svgElement);
5504
+ }
5505
+ if (svgElement instanceof SVGSVGElement) {
5506
+ return svgElement;
5507
+ }
5508
+ const parent = svgElement.parentNode;
5509
+ const root = getSVGElementRoot(parent);
5510
+ svgNodesRoots.set(svgElement, root);
5511
+ return root;
5512
+ }
5402
5513
  function overrideInlineStyle(
5403
5514
  element,
5404
5515
  theme,
5405
5516
  ignoreInlineSelectors,
5406
5517
  ignoreImageSelectors
5407
5518
  ) {
5519
+ if (elementsLastChanges.has(element)) {
5520
+ if (
5521
+ Date.now() - elementsLastChanges.get(element) <
5522
+ LOOP_DETECTION_THRESHOLD
5523
+ ) {
5524
+ const cycles = elementsLoopCycles.get(element) ?? 0;
5525
+ elementsLoopCycles.set(element, cycles + 1);
5526
+ }
5527
+ if ((elementsLoopCycles.get(element) ?? 0) >= MAX_LOOP_CYCLES) {
5528
+ return;
5529
+ }
5530
+ }
5531
+ if (element.parentElement?.dataset.nodeViewContent) {
5532
+ return;
5533
+ }
5408
5534
  const cacheKey = getInlineStyleCacheKey(element, theme);
5409
5535
  if (cacheKey === inlineStyleCache.get(element)) {
5410
5536
  return;
@@ -5555,14 +5681,34 @@ function overrideInlineStyle(
5555
5681
  }
5556
5682
  if (isSVGElement) {
5557
5683
  if (element.hasAttribute("fill")) {
5558
- const SMALL_SVG_LIMIT = 32;
5559
5684
  const value = element.getAttribute("fill");
5560
5685
  if (value !== "none") {
5561
5686
  if (!(element instanceof SVGTextElement)) {
5562
5687
  const handleSVGElement = () => {
5563
- const {width, height} = element.getBoundingClientRect();
5564
- const isBg =
5565
- width > SMALL_SVG_LIMIT || height > SMALL_SVG_LIMIT;
5688
+ let isSVGSmall = false;
5689
+ const root = getSVGElementRoot(element);
5690
+ if (!root) {
5691
+ return;
5692
+ }
5693
+ if (svgRootSizeTestResults.has(root)) {
5694
+ isSVGSmall = svgRootSizeTestResults.get(root);
5695
+ } else {
5696
+ const svgBounds = root.getBoundingClientRect();
5697
+ isSVGSmall =
5698
+ svgBounds.width * svgBounds.height <=
5699
+ Math.pow(SMALL_SVG_THRESHOLD, 2);
5700
+ svgRootSizeTestResults.set(root, isSVGSmall);
5701
+ }
5702
+ let isBg;
5703
+ if (isSVGSmall) {
5704
+ isBg = false;
5705
+ } else {
5706
+ const {width, height} =
5707
+ element.getBoundingClientRect();
5708
+ isBg =
5709
+ width > SMALL_SVG_THRESHOLD ||
5710
+ height > SMALL_SVG_THRESHOLD;
5711
+ }
5566
5712
  setCustomProp(
5567
5713
  "fill",
5568
5714
  isBg ? "background-color" : "color",
@@ -5647,6 +5793,7 @@ function overrideInlineStyle(
5647
5793
  element.removeAttribute(overrides[cssProp].dataAttr);
5648
5794
  });
5649
5795
  inlineStyleCache.set(element, getInlineStyleCacheKey(element, theme));
5796
+ elementsLastChanges.set(element, Date.now());
5650
5797
  }
5651
5798
 
5652
5799
  const metaThemeColorName = "theme-color";
@@ -5856,7 +6003,8 @@ function getManageableStyles(node, results = [], deep = true) {
5856
6003
  return results;
5857
6004
  }
5858
6005
  const syncStyleSet = new WeakSet();
5859
- const corsStyleSet = new WeakSet();
6006
+ const corsCopies = new WeakMap();
6007
+ const corsCopiesTextLengths = new WeakMap();
5860
6008
  let loadingLinkCounter = 0;
5861
6009
  const rejectorsForLoadingLinks = new Map();
5862
6010
  function cleanLoadingLinks() {
@@ -5864,7 +6012,6 @@ function cleanLoadingLinks() {
5864
6012
  }
5865
6013
  function manageStyle(element, {update, loadingStart, loadingEnd}) {
5866
6014
  const inMode = getStyleInjectionMode();
5867
- let corsCopy = null;
5868
6015
  let syncStyle = null;
5869
6016
  if (inMode === "next") {
5870
6017
  const prevStyles = [];
@@ -5875,16 +6022,11 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
5875
6022
  ) {
5876
6023
  prevStyles.push(next);
5877
6024
  }
5878
- corsCopy =
5879
- prevStyles.find(
5880
- (el) => el.matches(".darkreader--cors") && !corsStyleSet.has(el)
5881
- ) || null;
5882
6025
  syncStyle =
5883
6026
  prevStyles.find(
5884
6027
  (el) => el.matches(".darkreader--sync") && !syncStyleSet.has(el)
5885
6028
  ) || null;
5886
6029
  }
5887
- let corsCopyPositionWatcher = null;
5888
6030
  let syncStylePositionWatcher = null;
5889
6031
  let cancelAsyncOperations = false;
5890
6032
  let isOverrideEmpty = true;
@@ -5942,8 +6084,8 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
5942
6084
  return result;
5943
6085
  }
5944
6086
  function getRulesSync() {
5945
- if (corsCopy) {
5946
- return corsCopy.sheet.cssRules;
6087
+ if (corsCopies.has(element)) {
6088
+ return corsCopies.get(element).cssRules;
5947
6089
  }
5948
6090
  if (containsCSSImport()) {
5949
6091
  return null;
@@ -5964,26 +6106,10 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
5964
6106
  }
5965
6107
  function insertStyle() {
5966
6108
  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) {
6109
+ if (element.nextSibling !== syncStyle) {
5981
6110
  element.parentNode.insertBefore(syncStyle, element.nextSibling);
5982
6111
  }
5983
6112
  } else if (inMode === "away") {
5984
- if (corsCopy && !corsCopy.parentNode) {
5985
- injectStyleAway(corsCopy);
5986
- }
5987
6113
  injectStyleAway(syncStyle);
5988
6114
  }
5989
6115
  }
@@ -6059,8 +6185,8 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
6059
6185
  return null;
6060
6186
  }
6061
6187
  await createOrUpdateCORSCopy(cssText, cssBasePath);
6062
- if (corsCopy) {
6063
- return corsCopy.sheet.cssRules;
6188
+ if (corsCopies.has(element)) {
6189
+ return corsCopies.get(element).cssRules;
6064
6190
  }
6065
6191
  return null;
6066
6192
  }
@@ -6071,43 +6197,22 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
6071
6197
  cssText,
6072
6198
  cssBasePath
6073
6199
  );
6074
- if (corsCopy) {
6200
+ if (corsCopies.has(element)) {
6075
6201
  if (
6076
- (corsCopy.textContent?.length ?? 0) < fullCSSText.length
6202
+ (corsCopiesTextLengths.get(element) ?? 0) <
6203
+ fullCSSText.length
6077
6204
  ) {
6078
- corsCopy.textContent = fullCSSText;
6205
+ corsCopies.get(element).replaceSync(fullCSSText);
6206
+ corsCopiesTextLengths.set(element, fullCSSText.length);
6079
6207
  }
6080
6208
  } 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
- }
6209
+ const corsCopy = new CSSStyleSheet();
6210
+ corsCopy.replaceSync(fullCSSText);
6211
+ corsCopies.set(element, corsCopy);
6101
6212
  }
6102
6213
  } catch (err) {
6103
6214
  logWarn(err);
6104
6215
  }
6105
- if (corsCopy && inMode === "next") {
6106
- corsCopyPositionWatcher = watchForNodePosition(
6107
- corsCopy,
6108
- "prev-sibling"
6109
- );
6110
- }
6111
6216
  }
6112
6217
  }
6113
6218
  function details(options) {
@@ -6230,13 +6335,12 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
6230
6335
  function pause() {
6231
6336
  observer.disconnect();
6232
6337
  cancelAsyncOperations = true;
6233
- corsCopyPositionWatcher && corsCopyPositionWatcher.stop();
6234
6338
  syncStylePositionWatcher && syncStylePositionWatcher.stop();
6235
6339
  sheetChangeWatcher.stop();
6236
6340
  }
6237
6341
  function destroy() {
6238
6342
  pause();
6239
- removeNode(corsCopy);
6343
+ corsCopies.delete(element);
6240
6344
  removeNode(syncStyle);
6241
6345
  loadingEnd();
6242
6346
  if (rejectorsForLoadingLinks.has(loadingLinkId)) {
@@ -6264,7 +6368,6 @@ function manageStyle(element, {update, loadingStart, loadingEnd}) {
6264
6368
  }
6265
6369
  logWarn("Restore style", syncStyle, element);
6266
6370
  insertStyle();
6267
- corsCopyPositionWatcher && corsCopyPositionWatcher.skip();
6268
6371
  syncStylePositionWatcher && syncStylePositionWatcher.skip();
6269
6372
  if (!isOverrideEmpty) {
6270
6373
  forceRenderStyle = true;
@@ -6335,7 +6438,9 @@ async function loadText(url) {
6335
6438
  origin: location.origin
6336
6439
  });
6337
6440
  }
6338
- writeCSSFetchCache(url, text);
6441
+ if (parsedURL.origin === location.origin) {
6442
+ writeCSSFetchCache(url, text);
6443
+ }
6339
6444
  return text;
6340
6445
  }
6341
6446
  async function replaceCSSImports(cssText, basePath, cache = new Map()) {
@@ -6391,20 +6496,6 @@ async function replaceCSSImports(cssText, basePath, cache = new Map()) {
6391
6496
  cssText = cssText.trim();
6392
6497
  return cssText;
6393
6498
  }
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
6499
 
6409
6500
  function injectProxy(enableStyleSheetsProxy, enableCustomElementRegistryProxy) {
6410
6501
  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.118",
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
  }