@duckduckgo/autoconsent 14.97.0 → 15.1.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 (55) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/build.sh +3 -0
  3. package/dist/addon-firefox/background.bundle.js +10 -1
  4. package/dist/addon-firefox/compact-rules.json +1 -1
  5. package/dist/addon-firefox/content.bundle.js +157 -40
  6. package/dist/addon-firefox/manifest.json +1 -1
  7. package/dist/addon-firefox/popup.bundle.js +190 -14
  8. package/dist/addon-firefox/popup.html +93 -8
  9. package/dist/addon-firefox/rule-index.json +1 -0
  10. package/dist/addon-firefox/rules.json +1 -1
  11. package/dist/addon-mv3/background.bundle.js +10 -1
  12. package/dist/addon-mv3/compact-rules.json +1 -1
  13. package/dist/addon-mv3/content.bundle.js +157 -40
  14. package/dist/addon-mv3/manifest.json +1 -1
  15. package/dist/addon-mv3/popup.bundle.js +190 -14
  16. package/dist/addon-mv3/popup.html +93 -8
  17. package/dist/addon-mv3/rule-index.json +1 -0
  18. package/dist/addon-mv3/rules.json +1 -1
  19. package/dist/autoconsent.cjs.js +157 -40
  20. package/dist/autoconsent.esm.js +157 -40
  21. package/dist/autoconsent.extra.cjs.js +157 -40
  22. package/dist/autoconsent.extra.esm.js +157 -40
  23. package/dist/autoconsent.playwright.js +157 -40
  24. package/dist/autoconsent.standalone.js +4138 -0
  25. package/dist/types/cmps/base.d.ts +4 -2
  26. package/dist/types/cmps/trustarc-top.d.ts +1 -1
  27. package/dist/types/eval-handler.d.ts +3 -3
  28. package/dist/types/heuristic-patterns.d.ts +3 -0
  29. package/dist/types/heuristics.d.ts +4 -7
  30. package/dist/types/types.d.ts +18 -2
  31. package/lib/cmps/base.ts +20 -7
  32. package/lib/cmps/conversant.ts +7 -4
  33. package/lib/cmps/trustarc-top.ts +1 -1
  34. package/lib/cmps/uniconsent.ts +4 -4
  35. package/lib/eval-handler.ts +6 -3
  36. package/lib/heuristic-patterns.ts +68 -1
  37. package/lib/heuristics.ts +76 -27
  38. package/lib/types.ts +20 -2
  39. package/lib/utils.ts +13 -6
  40. package/lib/web.ts +3 -1
  41. package/package.json +1 -1
  42. package/rules/autoconsent/twitch.json +1 -1
  43. package/rules/build.ts +4 -0
  44. package/rules/compact-rules.json +1 -1
  45. package/rules/rule-index-builder.ts +63 -0
  46. package/rules/rule-index.json +1 -0
  47. package/rules/rules.json +1 -1
  48. package/standalone/content.ts +103 -0
  49. package/tests-wtr/heuristics/get-actionable-popups.html +16 -0
  50. package/tests-wtr/heuristics/get-actionable-popups.ts +85 -6
  51. package/tests-wtr/heuristics/heuristic-cmp.html +69 -0
  52. package/tests-wtr/heuristics/heuristic-cmp.ts +178 -0
  53. package/tests-wtr/heuristics/heuristics-utils.test.ts +96 -28
  54. package/tests-wtr/lifecycle/find-cmp.html +14 -0
  55. package/tests-wtr/lifecycle/find-cmp.ts +44 -2
@@ -435,6 +435,14 @@ async function evalAction(config) {
435
435
  });
436
436
  }
437
437
 
438
+ // lib/types.ts
439
+ var PopupHandlingModes = {
440
+ None: -1,
441
+ Reject: 0,
442
+ Tier1: 1,
443
+ Tier2: 2
444
+ };
445
+
438
446
  // lib/random.ts
439
447
  function getRandomID() {
440
448
  if (crypto && typeof crypto.randomUUID !== "undefined") {
@@ -461,6 +469,9 @@ var evalState = {
461
469
  sendContentMessage: null
462
470
  };
463
471
  function requestEval(code, snippetId) {
472
+ if (!evalState.sendContentMessage) {
473
+ return Promise.reject(new Error("AutoConsent is not initialized yet"));
474
+ }
464
475
  const id = getRandomID();
465
476
  evalState.sendContentMessage({
466
477
  type: "eval",
@@ -744,7 +755,8 @@ function normalizeConfig(providedConfig) {
744
755
  waits: false
745
756
  },
746
757
  performanceLoggingEnabled: false,
747
- heuristicPopupSearchTimeout: 100
758
+ heuristicPopupSearchTimeout: 100,
759
+ heuristicMode: PopupHandlingModes.Reject
748
760
  };
749
761
  const updatedConfig = copyObject(defaultConfig);
750
762
  for (const key of Object.keys(defaultConfig)) {
@@ -762,12 +774,13 @@ function scheduleWhenIdle(callback, timeout = 500) {
762
774
  }
763
775
  }
764
776
  function highlightNode(node) {
777
+ const highlightedNode = node;
765
778
  if (!node.style) return;
766
- if (node.__oldStyles !== void 0) {
779
+ if (highlightedNode.__oldStyles !== void 0) {
767
780
  return;
768
781
  }
769
782
  if (node.hasAttribute("style")) {
770
- node.__oldStyles = node.style.cssText;
783
+ highlightedNode.__oldStyles = node.style.cssText;
771
784
  }
772
785
  node.style.animation = "pulsate .5s infinite";
773
786
  node.style.outline = "solid red";
@@ -795,10 +808,11 @@ function highlightNode(node) {
795
808
  document.head.appendChild(styleTag);
796
809
  }
797
810
  function unhighlightNode(node) {
811
+ const highlightedNode = node;
798
812
  if (!node.style || !node.hasAttribute("style")) return;
799
- if (node.__oldStyles !== void 0) {
800
- node.style.cssText = node.__oldStyles;
801
- delete node.__oldStyles;
813
+ if (highlightedNode.__oldStyles !== void 0) {
814
+ node.style.cssText = highlightedNode.__oldStyles;
815
+ delete highlightedNode.__oldStyles;
802
816
  } else {
803
817
  node.removeAttribute("style");
804
818
  }
@@ -911,12 +925,17 @@ var REJECT_PATTERNS_ENGLISH = [
911
925
  /^\s*(use|accept|allow|continue\s+with)?\s*only\s*(strictly)?\s*(necessary|essentials?|required)?\s*(cookies)?\s*$/is,
912
926
  // e.g. "do not sell or share my personal information", "do not sell my personal information"
913
927
  // often used in CCPA
914
- /^\s*do\s+not\s+sell(\s+or\s+share)?\s*my\s*personal\s*information\s*$/is,
928
+ /^\s*do\s+not\s+sell(\s+or\s+share)?\s*my\s*personal\s*info(rmation)?\s*$/is,
915
929
  "allow selection",
916
- "disagree and close"
930
+ "disagree and close",
917
931
  // These are impactful, but look error-prone
932
+ // // e.g. "disagree"
933
+ /^(i)?\s*disagree\s*(and\s+close)?$/i,
918
934
  // // e.g. "i do not agree"
919
935
  // /^\s*(i\s+)?do\s+not\s+agree\s*$/i,
936
+ "no",
937
+ /^no,? thanks$/is,
938
+ /^opt[ -]out$/is
920
939
  ];
921
940
  var REJECT_PATTERNS_DUTCH = [
922
941
  "weigeren",
@@ -1492,6 +1511,64 @@ var NEVER_MATCH_PATTERNS = [
1492
1511
  /sostienici/is,
1493
1512
  /suscribir/is
1494
1513
  ];
1514
+ var SETTINGS_PATTERNS = [
1515
+ "settings",
1516
+ "preferences",
1517
+ /customi(s|z)e/is,
1518
+ "show details",
1519
+ "more options",
1520
+ /(manage|configure) (my|your) (preferences|choices|cookies)/is,
1521
+ "manage choices",
1522
+ /(cookie )?preference center/is,
1523
+ "change settings",
1524
+ "configure",
1525
+ "change my preferences",
1526
+ "cookie manager",
1527
+ "cookie preference",
1528
+ "let me choose",
1529
+ "cookieconsent preferences",
1530
+ /privacy choices/is,
1531
+ /(privacy|cookie|custom) settings/is,
1532
+ /cookies? (settings|preferences|setting)/is,
1533
+ /(manage|customize|customise|opt-out|edit).*(cookies|preferences|settings|options)/is,
1534
+ "cookie consent options",
1535
+ "privacy controls",
1536
+ "show purposes",
1537
+ // German
1538
+ "einstellungen"
1539
+ ];
1540
+ var ACCEPT_PATTERNS = [
1541
+ /^(accept|allow)( all)?( cookies)?$/is,
1542
+ /i (accept|allow)( all)?/is,
1543
+ "yes",
1544
+ /^(i )?agree$/is,
1545
+ "continue with all",
1546
+ "accept and continue",
1547
+ /accept all above/is,
1548
+ /^(accept|agree) and close/is,
1549
+ "accept continue",
1550
+ "agree proceed",
1551
+ "allow and continue",
1552
+ "close and accept",
1553
+ /accept all$/is,
1554
+ "im ok with that",
1555
+ "accept optional cookies",
1556
+ /^alle (cookies )?akzeptieren$/is
1557
+ ];
1558
+ var ACKNOWLEDGE_PATTERNS = [
1559
+ "ok",
1560
+ "close",
1561
+ "continue",
1562
+ "x",
1563
+ /^got it!?$/,
1564
+ "i understand",
1565
+ "dismiss",
1566
+ "okay",
1567
+ "acknowledge",
1568
+ /^close (banner|cookie notification)$/is,
1569
+ /understood$/is,
1570
+ "confirm my choices"
1571
+ ];
1495
1572
 
1496
1573
  // lib/heuristics.ts
1497
1574
  var BUTTON_LIKE_ELEMENT_SELECTOR = 'button, input[type="button"], input[type="submit"], a, [role="button"], [class*="button"]';
@@ -1510,48 +1587,61 @@ function checkHeuristicPatterns(allText, detectPatterns = DETECT_PATTERNS) {
1510
1587
  }
1511
1588
  return { patterns, snippets: snippets2 };
1512
1589
  }
1513
- function getActionablePopups(timeout = POPUP_SEARCH_MAX_TIME) {
1590
+ function getActionablePopups(mode = PopupHandlingModes.Reject, timeout = POPUP_SEARCH_MAX_TIME) {
1514
1591
  const popups = getPotentialPopups(timeout);
1515
1592
  const result = popups.reduce((acc, popup) => {
1516
1593
  const popupText = popup.text?.trim();
1517
1594
  if (popupText) {
1518
1595
  const { patterns } = checkHeuristicPatterns(popupText);
1519
1596
  if (patterns.length > 0) {
1520
- const { rejectButtons, otherButtons } = classifyButtons(popup.buttons);
1521
- if (rejectButtons.length > 0) {
1522
- acc.push({
1523
- ...popup,
1524
- rejectButtons,
1525
- otherButtons
1526
- });
1527
- }
1597
+ classifyButtons(popup.buttons);
1598
+ popup.regexClassification = classifyPopup(popup.buttons);
1599
+ acc.push({
1600
+ ...popup
1601
+ });
1528
1602
  }
1529
1603
  }
1530
1604
  return acc;
1531
1605
  }, []);
1532
- return result;
1606
+ return result.filter(
1607
+ (popup) => popup.regexClassification !== void 0 && popup.regexClassification !== PopupHandlingModes.None && popup.regexClassification <= mode
1608
+ ).sort((a, b) => (a.regexClassification ?? 0) - (b.regexClassification ?? 0));
1533
1609
  }
1534
1610
  function classifyButtons(buttons) {
1535
- const rejectButtons = [];
1536
- const otherButtons = [];
1537
1611
  for (const button of buttons) {
1538
- if (isRejectButton(button.text)) {
1539
- rejectButtons.push(button);
1540
- } else {
1541
- otherButtons.push(button);
1542
- }
1612
+ button.regexClassification = classifyButtonTextRegex(button.text);
1543
1613
  }
1544
- return {
1545
- rejectButtons,
1546
- otherButtons
1547
- };
1548
1614
  }
1549
- function isRejectButton(buttonText, rejectPatterns = REJECT_PATTERNS, neverMatchPatterns = NEVER_MATCH_PATTERNS) {
1615
+ function classifyPopup(buttons) {
1616
+ const { reject, settings, accept, acknowledge } = buttons.reduce(
1617
+ (acc, button) => {
1618
+ if (button.regexClassification && button.regexClassification !== "other") {
1619
+ acc[button.regexClassification]++;
1620
+ }
1621
+ return acc;
1622
+ },
1623
+ { reject: 0, settings: 0, accept: 0, acknowledge: 0 }
1624
+ );
1625
+ if (reject > 0) {
1626
+ return PopupHandlingModes.Reject;
1627
+ }
1628
+ if (settings > 0) {
1629
+ return PopupHandlingModes.None;
1630
+ }
1631
+ if (acknowledge > 0) {
1632
+ return PopupHandlingModes.Tier1;
1633
+ }
1634
+ if (accept > 0) {
1635
+ return accept === 1 ? PopupHandlingModes.Tier2 : PopupHandlingModes.None;
1636
+ }
1637
+ return PopupHandlingModes.None;
1638
+ }
1639
+ function testButtonMatches(buttonText, matchPatterns, neverMatchPatterns) {
1550
1640
  if (!buttonText) {
1551
1641
  return false;
1552
1642
  }
1553
1643
  const cleanedButtonText = cleanButtonText(buttonText);
1554
- return !neverMatchPatterns.some((p) => p.test(cleanedButtonText)) && rejectPatterns.some((p) => p instanceof RegExp && p.test(cleanedButtonText) || p === cleanedButtonText);
1644
+ return !neverMatchPatterns.some((p) => p instanceof RegExp && p.test(cleanedButtonText) || p === cleanedButtonText) && matchPatterns.some((p) => p instanceof RegExp && p.test(cleanedButtonText) || p === cleanedButtonText);
1555
1645
  }
1556
1646
  function cleanButtonText(buttonText) {
1557
1647
  let result = buttonText.toLowerCase();
@@ -1565,6 +1655,21 @@ function cleanButtonText(buttonText) {
1565
1655
  result = result.trim();
1566
1656
  return result;
1567
1657
  }
1658
+ function classifyButtonTextRegex(buttonText) {
1659
+ if (testButtonMatches(buttonText, REJECT_PATTERNS, NEVER_MATCH_PATTERNS)) {
1660
+ return "reject";
1661
+ }
1662
+ if (testButtonMatches(buttonText, SETTINGS_PATTERNS, NEVER_MATCH_PATTERNS)) {
1663
+ return "settings";
1664
+ }
1665
+ if (testButtonMatches(buttonText, ACCEPT_PATTERNS, NEVER_MATCH_PATTERNS)) {
1666
+ return "accept";
1667
+ }
1668
+ if (testButtonMatches(buttonText, ACKNOWLEDGE_PATTERNS, NEVER_MATCH_PATTERNS)) {
1669
+ return "acknowledge";
1670
+ }
1671
+ return "other";
1672
+ }
1568
1673
  function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME) {
1569
1674
  const isFramed = !isTopFrame();
1570
1675
  if (isFramed && window.parent && window.parent !== window.top) {
@@ -2010,7 +2115,7 @@ var AutoConsentCMP = class extends AutoConsentCMPBase {
2010
2115
  }
2011
2116
  };
2012
2117
  var AutoConsentHeuristicCMP = class extends AutoConsentCMPBase {
2013
- constructor(autoconsentInstance) {
2118
+ constructor(autoconsentInstance, mode = PopupHandlingModes.Reject) {
2014
2119
  super(autoconsentInstance);
2015
2120
  this.popups = [];
2016
2121
  this.name = "HEURISTIC";
@@ -2019,6 +2124,7 @@ var AutoConsentHeuristicCMP = class extends AutoConsentCMPBase {
2019
2124
  frame: false
2020
2125
  // do not run in iframes for security reasons
2021
2126
  };
2127
+ this.mode = mode;
2022
2128
  }
2023
2129
  get hasSelfTest() {
2024
2130
  return true;
@@ -2032,25 +2138,33 @@ var AutoConsentHeuristicCMP = class extends AutoConsentCMPBase {
2032
2138
  async detectCmp() {
2033
2139
  await new Promise((resolve) => setTimeout(resolve, 0));
2034
2140
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorStart");
2035
- this.popups = getActionablePopups(this.autoconsent.config.heuristicPopupSearchTimeout);
2141
+ this.popups = getActionablePopups(this.mode, this.autoconsent.config.heuristicPopupSearchTimeout);
2036
2142
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorEnd");
2037
2143
  this.autoconsent.config.performanceLoggingEnabled && performance.measure("heuristicDetector", "heuristicDetectorStart", "heuristicDetectorEnd");
2038
2144
  if (this.popups.length > 0) {
2145
+ this.name = `HEURISTIC-TIER${this.popups[0].regexClassification}`;
2039
2146
  return Promise.resolve(true);
2040
2147
  }
2041
2148
  return Promise.resolve(false);
2042
2149
  }
2043
2150
  async detectPopup() {
2044
2151
  if (this.popups.length > 0) {
2045
- if (this.popups.length > 1 || this.popups[0].rejectButtons && this.popups[0].rejectButtons.length > 1) {
2046
- this.autoconsent.config.logs.errors && console.warn("Heuristic found multiple reject buttons");
2152
+ if (this.popups.length > 1) {
2153
+ this.autoconsent.config.logs.errors && console.warn("Heuristic found multiple popups");
2047
2154
  }
2048
2155
  return true;
2049
2156
  }
2050
2157
  return false;
2051
2158
  }
2159
+ getTargetButton() {
2160
+ const popup = this.popups[0];
2161
+ const level = popup.regexClassification;
2162
+ const buttons = popup.buttons;
2163
+ const targetButtonType = level === PopupHandlingModes.Reject ? "reject" : level === PopupHandlingModes.Tier1 ? "acknowledge" : "accept";
2164
+ return buttons.find((button) => button.regexClassification === targetButtonType);
2165
+ }
2052
2166
  optOut() {
2053
- const button = this.popups[0]?.rejectButtons?.[0];
2167
+ const button = this.getTargetButton();
2054
2168
  if (button) {
2055
2169
  return this.clickElement(button.element);
2056
2170
  }
@@ -2063,7 +2177,7 @@ var AutoConsentHeuristicCMP = class extends AutoConsentCMPBase {
2063
2177
  throw new Error("Not Implemented");
2064
2178
  }
2065
2179
  async test() {
2066
- const button = this.popups[0].rejectButtons?.[0];
2180
+ const button = this.getTargetButton();
2067
2181
  if (button) {
2068
2182
  await this.wait(500);
2069
2183
  return !isElementVisible(button.element);
@@ -2744,7 +2858,7 @@ var Uniconsent = class extends AutoConsentCMPBase {
2744
2858
  async optOut() {
2745
2859
  await this.waitForElement(".unic button", 1e3);
2746
2860
  document.querySelectorAll(".unic button").forEach((button) => {
2747
- const text = button.textContent;
2861
+ const text = button.textContent || "";
2748
2862
  if (text.includes("Manage Options") || text.includes("Optionen verwalten")) {
2749
2863
  button.click();
2750
2864
  }
@@ -2757,7 +2871,7 @@ var Uniconsent = class extends AutoConsentCMPBase {
2757
2871
  }
2758
2872
  });
2759
2873
  for (const b of document.querySelectorAll(".unic button")) {
2760
- const text = b.textContent;
2874
+ const text = b.textContent || "";
2761
2875
  for (const pattern of ["Confirm Choices", "Save Choices", "Auswahl speichern"]) {
2762
2876
  if (text.includes(pattern)) {
2763
2877
  b.click();
@@ -2814,6 +2928,9 @@ var Conversant = class extends AutoConsentCMPBase {
2814
2928
  item.querySelector(".cmp-accordion-item-title").click();
2815
2929
  await waitFor(() => !!item.querySelector(".cmp-accordion-item-content.cmp-active"), 10, 50);
2816
2930
  const content = item.querySelector(".cmp-accordion-item-content.cmp-active");
2931
+ if (!content) {
2932
+ return false;
2933
+ }
2817
2934
  content.querySelectorAll(".cmp-toggle-actions .cmp-toggle-deny:not(.cmp-toggle-deny--active)").forEach((e) => e.click());
2818
2935
  content.querySelectorAll(".cmp-toggle-actions .cmp-toggle-checkbox:not(.cmp-toggle-checkbox--active)").forEach((e) => e.click());
2819
2936
  }
@@ -3765,7 +3882,7 @@ var AutoConsent = class {
3765
3882
  }
3766
3883
  }
3767
3884
  });
3768
- const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this)] : [];
3885
+ const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this, this.config.heuristicMode)] : [];
3769
3886
  const rulesPriorityStages = [
3770
3887
  ["site-specific", siteSpecificRules],
3771
3888
  ["generic", genericRules],