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