@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
@@ -407,6 +407,14 @@
407
407
  });
408
408
  }
409
409
 
410
+ // lib/types.ts
411
+ var PopupHandlingModes = {
412
+ None: -1,
413
+ Reject: 0,
414
+ Tier1: 1,
415
+ Tier2: 2
416
+ };
417
+
410
418
  // lib/random.ts
411
419
  function getRandomID() {
412
420
  if (crypto && typeof crypto.randomUUID !== "undefined") {
@@ -433,6 +441,9 @@
433
441
  sendContentMessage: null
434
442
  };
435
443
  function requestEval(code, snippetId) {
444
+ if (!evalState.sendContentMessage) {
445
+ return Promise.reject(new Error("AutoConsent is not initialized yet"));
446
+ }
436
447
  const id = getRandomID();
437
448
  evalState.sendContentMessage({
438
449
  type: "eval",
@@ -716,7 +727,8 @@
716
727
  waits: false
717
728
  },
718
729
  performanceLoggingEnabled: false,
719
- heuristicPopupSearchTimeout: 100
730
+ heuristicPopupSearchTimeout: 100,
731
+ heuristicMode: PopupHandlingModes.Reject
720
732
  };
721
733
  const updatedConfig = copyObject(defaultConfig);
722
734
  for (const key of Object.keys(defaultConfig)) {
@@ -734,12 +746,13 @@
734
746
  }
735
747
  }
736
748
  function highlightNode(node) {
749
+ const highlightedNode = node;
737
750
  if (!node.style) return;
738
- if (node.__oldStyles !== void 0) {
751
+ if (highlightedNode.__oldStyles !== void 0) {
739
752
  return;
740
753
  }
741
754
  if (node.hasAttribute("style")) {
742
- node.__oldStyles = node.style.cssText;
755
+ highlightedNode.__oldStyles = node.style.cssText;
743
756
  }
744
757
  node.style.animation = "pulsate .5s infinite";
745
758
  node.style.outline = "solid red";
@@ -767,10 +780,11 @@
767
780
  document.head.appendChild(styleTag);
768
781
  }
769
782
  function unhighlightNode(node) {
783
+ const highlightedNode = node;
770
784
  if (!node.style || !node.hasAttribute("style")) return;
771
- if (node.__oldStyles !== void 0) {
772
- node.style.cssText = node.__oldStyles;
773
- delete node.__oldStyles;
785
+ if (highlightedNode.__oldStyles !== void 0) {
786
+ node.style.cssText = highlightedNode.__oldStyles;
787
+ delete highlightedNode.__oldStyles;
774
788
  } else {
775
789
  node.removeAttribute("style");
776
790
  }
@@ -883,12 +897,17 @@
883
897
  /^\s*(use|accept|allow|continue\s+with)?\s*only\s*(strictly)?\s*(necessary|essentials?|required)?\s*(cookies)?\s*$/is,
884
898
  // e.g. "do not sell or share my personal information", "do not sell my personal information"
885
899
  // often used in CCPA
886
- /^\s*do\s+not\s+sell(\s+or\s+share)?\s*my\s*personal\s*information\s*$/is,
900
+ /^\s*do\s+not\s+sell(\s+or\s+share)?\s*my\s*personal\s*info(rmation)?\s*$/is,
887
901
  "allow selection",
888
- "disagree and close"
902
+ "disagree and close",
889
903
  // These are impactful, but look error-prone
904
+ // // e.g. "disagree"
905
+ /^(i)?\s*disagree\s*(and\s+close)?$/i,
890
906
  // // e.g. "i do not agree"
891
907
  // /^\s*(i\s+)?do\s+not\s+agree\s*$/i,
908
+ "no",
909
+ /^no,? thanks$/is,
910
+ /^opt[ -]out$/is
892
911
  ];
893
912
  var REJECT_PATTERNS_DUTCH = [
894
913
  "weigeren",
@@ -1464,6 +1483,64 @@
1464
1483
  /sostienici/is,
1465
1484
  /suscribir/is
1466
1485
  ];
1486
+ var SETTINGS_PATTERNS = [
1487
+ "settings",
1488
+ "preferences",
1489
+ /customi(s|z)e/is,
1490
+ "show details",
1491
+ "more options",
1492
+ /(manage|configure) (my|your) (preferences|choices|cookies)/is,
1493
+ "manage choices",
1494
+ /(cookie )?preference center/is,
1495
+ "change settings",
1496
+ "configure",
1497
+ "change my preferences",
1498
+ "cookie manager",
1499
+ "cookie preference",
1500
+ "let me choose",
1501
+ "cookieconsent preferences",
1502
+ /privacy choices/is,
1503
+ /(privacy|cookie|custom) settings/is,
1504
+ /cookies? (settings|preferences|setting)/is,
1505
+ /(manage|customize|customise|opt-out|edit).*(cookies|preferences|settings|options)/is,
1506
+ "cookie consent options",
1507
+ "privacy controls",
1508
+ "show purposes",
1509
+ // German
1510
+ "einstellungen"
1511
+ ];
1512
+ var ACCEPT_PATTERNS = [
1513
+ /^(accept|allow)( all)?( cookies)?$/is,
1514
+ /i (accept|allow)( all)?/is,
1515
+ "yes",
1516
+ /^(i )?agree$/is,
1517
+ "continue with all",
1518
+ "accept and continue",
1519
+ /accept all above/is,
1520
+ /^(accept|agree) and close/is,
1521
+ "accept continue",
1522
+ "agree proceed",
1523
+ "allow and continue",
1524
+ "close and accept",
1525
+ /accept all$/is,
1526
+ "im ok with that",
1527
+ "accept optional cookies",
1528
+ /^alle (cookies )?akzeptieren$/is
1529
+ ];
1530
+ var ACKNOWLEDGE_PATTERNS = [
1531
+ "ok",
1532
+ "close",
1533
+ "continue",
1534
+ "x",
1535
+ /^got it!?$/,
1536
+ "i understand",
1537
+ "dismiss",
1538
+ "okay",
1539
+ "acknowledge",
1540
+ /^close (banner|cookie notification)$/is,
1541
+ /understood$/is,
1542
+ "confirm my choices"
1543
+ ];
1467
1544
 
1468
1545
  // lib/heuristics.ts
1469
1546
  var BUTTON_LIKE_ELEMENT_SELECTOR = 'button, input[type="button"], input[type="submit"], a, [role="button"], [class*="button"]';
@@ -1482,48 +1559,61 @@
1482
1559
  }
1483
1560
  return { patterns, snippets: snippets2 };
1484
1561
  }
1485
- function getActionablePopups(timeout = POPUP_SEARCH_MAX_TIME) {
1562
+ function getActionablePopups(mode = PopupHandlingModes.Reject, timeout = POPUP_SEARCH_MAX_TIME) {
1486
1563
  const popups = getPotentialPopups(timeout);
1487
1564
  const result = popups.reduce((acc, popup) => {
1488
1565
  const popupText = popup.text?.trim();
1489
1566
  if (popupText) {
1490
1567
  const { patterns } = checkHeuristicPatterns(popupText);
1491
1568
  if (patterns.length > 0) {
1492
- const { rejectButtons, otherButtons } = classifyButtons(popup.buttons);
1493
- if (rejectButtons.length > 0) {
1494
- acc.push({
1495
- ...popup,
1496
- rejectButtons,
1497
- otherButtons
1498
- });
1499
- }
1569
+ classifyButtons(popup.buttons);
1570
+ popup.regexClassification = classifyPopup(popup.buttons);
1571
+ acc.push({
1572
+ ...popup
1573
+ });
1500
1574
  }
1501
1575
  }
1502
1576
  return acc;
1503
1577
  }, []);
1504
- return result;
1578
+ return result.filter(
1579
+ (popup) => popup.regexClassification !== void 0 && popup.regexClassification !== PopupHandlingModes.None && popup.regexClassification <= mode
1580
+ ).sort((a, b) => (a.regexClassification ?? 0) - (b.regexClassification ?? 0));
1505
1581
  }
1506
1582
  function classifyButtons(buttons) {
1507
- const rejectButtons = [];
1508
- const otherButtons = [];
1509
1583
  for (const button of buttons) {
1510
- if (isRejectButton(button.text)) {
1511
- rejectButtons.push(button);
1512
- } else {
1513
- otherButtons.push(button);
1514
- }
1584
+ button.regexClassification = classifyButtonTextRegex(button.text);
1515
1585
  }
1516
- return {
1517
- rejectButtons,
1518
- otherButtons
1519
- };
1520
1586
  }
1521
- function isRejectButton(buttonText, rejectPatterns = REJECT_PATTERNS, neverMatchPatterns = NEVER_MATCH_PATTERNS) {
1587
+ function classifyPopup(buttons) {
1588
+ const { reject, settings, accept, acknowledge } = buttons.reduce(
1589
+ (acc, button) => {
1590
+ if (button.regexClassification && button.regexClassification !== "other") {
1591
+ acc[button.regexClassification]++;
1592
+ }
1593
+ return acc;
1594
+ },
1595
+ { reject: 0, settings: 0, accept: 0, acknowledge: 0 }
1596
+ );
1597
+ if (reject > 0) {
1598
+ return PopupHandlingModes.Reject;
1599
+ }
1600
+ if (settings > 0) {
1601
+ return PopupHandlingModes.None;
1602
+ }
1603
+ if (acknowledge > 0) {
1604
+ return PopupHandlingModes.Tier1;
1605
+ }
1606
+ if (accept > 0) {
1607
+ return accept === 1 ? PopupHandlingModes.Tier2 : PopupHandlingModes.None;
1608
+ }
1609
+ return PopupHandlingModes.None;
1610
+ }
1611
+ function testButtonMatches(buttonText, matchPatterns, neverMatchPatterns) {
1522
1612
  if (!buttonText) {
1523
1613
  return false;
1524
1614
  }
1525
1615
  const cleanedButtonText = cleanButtonText(buttonText);
1526
- return !neverMatchPatterns.some((p) => p.test(cleanedButtonText)) && rejectPatterns.some((p) => p instanceof RegExp && p.test(cleanedButtonText) || p === cleanedButtonText);
1616
+ return !neverMatchPatterns.some((p) => p instanceof RegExp && p.test(cleanedButtonText) || p === cleanedButtonText) && matchPatterns.some((p) => p instanceof RegExp && p.test(cleanedButtonText) || p === cleanedButtonText);
1527
1617
  }
1528
1618
  function cleanButtonText(buttonText) {
1529
1619
  let result = buttonText.toLowerCase();
@@ -1537,6 +1627,21 @@
1537
1627
  result = result.trim();
1538
1628
  return result;
1539
1629
  }
1630
+ function classifyButtonTextRegex(buttonText) {
1631
+ if (testButtonMatches(buttonText, REJECT_PATTERNS, NEVER_MATCH_PATTERNS)) {
1632
+ return "reject";
1633
+ }
1634
+ if (testButtonMatches(buttonText, SETTINGS_PATTERNS, NEVER_MATCH_PATTERNS)) {
1635
+ return "settings";
1636
+ }
1637
+ if (testButtonMatches(buttonText, ACCEPT_PATTERNS, NEVER_MATCH_PATTERNS)) {
1638
+ return "accept";
1639
+ }
1640
+ if (testButtonMatches(buttonText, ACKNOWLEDGE_PATTERNS, NEVER_MATCH_PATTERNS)) {
1641
+ return "acknowledge";
1642
+ }
1643
+ return "other";
1644
+ }
1540
1645
  function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME) {
1541
1646
  const isFramed = !isTopFrame();
1542
1647
  if (isFramed && window.parent && window.parent !== window.top) {
@@ -1982,7 +2087,7 @@
1982
2087
  }
1983
2088
  };
1984
2089
  var AutoConsentHeuristicCMP = class extends AutoConsentCMPBase {
1985
- constructor(autoconsentInstance) {
2090
+ constructor(autoconsentInstance, mode = PopupHandlingModes.Reject) {
1986
2091
  super(autoconsentInstance);
1987
2092
  this.popups = [];
1988
2093
  this.name = "HEURISTIC";
@@ -1991,6 +2096,7 @@
1991
2096
  frame: false
1992
2097
  // do not run in iframes for security reasons
1993
2098
  };
2099
+ this.mode = mode;
1994
2100
  }
1995
2101
  get hasSelfTest() {
1996
2102
  return true;
@@ -2004,25 +2110,33 @@
2004
2110
  async detectCmp() {
2005
2111
  await new Promise((resolve) => setTimeout(resolve, 0));
2006
2112
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorStart");
2007
- this.popups = getActionablePopups(this.autoconsent.config.heuristicPopupSearchTimeout);
2113
+ this.popups = getActionablePopups(this.mode, this.autoconsent.config.heuristicPopupSearchTimeout);
2008
2114
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorEnd");
2009
2115
  this.autoconsent.config.performanceLoggingEnabled && performance.measure("heuristicDetector", "heuristicDetectorStart", "heuristicDetectorEnd");
2010
2116
  if (this.popups.length > 0) {
2117
+ this.name = `HEURISTIC-TIER${this.popups[0].regexClassification}`;
2011
2118
  return Promise.resolve(true);
2012
2119
  }
2013
2120
  return Promise.resolve(false);
2014
2121
  }
2015
2122
  async detectPopup() {
2016
2123
  if (this.popups.length > 0) {
2017
- if (this.popups.length > 1 || this.popups[0].rejectButtons && this.popups[0].rejectButtons.length > 1) {
2018
- this.autoconsent.config.logs.errors && console.warn("Heuristic found multiple reject buttons");
2124
+ if (this.popups.length > 1) {
2125
+ this.autoconsent.config.logs.errors && console.warn("Heuristic found multiple popups");
2019
2126
  }
2020
2127
  return true;
2021
2128
  }
2022
2129
  return false;
2023
2130
  }
2131
+ getTargetButton() {
2132
+ const popup = this.popups[0];
2133
+ const level = popup.regexClassification;
2134
+ const buttons = popup.buttons;
2135
+ const targetButtonType = level === PopupHandlingModes.Reject ? "reject" : level === PopupHandlingModes.Tier1 ? "acknowledge" : "accept";
2136
+ return buttons.find((button) => button.regexClassification === targetButtonType);
2137
+ }
2024
2138
  optOut() {
2025
- const button = this.popups[0]?.rejectButtons?.[0];
2139
+ const button = this.getTargetButton();
2026
2140
  if (button) {
2027
2141
  return this.clickElement(button.element);
2028
2142
  }
@@ -2035,7 +2149,7 @@
2035
2149
  throw new Error("Not Implemented");
2036
2150
  }
2037
2151
  async test() {
2038
- const button = this.popups[0].rejectButtons?.[0];
2152
+ const button = this.getTargetButton();
2039
2153
  if (button) {
2040
2154
  await this.wait(500);
2041
2155
  return !isElementVisible(button.element);
@@ -2716,7 +2830,7 @@
2716
2830
  async optOut() {
2717
2831
  await this.waitForElement(".unic button", 1e3);
2718
2832
  document.querySelectorAll(".unic button").forEach((button) => {
2719
- const text = button.textContent;
2833
+ const text = button.textContent || "";
2720
2834
  if (text.includes("Manage Options") || text.includes("Optionen verwalten")) {
2721
2835
  button.click();
2722
2836
  }
@@ -2729,7 +2843,7 @@
2729
2843
  }
2730
2844
  });
2731
2845
  for (const b of document.querySelectorAll(".unic button")) {
2732
- const text = b.textContent;
2846
+ const text = b.textContent || "";
2733
2847
  for (const pattern of ["Confirm Choices", "Save Choices", "Auswahl speichern"]) {
2734
2848
  if (text.includes(pattern)) {
2735
2849
  b.click();
@@ -2786,6 +2900,9 @@
2786
2900
  item.querySelector(".cmp-accordion-item-title").click();
2787
2901
  await waitFor(() => !!item.querySelector(".cmp-accordion-item-content.cmp-active"), 10, 50);
2788
2902
  const content = item.querySelector(".cmp-accordion-item-content.cmp-active");
2903
+ if (!content) {
2904
+ return false;
2905
+ }
2789
2906
  content.querySelectorAll(".cmp-toggle-actions .cmp-toggle-deny:not(.cmp-toggle-deny--active)").forEach((e) => e.click());
2790
2907
  content.querySelectorAll(".cmp-toggle-actions .cmp-toggle-checkbox:not(.cmp-toggle-checkbox--active)").forEach((e) => e.click());
2791
2908
  }
@@ -3505,7 +3622,7 @@
3505
3622
  }
3506
3623
  }
3507
3624
  });
3508
- const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this)] : [];
3625
+ const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this, this.config.heuristicMode)] : [];
3509
3626
  const rulesPriorityStages = [
3510
3627
  ["site-specific", siteSpecificRules],
3511
3628
  ["generic", genericRules],