@duckduckgo/autoconsent 14.97.0 → 15.0.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/addon-firefox/background.bundle.js +10 -1
  3. package/dist/addon-firefox/compact-rules.json +1 -1
  4. package/dist/addon-firefox/content.bundle.js +142 -33
  5. package/dist/addon-firefox/manifest.json +1 -1
  6. package/dist/addon-firefox/popup.bundle.js +10 -1
  7. package/dist/addon-firefox/rules.json +1 -1
  8. package/dist/addon-mv3/background.bundle.js +10 -1
  9. package/dist/addon-mv3/compact-rules.json +1 -1
  10. package/dist/addon-mv3/content.bundle.js +142 -33
  11. package/dist/addon-mv3/manifest.json +1 -1
  12. package/dist/addon-mv3/popup.bundle.js +10 -1
  13. package/dist/addon-mv3/rules.json +1 -1
  14. package/dist/autoconsent.cjs.js +142 -33
  15. package/dist/autoconsent.esm.js +142 -33
  16. package/dist/autoconsent.extra.cjs.js +142 -33
  17. package/dist/autoconsent.extra.esm.js +142 -33
  18. package/dist/autoconsent.playwright.js +142 -33
  19. package/dist/types/cmps/base.d.ts +4 -2
  20. package/dist/types/heuristic-patterns.d.ts +3 -0
  21. package/dist/types/heuristics.d.ts +4 -7
  22. package/dist/types/types.d.ts +18 -2
  23. package/lib/cmps/base.ts +20 -7
  24. package/lib/heuristic-patterns.ts +68 -1
  25. package/lib/heuristics.ts +76 -27
  26. package/lib/types.ts +20 -2
  27. package/lib/utils.ts +2 -1
  28. package/lib/web.ts +3 -1
  29. package/package.json +1 -1
  30. package/rules/autoconsent/twitch.json +1 -1
  31. package/rules/compact-rules.json +1 -1
  32. package/rules/rules.json +1 -1
  33. package/tests-wtr/heuristics/get-actionable-popups.html +16 -0
  34. package/tests-wtr/heuristics/get-actionable-popups.ts +85 -6
  35. package/tests-wtr/heuristics/heuristic-cmp.html +69 -0
  36. package/tests-wtr/heuristics/heuristic-cmp.ts +178 -0
  37. package/tests-wtr/heuristics/heuristics-utils.test.ts +96 -28
  38. package/tests-wtr/lifecycle/find-cmp.html +14 -0
  39. package/tests-wtr/lifecycle/find-cmp.ts +44 -2
@@ -400,6 +400,14 @@
400
400
  });
401
401
  }
402
402
 
403
+ // lib/types.ts
404
+ var PopupHandlingModes = {
405
+ None: -1,
406
+ Reject: 0,
407
+ Tier1: 1,
408
+ Tier2: 2
409
+ };
410
+
403
411
  // lib/random.ts
404
412
  function getRandomID() {
405
413
  if (crypto && typeof crypto.randomUUID !== "undefined") {
@@ -709,7 +717,8 @@
709
717
  waits: false
710
718
  },
711
719
  performanceLoggingEnabled: false,
712
- heuristicPopupSearchTimeout: 100
720
+ heuristicPopupSearchTimeout: 100,
721
+ heuristicMode: PopupHandlingModes.Reject
713
722
  };
714
723
  const updatedConfig = copyObject(defaultConfig);
715
724
  for (const key of Object.keys(defaultConfig)) {
@@ -876,12 +885,17 @@
876
885
  /^\s*(use|accept|allow|continue\s+with)?\s*only\s*(strictly)?\s*(necessary|essentials?|required)?\s*(cookies)?\s*$/is,
877
886
  // e.g. "do not sell or share my personal information", "do not sell my personal information"
878
887
  // often used in CCPA
879
- /^\s*do\s+not\s+sell(\s+or\s+share)?\s*my\s*personal\s*information\s*$/is,
888
+ /^\s*do\s+not\s+sell(\s+or\s+share)?\s*my\s*personal\s*info(rmation)?\s*$/is,
880
889
  "allow selection",
881
- "disagree and close"
890
+ "disagree and close",
882
891
  // These are impactful, but look error-prone
892
+ // // e.g. "disagree"
893
+ /^(i)?\s*disagree\s*(and\s+close)?$/i,
883
894
  // // e.g. "i do not agree"
884
895
  // /^\s*(i\s+)?do\s+not\s+agree\s*$/i,
896
+ "no",
897
+ /^no,? thanks$/is,
898
+ /^opt[ -]out$/is
885
899
  ];
886
900
  var REJECT_PATTERNS_DUTCH = [
887
901
  "weigeren",
@@ -1457,6 +1471,64 @@
1457
1471
  /sostienici/is,
1458
1472
  /suscribir/is
1459
1473
  ];
1474
+ var SETTINGS_PATTERNS = [
1475
+ "settings",
1476
+ "preferences",
1477
+ /customi(s|z)e/is,
1478
+ "show details",
1479
+ "more options",
1480
+ /(manage|configure) (my|your) (preferences|choices|cookies)/is,
1481
+ "manage choices",
1482
+ /(cookie )?preference center/is,
1483
+ "change settings",
1484
+ "configure",
1485
+ "change my preferences",
1486
+ "cookie manager",
1487
+ "cookie preference",
1488
+ "let me choose",
1489
+ "cookieconsent preferences",
1490
+ /privacy choices/is,
1491
+ /(privacy|cookie|custom) settings/is,
1492
+ /cookies? (settings|preferences|setting)/is,
1493
+ /(manage|customize|customise|opt-out|edit).*(cookies|preferences|settings|options)/is,
1494
+ "cookie consent options",
1495
+ "privacy controls",
1496
+ "show purposes",
1497
+ // German
1498
+ "einstellungen"
1499
+ ];
1500
+ var ACCEPT_PATTERNS = [
1501
+ /^(accept|allow)( all)?( cookies)?$/is,
1502
+ /i (accept|allow)( all)?/is,
1503
+ "yes",
1504
+ /^(i )?agree$/is,
1505
+ "continue with all",
1506
+ "accept and continue",
1507
+ /accept all above/is,
1508
+ /^(accept|agree) and close/is,
1509
+ "accept continue",
1510
+ "agree proceed",
1511
+ "allow and continue",
1512
+ "close and accept",
1513
+ /accept all$/is,
1514
+ "im ok with that",
1515
+ "accept optional cookies",
1516
+ /^alle (cookies )?akzeptieren$/is
1517
+ ];
1518
+ var ACKNOWLEDGE_PATTERNS = [
1519
+ "ok",
1520
+ "close",
1521
+ "continue",
1522
+ "x",
1523
+ /^got it!?$/,
1524
+ "i understand",
1525
+ "dismiss",
1526
+ "okay",
1527
+ "acknowledge",
1528
+ /^close (banner|cookie notification)$/is,
1529
+ /understood$/is,
1530
+ "confirm my choices"
1531
+ ];
1460
1532
 
1461
1533
  // lib/heuristics.ts
1462
1534
  var BUTTON_LIKE_ELEMENT_SELECTOR = 'button, input[type="button"], input[type="submit"], a, [role="button"], [class*="button"]';
@@ -1475,48 +1547,61 @@
1475
1547
  }
1476
1548
  return { patterns, snippets: snippets2 };
1477
1549
  }
1478
- function getActionablePopups(timeout = POPUP_SEARCH_MAX_TIME) {
1550
+ function getActionablePopups(mode = PopupHandlingModes.Reject, timeout = POPUP_SEARCH_MAX_TIME) {
1479
1551
  const popups = getPotentialPopups(timeout);
1480
1552
  const result = popups.reduce((acc, popup) => {
1481
1553
  const popupText = popup.text?.trim();
1482
1554
  if (popupText) {
1483
1555
  const { patterns } = checkHeuristicPatterns(popupText);
1484
1556
  if (patterns.length > 0) {
1485
- const { rejectButtons, otherButtons } = classifyButtons(popup.buttons);
1486
- if (rejectButtons.length > 0) {
1487
- acc.push({
1488
- ...popup,
1489
- rejectButtons,
1490
- otherButtons
1491
- });
1492
- }
1557
+ classifyButtons(popup.buttons);
1558
+ popup.regexClassification = classifyPopup(popup.buttons);
1559
+ acc.push({
1560
+ ...popup
1561
+ });
1493
1562
  }
1494
1563
  }
1495
1564
  return acc;
1496
1565
  }, []);
1497
- return result;
1566
+ return result.filter(
1567
+ (popup) => popup.regexClassification !== void 0 && popup.regexClassification !== PopupHandlingModes.None && popup.regexClassification <= mode
1568
+ ).sort((a, b) => (a.regexClassification ?? 0) - (b.regexClassification ?? 0));
1498
1569
  }
1499
1570
  function classifyButtons(buttons) {
1500
- const rejectButtons = [];
1501
- const otherButtons = [];
1502
1571
  for (const button of buttons) {
1503
- if (isRejectButton(button.text)) {
1504
- rejectButtons.push(button);
1505
- } else {
1506
- otherButtons.push(button);
1507
- }
1572
+ button.regexClassification = classifyButtonTextRegex(button.text);
1508
1573
  }
1509
- return {
1510
- rejectButtons,
1511
- otherButtons
1512
- };
1513
1574
  }
1514
- function isRejectButton(buttonText, rejectPatterns = REJECT_PATTERNS, neverMatchPatterns = NEVER_MATCH_PATTERNS) {
1575
+ function classifyPopup(buttons) {
1576
+ const { reject, settings, accept, acknowledge } = buttons.reduce(
1577
+ (acc, button) => {
1578
+ if (button.regexClassification && button.regexClassification !== "other") {
1579
+ acc[button.regexClassification]++;
1580
+ }
1581
+ return acc;
1582
+ },
1583
+ { reject: 0, settings: 0, accept: 0, acknowledge: 0 }
1584
+ );
1585
+ if (reject > 0) {
1586
+ return PopupHandlingModes.Reject;
1587
+ }
1588
+ if (settings > 0) {
1589
+ return PopupHandlingModes.None;
1590
+ }
1591
+ if (acknowledge > 0) {
1592
+ return PopupHandlingModes.Tier1;
1593
+ }
1594
+ if (accept > 0) {
1595
+ return accept === 1 ? PopupHandlingModes.Tier2 : PopupHandlingModes.None;
1596
+ }
1597
+ return PopupHandlingModes.None;
1598
+ }
1599
+ function testButtonMatches(buttonText, matchPatterns, neverMatchPatterns) {
1515
1600
  if (!buttonText) {
1516
1601
  return false;
1517
1602
  }
1518
1603
  const cleanedButtonText = cleanButtonText(buttonText);
1519
- return !neverMatchPatterns.some((p) => p.test(cleanedButtonText)) && rejectPatterns.some((p) => p instanceof RegExp && p.test(cleanedButtonText) || p === cleanedButtonText);
1604
+ return !neverMatchPatterns.some((p) => p instanceof RegExp && p.test(cleanedButtonText) || p === cleanedButtonText) && matchPatterns.some((p) => p instanceof RegExp && p.test(cleanedButtonText) || p === cleanedButtonText);
1520
1605
  }
1521
1606
  function cleanButtonText(buttonText) {
1522
1607
  let result = buttonText.toLowerCase();
@@ -1530,6 +1615,21 @@
1530
1615
  result = result.trim();
1531
1616
  return result;
1532
1617
  }
1618
+ function classifyButtonTextRegex(buttonText) {
1619
+ if (testButtonMatches(buttonText, REJECT_PATTERNS, NEVER_MATCH_PATTERNS)) {
1620
+ return "reject";
1621
+ }
1622
+ if (testButtonMatches(buttonText, SETTINGS_PATTERNS, NEVER_MATCH_PATTERNS)) {
1623
+ return "settings";
1624
+ }
1625
+ if (testButtonMatches(buttonText, ACCEPT_PATTERNS, NEVER_MATCH_PATTERNS)) {
1626
+ return "accept";
1627
+ }
1628
+ if (testButtonMatches(buttonText, ACKNOWLEDGE_PATTERNS, NEVER_MATCH_PATTERNS)) {
1629
+ return "acknowledge";
1630
+ }
1631
+ return "other";
1632
+ }
1533
1633
  function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME) {
1534
1634
  const isFramed = !isTopFrame();
1535
1635
  if (isFramed && window.parent && window.parent !== window.top) {
@@ -1975,7 +2075,7 @@
1975
2075
  }
1976
2076
  };
1977
2077
  var AutoConsentHeuristicCMP = class extends AutoConsentCMPBase {
1978
- constructor(autoconsentInstance) {
2078
+ constructor(autoconsentInstance, mode = PopupHandlingModes.Reject) {
1979
2079
  super(autoconsentInstance);
1980
2080
  this.popups = [];
1981
2081
  this.name = "HEURISTIC";
@@ -1984,6 +2084,7 @@
1984
2084
  frame: false
1985
2085
  // do not run in iframes for security reasons
1986
2086
  };
2087
+ this.mode = mode;
1987
2088
  }
1988
2089
  get hasSelfTest() {
1989
2090
  return true;
@@ -1997,25 +2098,33 @@
1997
2098
  async detectCmp() {
1998
2099
  await new Promise((resolve) => setTimeout(resolve, 0));
1999
2100
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorStart");
2000
- this.popups = getActionablePopups(this.autoconsent.config.heuristicPopupSearchTimeout);
2101
+ this.popups = getActionablePopups(this.mode, this.autoconsent.config.heuristicPopupSearchTimeout);
2001
2102
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorEnd");
2002
2103
  this.autoconsent.config.performanceLoggingEnabled && performance.measure("heuristicDetector", "heuristicDetectorStart", "heuristicDetectorEnd");
2003
2104
  if (this.popups.length > 0) {
2105
+ this.name = `HEURISTIC-TIER${this.popups[0].regexClassification}`;
2004
2106
  return Promise.resolve(true);
2005
2107
  }
2006
2108
  return Promise.resolve(false);
2007
2109
  }
2008
2110
  async detectPopup() {
2009
2111
  if (this.popups.length > 0) {
2010
- if (this.popups.length > 1 || this.popups[0].rejectButtons && this.popups[0].rejectButtons.length > 1) {
2011
- this.autoconsent.config.logs.errors && console.warn("Heuristic found multiple reject buttons");
2112
+ if (this.popups.length > 1) {
2113
+ this.autoconsent.config.logs.errors && console.warn("Heuristic found multiple popups");
2012
2114
  }
2013
2115
  return true;
2014
2116
  }
2015
2117
  return false;
2016
2118
  }
2119
+ getTargetButton() {
2120
+ const popup = this.popups[0];
2121
+ const level = popup.regexClassification;
2122
+ const buttons = popup.buttons;
2123
+ const targetButtonType = level === PopupHandlingModes.Reject ? "reject" : level === PopupHandlingModes.Tier1 ? "acknowledge" : "accept";
2124
+ return buttons.find((button) => button.regexClassification === targetButtonType);
2125
+ }
2017
2126
  optOut() {
2018
- const button = this.popups[0]?.rejectButtons?.[0];
2127
+ const button = this.getTargetButton();
2019
2128
  if (button) {
2020
2129
  return this.clickElement(button.element);
2021
2130
  }
@@ -2028,7 +2137,7 @@
2028
2137
  throw new Error("Not Implemented");
2029
2138
  }
2030
2139
  async test() {
2031
- const button = this.popups[0].rejectButtons?.[0];
2140
+ const button = this.getTargetButton();
2032
2141
  if (button) {
2033
2142
  await this.wait(500);
2034
2143
  return !isElementVisible(button.element);
@@ -3497,7 +3606,7 @@
3497
3606
  }
3498
3607
  }
3499
3608
  });
3500
- const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this)] : [];
3609
+ const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this, this.config.heuristicMode)] : [];
3501
3610
  const rulesPriorityStages = [
3502
3611
  ["site-specific", siteSpecificRules],
3503
3612
  ["generic", genericRules],
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Autoconsent",
4
- "version": "2026.6.18",
4
+ "version": "2026.6.19",
5
5
  "background": {
6
6
  "scripts": [
7
7
  "background.bundle.js"
@@ -761,6 +761,14 @@
761
761
  return parseImpl(url, 5, suffixLookup, options, getEmptyResult());
762
762
  }
763
763
 
764
+ // lib/types.ts
765
+ var PopupHandlingModes = {
766
+ None: -1,
767
+ Reject: 0,
768
+ Tier1: 1,
769
+ Tier2: 2
770
+ };
771
+
764
772
  // lib/utils.ts
765
773
  function copyObject(data) {
766
774
  if (globalThis.structuredClone) {
@@ -795,7 +803,8 @@
795
803
  waits: false
796
804
  },
797
805
  performanceLoggingEnabled: false,
798
- heuristicPopupSearchTimeout: 100
806
+ heuristicPopupSearchTimeout: 100,
807
+ heuristicMode: PopupHandlingModes.Reject
799
808
  };
800
809
  const updatedConfig = copyObject(defaultConfig);
801
810
  for (const key of Object.keys(defaultConfig)) {