@duckduckgo/autoconsent 14.93.0 → 14.95.0

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.
@@ -743,7 +743,8 @@ function normalizeConfig(providedConfig) {
743
743
  messages: false,
744
744
  waits: false
745
745
  },
746
- performanceLoggingEnabled: false
746
+ performanceLoggingEnabled: false,
747
+ heuristicPopupSearchTimeout: 100
747
748
  };
748
749
  const updatedConfig = copyObject(defaultConfig);
749
750
  for (const key of Object.keys(defaultConfig)) {
@@ -1495,6 +1496,7 @@ var NEVER_MATCH_PATTERNS = [
1495
1496
  // lib/heuristics.ts
1496
1497
  var BUTTON_LIKE_ELEMENT_SELECTOR = 'button, input[type="button"], input[type="submit"], a, [role="button"], [class*="button"]';
1497
1498
  var TEXT_LIMIT = 1e5;
1499
+ var POPUP_SEARCH_MAX_TIME = 100;
1498
1500
  function checkHeuristicPatterns(allText, detectPatterns = DETECT_PATTERNS) {
1499
1501
  allText = allText.slice(0, TEXT_LIMIT);
1500
1502
  const patterns = [];
@@ -1508,8 +1510,8 @@ function checkHeuristicPatterns(allText, detectPatterns = DETECT_PATTERNS) {
1508
1510
  }
1509
1511
  return { patterns, snippets: snippets2 };
1510
1512
  }
1511
- function getActionablePopups() {
1512
- const popups = getPotentialPopups();
1513
+ function getActionablePopups(timeout = POPUP_SEARCH_MAX_TIME) {
1514
+ const popups = getPotentialPopups(timeout);
1513
1515
  const result = popups.reduce((acc, popup) => {
1514
1516
  const popupText = popup.text?.trim();
1515
1517
  if (popupText) {
@@ -1563,17 +1565,17 @@ function cleanButtonText(buttonText) {
1563
1565
  result = result.trim();
1564
1566
  return result;
1565
1567
  }
1566
- function getPotentialPopups() {
1568
+ function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME) {
1567
1569
  const isFramed = !isTopFrame();
1568
1570
  if (isFramed && window.parent && window.parent !== window.top) {
1569
1571
  return [];
1570
1572
  }
1571
- return collectPotentialPopups(isFramed);
1573
+ return collectPotentialPopups(isFramed, timeout);
1572
1574
  }
1573
- function collectPotentialPopups(isFramed) {
1575
+ function collectPotentialPopups(isFramed, timeout = POPUP_SEARCH_MAX_TIME) {
1574
1576
  let elements = [];
1575
1577
  if (!isFramed) {
1576
- elements = getPopupLikeElements();
1578
+ elements = getPopupLikeElements(timeout);
1577
1579
  } else {
1578
1580
  const doc = document.body || document.documentElement;
1579
1581
  if (doc && isElementVisible(doc) && doc.innerText) {
@@ -1601,7 +1603,8 @@ function isDialogLikeElement(node) {
1601
1603
  }
1602
1604
  return false;
1603
1605
  }
1604
- function getPopupLikeElements() {
1606
+ function getPopupLikeElements(timeout = POPUP_SEARCH_MAX_TIME) {
1607
+ const startTime = performance.now();
1605
1608
  const walker = document.createTreeWalker(
1606
1609
  document.documentElement,
1607
1610
  NodeFilter.SHOW_ELEMENT,
@@ -1620,6 +1623,9 @@ function getPopupLikeElements() {
1620
1623
  return NodeFilter.FILTER_ACCEPT;
1621
1624
  }
1622
1625
  }
1626
+ if (performance.now() - startTime > timeout) {
1627
+ return NodeFilter.FILTER_REJECT;
1628
+ }
1623
1629
  return NodeFilter.FILTER_SKIP;
1624
1630
  }
1625
1631
  }
@@ -2023,9 +2029,10 @@ var AutoConsentHeuristicCMP = class extends AutoConsentCMPBase {
2023
2029
  get isCosmetic() {
2024
2030
  return false;
2025
2031
  }
2026
- detectCmp() {
2032
+ async detectCmp() {
2033
+ await new Promise((resolve) => setTimeout(resolve, 0));
2027
2034
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorStart");
2028
- this.popups = getActionablePopups();
2035
+ this.popups = getActionablePopups(this.autoconsent.config.heuristicPopupSearchTimeout);
2029
2036
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorEnd");
2030
2037
  this.autoconsent.config.performanceLoggingEnabled && performance.measure("heuristicDetector", "heuristicDetectorStart", "heuristicDetectorEnd");
2031
2038
  if (this.popups.length > 0) {
@@ -3758,7 +3765,7 @@ var AutoConsent = class {
3758
3765
  }
3759
3766
  }
3760
3767
  });
3761
- const heuristicRules = isTop && this.config.enableHeuristicAction ? [new AutoConsentHeuristicCMP(this)] : [];
3768
+ const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this)] : [];
3762
3769
  const rulesPriorityStages = [
3763
3770
  ["site-specific", siteSpecificRules],
3764
3771
  ["generic", genericRules],
@@ -3798,8 +3805,12 @@ var AutoConsent = class {
3798
3805
  }
3799
3806
  this.detectHeuristics();
3800
3807
  if (foundCMPs.length === 0 && retries > 0) {
3808
+ const waitFor2 = [this.domActions.wait(500)];
3809
+ if (this.state.findCmpAttempts > 1) {
3810
+ waitFor2.push(mutationObserver);
3811
+ }
3801
3812
  try {
3802
- await Promise.all([this.domActions.wait(500), mutationObserver]);
3813
+ await Promise.all(waitFor2);
3803
3814
  } catch (e) {
3804
3815
  return [];
3805
3816
  }
@@ -713,7 +713,8 @@ function normalizeConfig(providedConfig) {
713
713
  messages: false,
714
714
  waits: false
715
715
  },
716
- performanceLoggingEnabled: false
716
+ performanceLoggingEnabled: false,
717
+ heuristicPopupSearchTimeout: 100
717
718
  };
718
719
  const updatedConfig = copyObject(defaultConfig);
719
720
  for (const key of Object.keys(defaultConfig)) {
@@ -1465,6 +1466,7 @@ var NEVER_MATCH_PATTERNS = [
1465
1466
  // lib/heuristics.ts
1466
1467
  var BUTTON_LIKE_ELEMENT_SELECTOR = 'button, input[type="button"], input[type="submit"], a, [role="button"], [class*="button"]';
1467
1468
  var TEXT_LIMIT = 1e5;
1469
+ var POPUP_SEARCH_MAX_TIME = 100;
1468
1470
  function checkHeuristicPatterns(allText, detectPatterns = DETECT_PATTERNS) {
1469
1471
  allText = allText.slice(0, TEXT_LIMIT);
1470
1472
  const patterns = [];
@@ -1478,8 +1480,8 @@ function checkHeuristicPatterns(allText, detectPatterns = DETECT_PATTERNS) {
1478
1480
  }
1479
1481
  return { patterns, snippets: snippets2 };
1480
1482
  }
1481
- function getActionablePopups() {
1482
- const popups = getPotentialPopups();
1483
+ function getActionablePopups(timeout = POPUP_SEARCH_MAX_TIME) {
1484
+ const popups = getPotentialPopups(timeout);
1483
1485
  const result = popups.reduce((acc, popup) => {
1484
1486
  const popupText = popup.text?.trim();
1485
1487
  if (popupText) {
@@ -1533,17 +1535,17 @@ function cleanButtonText(buttonText) {
1533
1535
  result = result.trim();
1534
1536
  return result;
1535
1537
  }
1536
- function getPotentialPopups() {
1538
+ function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME) {
1537
1539
  const isFramed = !isTopFrame();
1538
1540
  if (isFramed && window.parent && window.parent !== window.top) {
1539
1541
  return [];
1540
1542
  }
1541
- return collectPotentialPopups(isFramed);
1543
+ return collectPotentialPopups(isFramed, timeout);
1542
1544
  }
1543
- function collectPotentialPopups(isFramed) {
1545
+ function collectPotentialPopups(isFramed, timeout = POPUP_SEARCH_MAX_TIME) {
1544
1546
  let elements = [];
1545
1547
  if (!isFramed) {
1546
- elements = getPopupLikeElements();
1548
+ elements = getPopupLikeElements(timeout);
1547
1549
  } else {
1548
1550
  const doc = document.body || document.documentElement;
1549
1551
  if (doc && isElementVisible(doc) && doc.innerText) {
@@ -1571,7 +1573,8 @@ function isDialogLikeElement(node) {
1571
1573
  }
1572
1574
  return false;
1573
1575
  }
1574
- function getPopupLikeElements() {
1576
+ function getPopupLikeElements(timeout = POPUP_SEARCH_MAX_TIME) {
1577
+ const startTime = performance.now();
1575
1578
  const walker = document.createTreeWalker(
1576
1579
  document.documentElement,
1577
1580
  NodeFilter.SHOW_ELEMENT,
@@ -1590,6 +1593,9 @@ function getPopupLikeElements() {
1590
1593
  return NodeFilter.FILTER_ACCEPT;
1591
1594
  }
1592
1595
  }
1596
+ if (performance.now() - startTime > timeout) {
1597
+ return NodeFilter.FILTER_REJECT;
1598
+ }
1593
1599
  return NodeFilter.FILTER_SKIP;
1594
1600
  }
1595
1601
  }
@@ -1993,9 +1999,10 @@ var AutoConsentHeuristicCMP = class extends AutoConsentCMPBase {
1993
1999
  get isCosmetic() {
1994
2000
  return false;
1995
2001
  }
1996
- detectCmp() {
2002
+ async detectCmp() {
2003
+ await new Promise((resolve) => setTimeout(resolve, 0));
1997
2004
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorStart");
1998
- this.popups = getActionablePopups();
2005
+ this.popups = getActionablePopups(this.autoconsent.config.heuristicPopupSearchTimeout);
1999
2006
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorEnd");
2000
2007
  this.autoconsent.config.performanceLoggingEnabled && performance.measure("heuristicDetector", "heuristicDetectorStart", "heuristicDetectorEnd");
2001
2008
  if (this.popups.length > 0) {
@@ -3728,7 +3735,7 @@ var AutoConsent = class {
3728
3735
  }
3729
3736
  }
3730
3737
  });
3731
- const heuristicRules = isTop && this.config.enableHeuristicAction ? [new AutoConsentHeuristicCMP(this)] : [];
3738
+ const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this)] : [];
3732
3739
  const rulesPriorityStages = [
3733
3740
  ["site-specific", siteSpecificRules],
3734
3741
  ["generic", genericRules],
@@ -3768,8 +3775,12 @@ var AutoConsent = class {
3768
3775
  }
3769
3776
  this.detectHeuristics();
3770
3777
  if (foundCMPs.length === 0 && retries > 0) {
3778
+ const waitFor2 = [this.domActions.wait(500)];
3779
+ if (this.state.findCmpAttempts > 1) {
3780
+ waitFor2.push(mutationObserver);
3781
+ }
3771
3782
  try {
3772
- await Promise.all([this.domActions.wait(500), mutationObserver]);
3783
+ await Promise.all(waitFor2);
3773
3784
  } catch (e) {
3774
3785
  return [];
3775
3786
  }
@@ -11306,7 +11306,8 @@ function normalizeConfig(providedConfig) {
11306
11306
  messages: false,
11307
11307
  waits: false
11308
11308
  },
11309
- performanceLoggingEnabled: false
11309
+ performanceLoggingEnabled: false,
11310
+ heuristicPopupSearchTimeout: 100
11310
11311
  };
11311
11312
  const updatedConfig = copyObject(defaultConfig);
11312
11313
  for (const key of Object.keys(defaultConfig)) {
@@ -12725,6 +12726,7 @@ var NEVER_MATCH_PATTERNS = [
12725
12726
  // lib/heuristics.ts
12726
12727
  var BUTTON_LIKE_ELEMENT_SELECTOR = 'button, input[type="button"], input[type="submit"], a, [role="button"], [class*="button"]';
12727
12728
  var TEXT_LIMIT = 1e5;
12729
+ var POPUP_SEARCH_MAX_TIME = 100;
12728
12730
  function checkHeuristicPatterns(allText, detectPatterns = DETECT_PATTERNS) {
12729
12731
  allText = allText.slice(0, TEXT_LIMIT);
12730
12732
  const patterns = [];
@@ -12738,8 +12740,8 @@ function checkHeuristicPatterns(allText, detectPatterns = DETECT_PATTERNS) {
12738
12740
  }
12739
12741
  return { patterns, snippets: snippets2 };
12740
12742
  }
12741
- function getActionablePopups() {
12742
- const popups = getPotentialPopups();
12743
+ function getActionablePopups(timeout = POPUP_SEARCH_MAX_TIME) {
12744
+ const popups = getPotentialPopups(timeout);
12743
12745
  const result = popups.reduce((acc, popup) => {
12744
12746
  const popupText = popup.text?.trim();
12745
12747
  if (popupText) {
@@ -12793,17 +12795,17 @@ function cleanButtonText(buttonText) {
12793
12795
  result = result.trim();
12794
12796
  return result;
12795
12797
  }
12796
- function getPotentialPopups() {
12798
+ function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME) {
12797
12799
  const isFramed = !isTopFrame();
12798
12800
  if (isFramed && window.parent && window.parent !== window.top) {
12799
12801
  return [];
12800
12802
  }
12801
- return collectPotentialPopups(isFramed);
12803
+ return collectPotentialPopups(isFramed, timeout);
12802
12804
  }
12803
- function collectPotentialPopups(isFramed) {
12805
+ function collectPotentialPopups(isFramed, timeout = POPUP_SEARCH_MAX_TIME) {
12804
12806
  let elements = [];
12805
12807
  if (!isFramed) {
12806
- elements = getPopupLikeElements();
12808
+ elements = getPopupLikeElements(timeout);
12807
12809
  } else {
12808
12810
  const doc = document.body || document.documentElement;
12809
12811
  if (doc && isElementVisible(doc) && doc.innerText) {
@@ -12831,7 +12833,8 @@ function isDialogLikeElement(node) {
12831
12833
  }
12832
12834
  return false;
12833
12835
  }
12834
- function getPopupLikeElements() {
12836
+ function getPopupLikeElements(timeout = POPUP_SEARCH_MAX_TIME) {
12837
+ const startTime = performance.now();
12835
12838
  const walker = document.createTreeWalker(
12836
12839
  document.documentElement,
12837
12840
  NodeFilter.SHOW_ELEMENT,
@@ -12850,6 +12853,9 @@ function getPopupLikeElements() {
12850
12853
  return NodeFilter.FILTER_ACCEPT;
12851
12854
  }
12852
12855
  }
12856
+ if (performance.now() - startTime > timeout) {
12857
+ return NodeFilter.FILTER_REJECT;
12858
+ }
12853
12859
  return NodeFilter.FILTER_SKIP;
12854
12860
  }
12855
12861
  }
@@ -13253,9 +13259,10 @@ var AutoConsentHeuristicCMP = class extends AutoConsentCMPBase {
13253
13259
  get isCosmetic() {
13254
13260
  return false;
13255
13261
  }
13256
- detectCmp() {
13262
+ async detectCmp() {
13263
+ await new Promise((resolve) => setTimeout(resolve, 0));
13257
13264
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorStart");
13258
- this.popups = getActionablePopups();
13265
+ this.popups = getActionablePopups(this.autoconsent.config.heuristicPopupSearchTimeout);
13259
13266
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorEnd");
13260
13267
  this.autoconsent.config.performanceLoggingEnabled && performance.measure("heuristicDetector", "heuristicDetectorStart", "heuristicDetectorEnd");
13261
13268
  if (this.popups.length > 0) {
@@ -14756,7 +14763,7 @@ var AutoConsent = class {
14756
14763
  }
14757
14764
  }
14758
14765
  });
14759
- const heuristicRules = isTop && this.config.enableHeuristicAction ? [new AutoConsentHeuristicCMP(this)] : [];
14766
+ const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this)] : [];
14760
14767
  const rulesPriorityStages = [
14761
14768
  ["site-specific", siteSpecificRules],
14762
14769
  ["generic", genericRules],
@@ -14796,8 +14803,12 @@ var AutoConsent = class {
14796
14803
  }
14797
14804
  this.detectHeuristics();
14798
14805
  if (foundCMPs.length === 0 && retries > 0) {
14806
+ const waitFor2 = [this.domActions.wait(500)];
14807
+ if (this.state.findCmpAttempts > 1) {
14808
+ waitFor2.push(mutationObserver);
14809
+ }
14799
14810
  try {
14800
- await Promise.all([this.domActions.wait(500), mutationObserver]);
14811
+ await Promise.all(waitFor2);
14801
14812
  } catch (e) {
14802
14813
  return [];
14803
14814
  }
@@ -11240,7 +11240,8 @@ function normalizeConfig(providedConfig) {
11240
11240
  messages: false,
11241
11241
  waits: false
11242
11242
  },
11243
- performanceLoggingEnabled: false
11243
+ performanceLoggingEnabled: false,
11244
+ heuristicPopupSearchTimeout: 100
11244
11245
  };
11245
11246
  const updatedConfig = copyObject(defaultConfig);
11246
11247
  for (const key of Object.keys(defaultConfig)) {
@@ -12659,6 +12660,7 @@ var NEVER_MATCH_PATTERNS = [
12659
12660
  // lib/heuristics.ts
12660
12661
  var BUTTON_LIKE_ELEMENT_SELECTOR = 'button, input[type="button"], input[type="submit"], a, [role="button"], [class*="button"]';
12661
12662
  var TEXT_LIMIT = 1e5;
12663
+ var POPUP_SEARCH_MAX_TIME = 100;
12662
12664
  function checkHeuristicPatterns(allText, detectPatterns = DETECT_PATTERNS) {
12663
12665
  allText = allText.slice(0, TEXT_LIMIT);
12664
12666
  const patterns = [];
@@ -12672,8 +12674,8 @@ function checkHeuristicPatterns(allText, detectPatterns = DETECT_PATTERNS) {
12672
12674
  }
12673
12675
  return { patterns, snippets: snippets2 };
12674
12676
  }
12675
- function getActionablePopups() {
12676
- const popups = getPotentialPopups();
12677
+ function getActionablePopups(timeout = POPUP_SEARCH_MAX_TIME) {
12678
+ const popups = getPotentialPopups(timeout);
12677
12679
  const result = popups.reduce((acc, popup) => {
12678
12680
  const popupText = popup.text?.trim();
12679
12681
  if (popupText) {
@@ -12727,17 +12729,17 @@ function cleanButtonText(buttonText) {
12727
12729
  result = result.trim();
12728
12730
  return result;
12729
12731
  }
12730
- function getPotentialPopups() {
12732
+ function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME) {
12731
12733
  const isFramed = !isTopFrame();
12732
12734
  if (isFramed && window.parent && window.parent !== window.top) {
12733
12735
  return [];
12734
12736
  }
12735
- return collectPotentialPopups(isFramed);
12737
+ return collectPotentialPopups(isFramed, timeout);
12736
12738
  }
12737
- function collectPotentialPopups(isFramed) {
12739
+ function collectPotentialPopups(isFramed, timeout = POPUP_SEARCH_MAX_TIME) {
12738
12740
  let elements = [];
12739
12741
  if (!isFramed) {
12740
- elements = getPopupLikeElements();
12742
+ elements = getPopupLikeElements(timeout);
12741
12743
  } else {
12742
12744
  const doc = document.body || document.documentElement;
12743
12745
  if (doc && isElementVisible(doc) && doc.innerText) {
@@ -12765,7 +12767,8 @@ function isDialogLikeElement(node) {
12765
12767
  }
12766
12768
  return false;
12767
12769
  }
12768
- function getPopupLikeElements() {
12770
+ function getPopupLikeElements(timeout = POPUP_SEARCH_MAX_TIME) {
12771
+ const startTime = performance.now();
12769
12772
  const walker = document.createTreeWalker(
12770
12773
  document.documentElement,
12771
12774
  NodeFilter.SHOW_ELEMENT,
@@ -12784,6 +12787,9 @@ function getPopupLikeElements() {
12784
12787
  return NodeFilter.FILTER_ACCEPT;
12785
12788
  }
12786
12789
  }
12790
+ if (performance.now() - startTime > timeout) {
12791
+ return NodeFilter.FILTER_REJECT;
12792
+ }
12787
12793
  return NodeFilter.FILTER_SKIP;
12788
12794
  }
12789
12795
  }
@@ -13187,9 +13193,10 @@ var AutoConsentHeuristicCMP = class extends AutoConsentCMPBase {
13187
13193
  get isCosmetic() {
13188
13194
  return false;
13189
13195
  }
13190
- detectCmp() {
13196
+ async detectCmp() {
13197
+ await new Promise((resolve) => setTimeout(resolve, 0));
13191
13198
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorStart");
13192
- this.popups = getActionablePopups();
13199
+ this.popups = getActionablePopups(this.autoconsent.config.heuristicPopupSearchTimeout);
13193
13200
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorEnd");
13194
13201
  this.autoconsent.config.performanceLoggingEnabled && performance.measure("heuristicDetector", "heuristicDetectorStart", "heuristicDetectorEnd");
13195
13202
  if (this.popups.length > 0) {
@@ -14690,7 +14697,7 @@ var AutoConsent = class {
14690
14697
  }
14691
14698
  }
14692
14699
  });
14693
- const heuristicRules = isTop && this.config.enableHeuristicAction ? [new AutoConsentHeuristicCMP(this)] : [];
14700
+ const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this)] : [];
14694
14701
  const rulesPriorityStages = [
14695
14702
  ["site-specific", siteSpecificRules],
14696
14703
  ["generic", genericRules],
@@ -14730,8 +14737,12 @@ var AutoConsent = class {
14730
14737
  }
14731
14738
  this.detectHeuristics();
14732
14739
  if (foundCMPs.length === 0 && retries > 0) {
14740
+ const waitFor2 = [this.domActions.wait(500)];
14741
+ if (this.state.findCmpAttempts > 1) {
14742
+ waitFor2.push(mutationObserver);
14743
+ }
14733
14744
  try {
14734
- await Promise.all([this.domActions.wait(500), mutationObserver]);
14745
+ await Promise.all(waitFor2);
14735
14746
  } catch (e) {
14736
14747
  return [];
14737
14748
  }
@@ -715,7 +715,8 @@
715
715
  messages: false,
716
716
  waits: false
717
717
  },
718
- performanceLoggingEnabled: false
718
+ performanceLoggingEnabled: false,
719
+ heuristicPopupSearchTimeout: 100
719
720
  };
720
721
  const updatedConfig = copyObject(defaultConfig);
721
722
  for (const key of Object.keys(defaultConfig)) {
@@ -1467,6 +1468,7 @@
1467
1468
  // lib/heuristics.ts
1468
1469
  var BUTTON_LIKE_ELEMENT_SELECTOR = 'button, input[type="button"], input[type="submit"], a, [role="button"], [class*="button"]';
1469
1470
  var TEXT_LIMIT = 1e5;
1471
+ var POPUP_SEARCH_MAX_TIME = 100;
1470
1472
  function checkHeuristicPatterns(allText, detectPatterns = DETECT_PATTERNS) {
1471
1473
  allText = allText.slice(0, TEXT_LIMIT);
1472
1474
  const patterns = [];
@@ -1480,8 +1482,8 @@
1480
1482
  }
1481
1483
  return { patterns, snippets: snippets2 };
1482
1484
  }
1483
- function getActionablePopups() {
1484
- const popups = getPotentialPopups();
1485
+ function getActionablePopups(timeout = POPUP_SEARCH_MAX_TIME) {
1486
+ const popups = getPotentialPopups(timeout);
1485
1487
  const result = popups.reduce((acc, popup) => {
1486
1488
  const popupText = popup.text?.trim();
1487
1489
  if (popupText) {
@@ -1535,17 +1537,17 @@
1535
1537
  result = result.trim();
1536
1538
  return result;
1537
1539
  }
1538
- function getPotentialPopups() {
1540
+ function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME) {
1539
1541
  const isFramed = !isTopFrame();
1540
1542
  if (isFramed && window.parent && window.parent !== window.top) {
1541
1543
  return [];
1542
1544
  }
1543
- return collectPotentialPopups(isFramed);
1545
+ return collectPotentialPopups(isFramed, timeout);
1544
1546
  }
1545
- function collectPotentialPopups(isFramed) {
1547
+ function collectPotentialPopups(isFramed, timeout = POPUP_SEARCH_MAX_TIME) {
1546
1548
  let elements = [];
1547
1549
  if (!isFramed) {
1548
- elements = getPopupLikeElements();
1550
+ elements = getPopupLikeElements(timeout);
1549
1551
  } else {
1550
1552
  const doc = document.body || document.documentElement;
1551
1553
  if (doc && isElementVisible(doc) && doc.innerText) {
@@ -1573,7 +1575,8 @@
1573
1575
  }
1574
1576
  return false;
1575
1577
  }
1576
- function getPopupLikeElements() {
1578
+ function getPopupLikeElements(timeout = POPUP_SEARCH_MAX_TIME) {
1579
+ const startTime = performance.now();
1577
1580
  const walker = document.createTreeWalker(
1578
1581
  document.documentElement,
1579
1582
  NodeFilter.SHOW_ELEMENT,
@@ -1592,6 +1595,9 @@
1592
1595
  return NodeFilter.FILTER_ACCEPT;
1593
1596
  }
1594
1597
  }
1598
+ if (performance.now() - startTime > timeout) {
1599
+ return NodeFilter.FILTER_REJECT;
1600
+ }
1595
1601
  return NodeFilter.FILTER_SKIP;
1596
1602
  }
1597
1603
  }
@@ -1995,9 +2001,10 @@
1995
2001
  get isCosmetic() {
1996
2002
  return false;
1997
2003
  }
1998
- detectCmp() {
2004
+ async detectCmp() {
2005
+ await new Promise((resolve) => setTimeout(resolve, 0));
1999
2006
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorStart");
2000
- this.popups = getActionablePopups();
2007
+ this.popups = getActionablePopups(this.autoconsent.config.heuristicPopupSearchTimeout);
2001
2008
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorEnd");
2002
2009
  this.autoconsent.config.performanceLoggingEnabled && performance.measure("heuristicDetector", "heuristicDetectorStart", "heuristicDetectorEnd");
2003
2010
  if (this.popups.length > 0) {
@@ -3498,7 +3505,7 @@
3498
3505
  }
3499
3506
  }
3500
3507
  });
3501
- const heuristicRules = isTop && this.config.enableHeuristicAction ? [new AutoConsentHeuristicCMP(this)] : [];
3508
+ const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this)] : [];
3502
3509
  const rulesPriorityStages = [
3503
3510
  ["site-specific", siteSpecificRules],
3504
3511
  ["generic", genericRules],
@@ -3538,8 +3545,12 @@
3538
3545
  }
3539
3546
  this.detectHeuristics();
3540
3547
  if (foundCMPs.length === 0 && retries > 0) {
3548
+ const waitFor2 = [this.domActions.wait(500)];
3549
+ if (this.state.findCmpAttempts > 1) {
3550
+ waitFor2.push(mutationObserver);
3551
+ }
3541
3552
  try {
3542
- await Promise.all([this.domActions.wait(500), mutationObserver]);
3553
+ await Promise.all(waitFor2);
3543
3554
  } catch (e) {
3544
3555
  return [];
3545
3556
  }
@@ -3,7 +3,7 @@ export declare function checkHeuristicPatterns(allText: string, detectPatterns?:
3
3
  patterns: string[];
4
4
  snippets: string[];
5
5
  };
6
- export declare function getActionablePopups(): PopupData[];
6
+ export declare function getActionablePopups(timeout?: number): PopupData[];
7
7
  export declare function classifyButtons(buttons: ButtonData[]): {
8
8
  rejectButtons: ButtonData[];
9
9
  otherButtons: ButtonData[];
@@ -73,6 +73,7 @@ export type Config = {
73
73
  waits: boolean;
74
74
  };
75
75
  performanceLoggingEnabled: boolean;
76
+ heuristicPopupSearchTimeout: number;
76
77
  };
77
78
  export type LifecycleState = 'loading' | 'initialized' | 'waitingForInitResponse' | 'started' | 'nothingDetected' | 'cosmeticFiltersDetected' | 'cmpDetected' | 'openPopupDetected' | 'runningOptOut' | 'runningOptIn' | 'optOutSucceeded' | 'optOutFailed' | 'optInSucceeded' | 'optInFailed' | 'done';
78
79
  export type ConsentState = {
package/lib/cmps/base.ts CHANGED
@@ -441,9 +441,11 @@ export class AutoConsentHeuristicCMP extends AutoConsentCMPBase {
441
441
  return false;
442
442
  }
443
443
 
444
- detectCmp(): Promise<boolean> {
444
+ async detectCmp(): Promise<boolean> {
445
+ // wait for one tick to deprioritize heavy DOM operations
446
+ await new Promise((resolve) => setTimeout(resolve, 0));
445
447
  this.autoconsent.config.performanceLoggingEnabled && performance.mark('heuristicDetectorStart');
446
- this.popups = getActionablePopups();
448
+ this.popups = getActionablePopups(this.autoconsent.config.heuristicPopupSearchTimeout);
447
449
  this.autoconsent.config.performanceLoggingEnabled && performance.mark('heuristicDetectorEnd');
448
450
  this.autoconsent.config.performanceLoggingEnabled &&
449
451
  performance.measure('heuristicDetector', 'heuristicDetectorStart', 'heuristicDetectorEnd');
package/lib/heuristics.ts CHANGED
@@ -4,6 +4,7 @@ import { isElementVisible, isTopFrame } from './utils';
4
4
 
5
5
  const BUTTON_LIKE_ELEMENT_SELECTOR = 'button, input[type="button"], input[type="submit"], a, [role="button"], [class*="button"]';
6
6
  const TEXT_LIMIT = 100000;
7
+ const POPUP_SEARCH_MAX_TIME = 100;
7
8
 
8
9
  export function checkHeuristicPatterns(allText: string, detectPatterns = DETECT_PATTERNS) {
9
10
  allText = allText.slice(0, TEXT_LIMIT);
@@ -20,8 +21,8 @@ export function checkHeuristicPatterns(allText: string, detectPatterns = DETECT_
20
21
  return { patterns, snippets };
21
22
  }
22
23
 
23
- export function getActionablePopups(): PopupData[] {
24
- const popups = getPotentialPopups();
24
+ export function getActionablePopups(timeout = POPUP_SEARCH_MAX_TIME): PopupData[] {
25
+ const popups = getPotentialPopups(timeout);
25
26
  const result = popups.reduce((acc, popup) => {
26
27
  const popupText = popup.text?.trim();
27
28
  if (popupText) {
@@ -88,20 +89,20 @@ export function cleanButtonText(buttonText: string): string {
88
89
  return result;
89
90
  }
90
91
 
91
- function getPotentialPopups() {
92
+ function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME) {
92
93
  const isFramed = !isTopFrame();
93
94
  // do not inspect frames that are more than one level deep
94
95
  if (isFramed && window.parent && window.parent !== window.top) {
95
96
  return [];
96
97
  }
97
98
 
98
- return collectPotentialPopups(isFramed);
99
+ return collectPotentialPopups(isFramed, timeout);
99
100
  }
100
101
 
101
- function collectPotentialPopups(isFramed: boolean): PopupData[] {
102
+ function collectPotentialPopups(isFramed: boolean, timeout = POPUP_SEARCH_MAX_TIME): PopupData[] {
102
103
  let elements = [];
103
104
  if (!isFramed) {
104
- elements = getPopupLikeElements();
105
+ elements = getPopupLikeElements(timeout);
105
106
  } else {
106
107
  // for iframes, just take the whole document
107
108
  const doc = document.body || document.documentElement;
@@ -140,7 +141,8 @@ export function isDialogLikeElement(node: HTMLElement): boolean {
140
141
  * Heuristic to get all elements that look like "popups"
141
142
  * TODO: this heuristic is too strict, not all popups are actually sticky/fixed
142
143
  */
143
- function getPopupLikeElements(): HTMLElement[] {
144
+ function getPopupLikeElements(timeout = POPUP_SEARCH_MAX_TIME): HTMLElement[] {
145
+ const startTime = performance.now();
144
146
  const walker = document.createTreeWalker(
145
147
  document.documentElement,
146
148
  NodeFilter.SHOW_ELEMENT, // visit only element nodes
@@ -158,11 +160,14 @@ function getPopupLikeElements(): HTMLElement[] {
158
160
  return NodeFilter.FILTER_ACCEPT;
159
161
  }
160
162
  }
163
+ // start rejecting after POPUP_SEARCH_MAX_TIME to avoid blocking the main thread
164
+ if (performance.now() - startTime > timeout) {
165
+ return NodeFilter.FILTER_REJECT;
166
+ }
161
167
  return NodeFilter.FILTER_SKIP;
162
168
  },
163
169
  },
164
170
  );
165
-
166
171
  const found = [];
167
172
  for (let node = walker.nextNode(); node; node = walker.nextNode()) {
168
173
  found.push(node as HTMLElement);
package/lib/types.ts CHANGED
@@ -77,6 +77,7 @@ export type Config = {
77
77
  waits: boolean;
78
78
  };
79
79
  performanceLoggingEnabled: boolean;
80
+ heuristicPopupSearchTimeout: number;
80
81
  };
81
82
 
82
83
  export type LifecycleState =
package/lib/utils.ts CHANGED
@@ -94,6 +94,7 @@ export function normalizeConfig(providedConfig: any): Config {
94
94
  waits: false,
95
95
  },
96
96
  performanceLoggingEnabled: false,
97
+ heuristicPopupSearchTimeout: 100,
97
98
  };
98
99
  const updatedConfig: Config = copyObject(defaultConfig);
99
100
  // filter out any unknown entries
package/lib/web.ts CHANGED
@@ -294,8 +294,9 @@ export default class AutoConsent {
294
294
  }
295
295
  }
296
296
  });
297
- // heuristic CMP is only run in the top frame and only if heuristic action is enabled
298
- const heuristicRules = isTop && this.config.enableHeuristicAction ? [new AutoConsentHeuristicCMP(this)] : [];
297
+ // heuristic CMP is only run in the top frame and only if heuristic action is enabled and retries is odd
298
+ const heuristicRules =
299
+ isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this)] : [];
299
300
 
300
301
  const rulesPriorityStages: [string, AutoCMP[]][] = [
301
302
  ['site-specific', siteSpecificRules],
@@ -347,8 +348,12 @@ export default class AutoConsent {
347
348
  // if we didn't find a CMP, try again
348
349
  // We wait 500ms, and also for some kind of dom mutation to happen before
349
350
  // rerunning the findCmp check
351
+ const waitFor: Promise<boolean>[] = [this.domActions.wait(500)];
352
+ if (this.state.findCmpAttempts > 1) {
353
+ waitFor.push(mutationObserver);
354
+ }
350
355
  try {
351
- await Promise.all([this.domActions.wait(500), mutationObserver]);
356
+ await Promise.all(waitFor);
352
357
  } catch (e) {
353
358
  // timeout waiting for mutation - break out of detection
354
359
  return [];