@duckduckgo/autoconsent 14.92.0 → 14.94.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ # v14.94.0 (Fri Jun 12 2026)
2
+
3
+ #### 🚀 Enhancement
4
+
5
+ - Improve heuristic detection performance [#1381](https://github.com/duckduckgo/autoconsent/pull/1381) ([@sammacbeth](https://github.com/sammacbeth))
6
+
7
+ #### Authors: 1
8
+
9
+ - Sam Macbeth ([@sammacbeth](https://github.com/sammacbeth))
10
+
11
+ ---
12
+
13
+ # v14.93.0 (Wed Jun 10 2026)
14
+
15
+ #### 🚀 Enhancement
16
+
17
+ - Disable per-rule performance measurement [#1389](https://github.com/duckduckgo/autoconsent/pull/1389) ([@sammacbeth](https://github.com/sammacbeth))
18
+
19
+ #### Authors: 1
20
+
21
+ - Sam Macbeth ([@sammacbeth](https://github.com/sammacbeth))
22
+
23
+ ---
24
+
1
25
  # v14.92.0 (Tue Jun 09 2026)
2
26
 
3
27
  #### 🚀 Enhancement
@@ -712,7 +712,8 @@
712
712
  messages: false,
713
713
  waits: false
714
714
  },
715
- performanceLoggingEnabled: false
715
+ performanceLoggingEnabled: false,
716
+ heuristicPopupSearchTimeout: 100
716
717
  };
717
718
  const updatedConfig = copyObject(defaultConfig);
718
719
  for (const key of Object.keys(defaultConfig)) {
@@ -708,7 +708,8 @@
708
708
  messages: false,
709
709
  waits: false
710
710
  },
711
- performanceLoggingEnabled: false
711
+ performanceLoggingEnabled: false,
712
+ heuristicPopupSearchTimeout: 100
712
713
  };
713
714
  const updatedConfig = copyObject(defaultConfig);
714
715
  for (const key of Object.keys(defaultConfig)) {
@@ -1460,6 +1461,7 @@
1460
1461
  // lib/heuristics.ts
1461
1462
  var BUTTON_LIKE_ELEMENT_SELECTOR = 'button, input[type="button"], input[type="submit"], a, [role="button"], [class*="button"]';
1462
1463
  var TEXT_LIMIT = 1e5;
1464
+ var POPUP_SEARCH_MAX_TIME = 100;
1463
1465
  function checkHeuristicPatterns(allText, detectPatterns = DETECT_PATTERNS) {
1464
1466
  allText = allText.slice(0, TEXT_LIMIT);
1465
1467
  const patterns = [];
@@ -1473,8 +1475,8 @@
1473
1475
  }
1474
1476
  return { patterns, snippets: snippets2 };
1475
1477
  }
1476
- function getActionablePopups() {
1477
- const popups = getPotentialPopups();
1478
+ function getActionablePopups(timeout = POPUP_SEARCH_MAX_TIME) {
1479
+ const popups = getPotentialPopups(timeout);
1478
1480
  const result = popups.reduce((acc, popup) => {
1479
1481
  const popupText = popup.text?.trim();
1480
1482
  if (popupText) {
@@ -1528,17 +1530,17 @@
1528
1530
  result = result.trim();
1529
1531
  return result;
1530
1532
  }
1531
- function getPotentialPopups() {
1533
+ function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME) {
1532
1534
  const isFramed = !isTopFrame();
1533
1535
  if (isFramed && window.parent && window.parent !== window.top) {
1534
1536
  return [];
1535
1537
  }
1536
- return collectPotentialPopups(isFramed);
1538
+ return collectPotentialPopups(isFramed, timeout);
1537
1539
  }
1538
- function collectPotentialPopups(isFramed) {
1540
+ function collectPotentialPopups(isFramed, timeout = POPUP_SEARCH_MAX_TIME) {
1539
1541
  let elements = [];
1540
1542
  if (!isFramed) {
1541
- elements = getPopupLikeElements();
1543
+ elements = getPopupLikeElements(timeout);
1542
1544
  } else {
1543
1545
  const doc = document.body || document.documentElement;
1544
1546
  if (doc && isElementVisible(doc) && doc.innerText) {
@@ -1566,7 +1568,8 @@
1566
1568
  }
1567
1569
  return false;
1568
1570
  }
1569
- function getPopupLikeElements() {
1571
+ function getPopupLikeElements(timeout = POPUP_SEARCH_MAX_TIME) {
1572
+ const startTime = performance.now();
1570
1573
  const walker = document.createTreeWalker(
1571
1574
  document.documentElement,
1572
1575
  NodeFilter.SHOW_ELEMENT,
@@ -1585,6 +1588,9 @@
1585
1588
  return NodeFilter.FILTER_ACCEPT;
1586
1589
  }
1587
1590
  }
1591
+ if (performance.now() - startTime > timeout) {
1592
+ return NodeFilter.FILTER_REJECT;
1593
+ }
1588
1594
  return NodeFilter.FILTER_SKIP;
1589
1595
  }
1590
1596
  }
@@ -1988,9 +1994,10 @@
1988
1994
  get isCosmetic() {
1989
1995
  return false;
1990
1996
  }
1991
- detectCmp() {
1997
+ async detectCmp() {
1998
+ await new Promise((resolve) => setTimeout(resolve, 0));
1992
1999
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorStart");
1993
- this.popups = getActionablePopups();
2000
+ this.popups = getActionablePopups(this.autoconsent.config.heuristicPopupSearchTimeout);
1994
2001
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorEnd");
1995
2002
  this.autoconsent.config.performanceLoggingEnabled && performance.measure("heuristicDetector", "heuristicDetectorStart", "heuristicDetectorEnd");
1996
2003
  if (this.popups.length > 0) {
@@ -3490,7 +3497,7 @@
3490
3497
  }
3491
3498
  }
3492
3499
  });
3493
- const heuristicRules = isTop && this.config.enableHeuristicAction ? [new AutoConsentHeuristicCMP(this)] : [];
3500
+ const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this)] : [];
3494
3501
  const rulesPriorityStages = [
3495
3502
  ["site-specific", siteSpecificRules],
3496
3503
  ["generic", genericRules],
@@ -3498,10 +3505,7 @@
3498
3505
  ];
3499
3506
  const runDetectCmp = async (cmp) => {
3500
3507
  try {
3501
- this.config.performanceLoggingEnabled && performance.mark(`detectCmp_${cmp.name}`);
3502
3508
  const result = await cmp.detectCmp();
3503
- this.config.performanceLoggingEnabled && performance.mark(`detectCmpEnd_${cmp.name}`);
3504
- this.config.performanceLoggingEnabled && performance.measure(`detectCmp_${cmp.name}`, `detectCmp_${cmp.name}`, `detectCmpEnd_${cmp.name}`);
3505
3509
  if (result) {
3506
3510
  logsConfig.lifecycle && console.log(`Found CMP: ${cmp.name} ${window.location.href}`);
3507
3511
  this.sendContentMessage({
@@ -3533,8 +3537,12 @@
3533
3537
  }
3534
3538
  this.detectHeuristics();
3535
3539
  if (foundCMPs.length === 0 && retries > 0) {
3540
+ const waitFor2 = [this.domActions.wait(500)];
3541
+ if (this.state.findCmpAttempts > 1) {
3542
+ waitFor2.push(mutationObserver);
3543
+ }
3536
3544
  try {
3537
- await Promise.all([this.domActions.wait(500), mutationObserver]);
3545
+ await Promise.all(waitFor2);
3538
3546
  } catch (e) {
3539
3547
  return [];
3540
3548
  }
@@ -3829,17 +3837,6 @@
3829
3837
  parseDeclarativeRules: getRoundedPerformanceEntries("parseDeclarativeRules")
3830
3838
  };
3831
3839
  }
3832
- measureDetailedRulePerformance() {
3833
- const cmpPerformance = performance.getEntriesByType("measure").filter((m) => m.name.startsWith("detectCmp_")).reduce((acc, m) => {
3834
- const k = m.name.slice(10);
3835
- if (!acc[k]) {
3836
- acc[k] = 0;
3837
- }
3838
- acc[k] += m.duration;
3839
- return acc;
3840
- }, /* @__PURE__ */ Object.create(null));
3841
- return cmpPerformance;
3842
- }
3843
3840
  };
3844
3841
 
3845
3842
  // addon/content.ts
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Autoconsent",
4
- "version": "2026.6.6",
4
+ "version": "2026.6.10",
5
5
  "background": {
6
6
  "scripts": [
7
7
  "background.bundle.js"
@@ -539,7 +539,8 @@
539
539
  messages: false,
540
540
  waits: false
541
541
  },
542
- performanceLoggingEnabled: false
542
+ performanceLoggingEnabled: false,
543
+ heuristicPopupSearchTimeout: 100
543
544
  };
544
545
  const updatedConfig = copyObject(defaultConfig);
545
546
  for (const key of Object.keys(defaultConfig)) {
@@ -712,7 +712,8 @@
712
712
  messages: false,
713
713
  waits: false
714
714
  },
715
- performanceLoggingEnabled: false
715
+ performanceLoggingEnabled: false,
716
+ heuristicPopupSearchTimeout: 100
716
717
  };
717
718
  const updatedConfig = copyObject(defaultConfig);
718
719
  for (const key of Object.keys(defaultConfig)) {
@@ -708,7 +708,8 @@
708
708
  messages: false,
709
709
  waits: false
710
710
  },
711
- performanceLoggingEnabled: false
711
+ performanceLoggingEnabled: false,
712
+ heuristicPopupSearchTimeout: 100
712
713
  };
713
714
  const updatedConfig = copyObject(defaultConfig);
714
715
  for (const key of Object.keys(defaultConfig)) {
@@ -1460,6 +1461,7 @@
1460
1461
  // lib/heuristics.ts
1461
1462
  var BUTTON_LIKE_ELEMENT_SELECTOR = 'button, input[type="button"], input[type="submit"], a, [role="button"], [class*="button"]';
1462
1463
  var TEXT_LIMIT = 1e5;
1464
+ var POPUP_SEARCH_MAX_TIME = 100;
1463
1465
  function checkHeuristicPatterns(allText, detectPatterns = DETECT_PATTERNS) {
1464
1466
  allText = allText.slice(0, TEXT_LIMIT);
1465
1467
  const patterns = [];
@@ -1473,8 +1475,8 @@
1473
1475
  }
1474
1476
  return { patterns, snippets: snippets2 };
1475
1477
  }
1476
- function getActionablePopups() {
1477
- const popups = getPotentialPopups();
1478
+ function getActionablePopups(timeout = POPUP_SEARCH_MAX_TIME) {
1479
+ const popups = getPotentialPopups(timeout);
1478
1480
  const result = popups.reduce((acc, popup) => {
1479
1481
  const popupText = popup.text?.trim();
1480
1482
  if (popupText) {
@@ -1528,17 +1530,17 @@
1528
1530
  result = result.trim();
1529
1531
  return result;
1530
1532
  }
1531
- function getPotentialPopups() {
1533
+ function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME) {
1532
1534
  const isFramed = !isTopFrame();
1533
1535
  if (isFramed && window.parent && window.parent !== window.top) {
1534
1536
  return [];
1535
1537
  }
1536
- return collectPotentialPopups(isFramed);
1538
+ return collectPotentialPopups(isFramed, timeout);
1537
1539
  }
1538
- function collectPotentialPopups(isFramed) {
1540
+ function collectPotentialPopups(isFramed, timeout = POPUP_SEARCH_MAX_TIME) {
1539
1541
  let elements = [];
1540
1542
  if (!isFramed) {
1541
- elements = getPopupLikeElements();
1543
+ elements = getPopupLikeElements(timeout);
1542
1544
  } else {
1543
1545
  const doc = document.body || document.documentElement;
1544
1546
  if (doc && isElementVisible(doc) && doc.innerText) {
@@ -1566,7 +1568,8 @@
1566
1568
  }
1567
1569
  return false;
1568
1570
  }
1569
- function getPopupLikeElements() {
1571
+ function getPopupLikeElements(timeout = POPUP_SEARCH_MAX_TIME) {
1572
+ const startTime = performance.now();
1570
1573
  const walker = document.createTreeWalker(
1571
1574
  document.documentElement,
1572
1575
  NodeFilter.SHOW_ELEMENT,
@@ -1585,6 +1588,9 @@
1585
1588
  return NodeFilter.FILTER_ACCEPT;
1586
1589
  }
1587
1590
  }
1591
+ if (performance.now() - startTime > timeout) {
1592
+ return NodeFilter.FILTER_REJECT;
1593
+ }
1588
1594
  return NodeFilter.FILTER_SKIP;
1589
1595
  }
1590
1596
  }
@@ -1988,9 +1994,10 @@
1988
1994
  get isCosmetic() {
1989
1995
  return false;
1990
1996
  }
1991
- detectCmp() {
1997
+ async detectCmp() {
1998
+ await new Promise((resolve) => setTimeout(resolve, 0));
1992
1999
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorStart");
1993
- this.popups = getActionablePopups();
2000
+ this.popups = getActionablePopups(this.autoconsent.config.heuristicPopupSearchTimeout);
1994
2001
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorEnd");
1995
2002
  this.autoconsent.config.performanceLoggingEnabled && performance.measure("heuristicDetector", "heuristicDetectorStart", "heuristicDetectorEnd");
1996
2003
  if (this.popups.length > 0) {
@@ -3490,7 +3497,7 @@
3490
3497
  }
3491
3498
  }
3492
3499
  });
3493
- const heuristicRules = isTop && this.config.enableHeuristicAction ? [new AutoConsentHeuristicCMP(this)] : [];
3500
+ const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this)] : [];
3494
3501
  const rulesPriorityStages = [
3495
3502
  ["site-specific", siteSpecificRules],
3496
3503
  ["generic", genericRules],
@@ -3498,10 +3505,7 @@
3498
3505
  ];
3499
3506
  const runDetectCmp = async (cmp) => {
3500
3507
  try {
3501
- this.config.performanceLoggingEnabled && performance.mark(`detectCmp_${cmp.name}`);
3502
3508
  const result = await cmp.detectCmp();
3503
- this.config.performanceLoggingEnabled && performance.mark(`detectCmpEnd_${cmp.name}`);
3504
- this.config.performanceLoggingEnabled && performance.measure(`detectCmp_${cmp.name}`, `detectCmp_${cmp.name}`, `detectCmpEnd_${cmp.name}`);
3505
3509
  if (result) {
3506
3510
  logsConfig.lifecycle && console.log(`Found CMP: ${cmp.name} ${window.location.href}`);
3507
3511
  this.sendContentMessage({
@@ -3533,8 +3537,12 @@
3533
3537
  }
3534
3538
  this.detectHeuristics();
3535
3539
  if (foundCMPs.length === 0 && retries > 0) {
3540
+ const waitFor2 = [this.domActions.wait(500)];
3541
+ if (this.state.findCmpAttempts > 1) {
3542
+ waitFor2.push(mutationObserver);
3543
+ }
3536
3544
  try {
3537
- await Promise.all([this.domActions.wait(500), mutationObserver]);
3545
+ await Promise.all(waitFor2);
3538
3546
  } catch (e) {
3539
3547
  return [];
3540
3548
  }
@@ -3829,17 +3837,6 @@
3829
3837
  parseDeclarativeRules: getRoundedPerformanceEntries("parseDeclarativeRules")
3830
3838
  };
3831
3839
  }
3832
- measureDetailedRulePerformance() {
3833
- const cmpPerformance = performance.getEntriesByType("measure").filter((m) => m.name.startsWith("detectCmp_")).reduce((acc, m) => {
3834
- const k = m.name.slice(10);
3835
- if (!acc[k]) {
3836
- acc[k] = 0;
3837
- }
3838
- acc[k] += m.duration;
3839
- return acc;
3840
- }, /* @__PURE__ */ Object.create(null));
3841
- return cmpPerformance;
3842
- }
3843
3840
  };
3844
3841
 
3845
3842
  // addon/content.ts
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Autoconsent",
4
- "version": "2026.6.6",
4
+ "version": "2026.6.10",
5
5
  "background": {
6
6
  "service_worker": "background.bundle.js"
7
7
  },
@@ -539,7 +539,8 @@
539
539
  messages: false,
540
540
  waits: false
541
541
  },
542
- performanceLoggingEnabled: false
542
+ performanceLoggingEnabled: false,
543
+ heuristicPopupSearchTimeout: 100
543
544
  };
544
545
  const updatedConfig = copyObject(defaultConfig);
545
546
  for (const key of Object.keys(defaultConfig)) {
@@ -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],
@@ -3766,10 +3773,7 @@ var AutoConsent = class {
3766
3773
  ];
3767
3774
  const runDetectCmp = async (cmp) => {
3768
3775
  try {
3769
- this.config.performanceLoggingEnabled && performance.mark(`detectCmp_${cmp.name}`);
3770
3776
  const result = await cmp.detectCmp();
3771
- this.config.performanceLoggingEnabled && performance.mark(`detectCmpEnd_${cmp.name}`);
3772
- this.config.performanceLoggingEnabled && performance.measure(`detectCmp_${cmp.name}`, `detectCmp_${cmp.name}`, `detectCmpEnd_${cmp.name}`);
3773
3777
  if (result) {
3774
3778
  logsConfig.lifecycle && console.log(`Found CMP: ${cmp.name} ${window.location.href}`);
3775
3779
  this.sendContentMessage({
@@ -3801,8 +3805,12 @@ var AutoConsent = class {
3801
3805
  }
3802
3806
  this.detectHeuristics();
3803
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
+ }
3804
3812
  try {
3805
- await Promise.all([this.domActions.wait(500), mutationObserver]);
3813
+ await Promise.all(waitFor2);
3806
3814
  } catch (e) {
3807
3815
  return [];
3808
3816
  }
@@ -4097,17 +4105,6 @@ var AutoConsent = class {
4097
4105
  parseDeclarativeRules: getRoundedPerformanceEntries("parseDeclarativeRules")
4098
4106
  };
4099
4107
  }
4100
- measureDetailedRulePerformance() {
4101
- const cmpPerformance = performance.getEntriesByType("measure").filter((m) => m.name.startsWith("detectCmp_")).reduce((acc, m) => {
4102
- const k = m.name.slice(10);
4103
- if (!acc[k]) {
4104
- acc[k] = 0;
4105
- }
4106
- acc[k] += m.duration;
4107
- return acc;
4108
- }, /* @__PURE__ */ Object.create(null));
4109
- return cmpPerformance;
4110
- }
4111
4108
  };
4112
4109
  _config = new WeakMap();
4113
4110
  // Annotate the CommonJS export names for ESM import in node:
@@ -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],
@@ -3736,10 +3743,7 @@ var AutoConsent = class {
3736
3743
  ];
3737
3744
  const runDetectCmp = async (cmp) => {
3738
3745
  try {
3739
- this.config.performanceLoggingEnabled && performance.mark(`detectCmp_${cmp.name}`);
3740
3746
  const result = await cmp.detectCmp();
3741
- this.config.performanceLoggingEnabled && performance.mark(`detectCmpEnd_${cmp.name}`);
3742
- this.config.performanceLoggingEnabled && performance.measure(`detectCmp_${cmp.name}`, `detectCmp_${cmp.name}`, `detectCmpEnd_${cmp.name}`);
3743
3747
  if (result) {
3744
3748
  logsConfig.lifecycle && console.log(`Found CMP: ${cmp.name} ${window.location.href}`);
3745
3749
  this.sendContentMessage({
@@ -3771,8 +3775,12 @@ var AutoConsent = class {
3771
3775
  }
3772
3776
  this.detectHeuristics();
3773
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
+ }
3774
3782
  try {
3775
- await Promise.all([this.domActions.wait(500), mutationObserver]);
3783
+ await Promise.all(waitFor2);
3776
3784
  } catch (e) {
3777
3785
  return [];
3778
3786
  }
@@ -4067,17 +4075,6 @@ var AutoConsent = class {
4067
4075
  parseDeclarativeRules: getRoundedPerformanceEntries("parseDeclarativeRules")
4068
4076
  };
4069
4077
  }
4070
- measureDetailedRulePerformance() {
4071
- const cmpPerformance = performance.getEntriesByType("measure").filter((m) => m.name.startsWith("detectCmp_")).reduce((acc, m) => {
4072
- const k = m.name.slice(10);
4073
- if (!acc[k]) {
4074
- acc[k] = 0;
4075
- }
4076
- acc[k] += m.duration;
4077
- return acc;
4078
- }, /* @__PURE__ */ Object.create(null));
4079
- return cmpPerformance;
4080
- }
4081
4078
  };
4082
4079
  _config = new WeakMap();
4083
4080
  export {
@@ -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],
@@ -14764,10 +14771,7 @@ var AutoConsent = class {
14764
14771
  ];
14765
14772
  const runDetectCmp = async (cmp) => {
14766
14773
  try {
14767
- this.config.performanceLoggingEnabled && performance.mark(`detectCmp_${cmp.name}`);
14768
14774
  const result = await cmp.detectCmp();
14769
- this.config.performanceLoggingEnabled && performance.mark(`detectCmpEnd_${cmp.name}`);
14770
- this.config.performanceLoggingEnabled && performance.measure(`detectCmp_${cmp.name}`, `detectCmp_${cmp.name}`, `detectCmpEnd_${cmp.name}`);
14771
14775
  if (result) {
14772
14776
  logsConfig.lifecycle && console.log(`Found CMP: ${cmp.name} ${window.location.href}`);
14773
14777
  this.sendContentMessage({
@@ -14799,8 +14803,12 @@ var AutoConsent = class {
14799
14803
  }
14800
14804
  this.detectHeuristics();
14801
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
+ }
14802
14810
  try {
14803
- await Promise.all([this.domActions.wait(500), mutationObserver]);
14811
+ await Promise.all(waitFor2);
14804
14812
  } catch (e) {
14805
14813
  return [];
14806
14814
  }
@@ -15095,17 +15103,6 @@ var AutoConsent = class {
15095
15103
  parseDeclarativeRules: getRoundedPerformanceEntries("parseDeclarativeRules")
15096
15104
  };
15097
15105
  }
15098
- measureDetailedRulePerformance() {
15099
- const cmpPerformance = performance.getEntriesByType("measure").filter((m) => m.name.startsWith("detectCmp_")).reduce((acc, m) => {
15100
- const k = m.name.slice(10);
15101
- if (!acc[k]) {
15102
- acc[k] = 0;
15103
- }
15104
- acc[k] += m.duration;
15105
- return acc;
15106
- }, /* @__PURE__ */ Object.create(null));
15107
- return cmpPerformance;
15108
- }
15109
15106
  };
15110
15107
  _config = new WeakMap();
15111
15108
 
@@ -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],
@@ -14698,10 +14705,7 @@ var AutoConsent = class {
14698
14705
  ];
14699
14706
  const runDetectCmp = async (cmp) => {
14700
14707
  try {
14701
- this.config.performanceLoggingEnabled && performance.mark(`detectCmp_${cmp.name}`);
14702
14708
  const result = await cmp.detectCmp();
14703
- this.config.performanceLoggingEnabled && performance.mark(`detectCmpEnd_${cmp.name}`);
14704
- this.config.performanceLoggingEnabled && performance.measure(`detectCmp_${cmp.name}`, `detectCmp_${cmp.name}`, `detectCmpEnd_${cmp.name}`);
14705
14709
  if (result) {
14706
14710
  logsConfig.lifecycle && console.log(`Found CMP: ${cmp.name} ${window.location.href}`);
14707
14711
  this.sendContentMessage({
@@ -14733,8 +14737,12 @@ var AutoConsent = class {
14733
14737
  }
14734
14738
  this.detectHeuristics();
14735
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
+ }
14736
14744
  try {
14737
- await Promise.all([this.domActions.wait(500), mutationObserver]);
14745
+ await Promise.all(waitFor2);
14738
14746
  } catch (e) {
14739
14747
  return [];
14740
14748
  }
@@ -15029,17 +15037,6 @@ var AutoConsent = class {
15029
15037
  parseDeclarativeRules: getRoundedPerformanceEntries("parseDeclarativeRules")
15030
15038
  };
15031
15039
  }
15032
- measureDetailedRulePerformance() {
15033
- const cmpPerformance = performance.getEntriesByType("measure").filter((m) => m.name.startsWith("detectCmp_")).reduce((acc, m) => {
15034
- const k = m.name.slice(10);
15035
- if (!acc[k]) {
15036
- acc[k] = 0;
15037
- }
15038
- acc[k] += m.duration;
15039
- return acc;
15040
- }, /* @__PURE__ */ Object.create(null));
15041
- return cmpPerformance;
15042
- }
15043
15040
  };
15044
15041
  _config = new WeakMap();
15045
15042
 
@@ -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],
@@ -3506,10 +3513,7 @@
3506
3513
  ];
3507
3514
  const runDetectCmp = async (cmp) => {
3508
3515
  try {
3509
- this.config.performanceLoggingEnabled && performance.mark(`detectCmp_${cmp.name}`);
3510
3516
  const result = await cmp.detectCmp();
3511
- this.config.performanceLoggingEnabled && performance.mark(`detectCmpEnd_${cmp.name}`);
3512
- this.config.performanceLoggingEnabled && performance.measure(`detectCmp_${cmp.name}`, `detectCmp_${cmp.name}`, `detectCmpEnd_${cmp.name}`);
3513
3517
  if (result) {
3514
3518
  logsConfig.lifecycle && console.log(`Found CMP: ${cmp.name} ${window.location.href}`);
3515
3519
  this.sendContentMessage({
@@ -3541,8 +3545,12 @@
3541
3545
  }
3542
3546
  this.detectHeuristics();
3543
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
+ }
3544
3552
  try {
3545
- await Promise.all([this.domActions.wait(500), mutationObserver]);
3553
+ await Promise.all(waitFor2);
3546
3554
  } catch (e) {
3547
3555
  return [];
3548
3556
  }
@@ -3837,17 +3845,6 @@
3837
3845
  parseDeclarativeRules: getRoundedPerformanceEntries("parseDeclarativeRules")
3838
3846
  };
3839
3847
  }
3840
- measureDetailedRulePerformance() {
3841
- const cmpPerformance = performance.getEntriesByType("measure").filter((m) => m.name.startsWith("detectCmp_")).reduce((acc, m) => {
3842
- const k = m.name.slice(10);
3843
- if (!acc[k]) {
3844
- acc[k] = 0;
3845
- }
3846
- acc[k] += m.duration;
3847
- return acc;
3848
- }, /* @__PURE__ */ Object.create(null));
3849
- return cmpPerformance;
3850
- }
3851
3848
  };
3852
3849
  _config = new WeakMap();
3853
3850
 
@@ -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 = {
@@ -65,5 +65,4 @@ export default class AutoConsent {
65
65
  findCmpHeuristic: number[];
66
66
  parseDeclarativeRules: number[];
67
67
  };
68
- measureDetailedRulePerformance(): Record<string, number>;
69
68
  }
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],
@@ -304,11 +305,7 @@ export default class AutoConsent {
304
305
  ];
305
306
  const runDetectCmp = async (cmp: AutoCMP) => {
306
307
  try {
307
- this.config.performanceLoggingEnabled && performance.mark(`detectCmp_${cmp.name}`);
308
308
  const result = await cmp.detectCmp();
309
- this.config.performanceLoggingEnabled && performance.mark(`detectCmpEnd_${cmp.name}`);
310
- this.config.performanceLoggingEnabled &&
311
- performance.measure(`detectCmp_${cmp.name}`, `detectCmp_${cmp.name}`, `detectCmpEnd_${cmp.name}`);
312
309
  if (result) {
313
310
  logsConfig.lifecycle && console.log(`Found CMP: ${cmp.name} ${window.location.href}`);
314
311
  this.sendContentMessage({
@@ -351,8 +348,12 @@ export default class AutoConsent {
351
348
  // if we didn't find a CMP, try again
352
349
  // We wait 500ms, and also for some kind of dom mutation to happen before
353
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
+ }
354
355
  try {
355
- await Promise.all([this.domActions.wait(500), mutationObserver]);
356
+ await Promise.all(waitFor);
356
357
  } catch (e) {
357
358
  // timeout waiting for mutation - break out of detection
358
359
  return [];
@@ -701,19 +702,4 @@ export default class AutoConsent {
701
702
  parseDeclarativeRules: getRoundedPerformanceEntries('parseDeclarativeRules'),
702
703
  };
703
704
  }
704
-
705
- measureDetailedRulePerformance() {
706
- const cmpPerformance: Record<string, number> = performance
707
- .getEntriesByType('measure')
708
- .filter((m) => m.name.startsWith('detectCmp_'))
709
- .reduce((acc, m) => {
710
- const k = m.name.slice(10);
711
- if (!acc[k]) {
712
- acc[k] = 0;
713
- }
714
- acc[k] += m.duration;
715
- return acc;
716
- }, Object.create(null));
717
- return cmpPerformance;
718
- }
719
705
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckduckgo/autoconsent",
3
- "version": "14.92.0",
3
+ "version": "14.94.0",
4
4
  "description": "",
5
5
  "types": "./dist/types/web.d.ts",
6
6
  "exports": {
@@ -156,7 +156,9 @@ describe('Autoconsent.findCmp', () => {
156
156
  optIn: [],
157
157
  optOut: [],
158
158
  });
159
- const found = await autoconsent.findCmp(0);
159
+ // force findCmpAttempts to 1 so heuristic CMP is run on the first attempt
160
+ autoconsent.state.findCmpAttempts = 1;
161
+ const found = await autoconsent.findCmp(1);
160
162
 
161
163
  expect(found).to.have.length(1);
162
164
  expect(found[0].name).to.equal('HEURISTIC');