@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
@@ -11483,6 +11483,14 @@ function extractFeaturesFromDOM(roots = [document.documentElement]) {
11483
11483
  // lib/filterlist-utils.ts
11484
11484
  var import_tldts_experimental3 = __toESM(require_cjs());
11485
11485
 
11486
+ // lib/types.ts
11487
+ var PopupHandlingModes = {
11488
+ None: -1,
11489
+ Reject: 0,
11490
+ Tier1: 1,
11491
+ Tier2: 2
11492
+ };
11493
+
11486
11494
  // lib/utils.ts
11487
11495
  function getStyleElement(styleOverrideElementId = "autoconsent-css-rules") {
11488
11496
  const styleSelector = `style#${styleOverrideElementId}`;
@@ -11567,7 +11575,8 @@ function normalizeConfig(providedConfig) {
11567
11575
  waits: false
11568
11576
  },
11569
11577
  performanceLoggingEnabled: false,
11570
- heuristicPopupSearchTimeout: 100
11578
+ heuristicPopupSearchTimeout: 100,
11579
+ heuristicMode: PopupHandlingModes.Reject
11571
11580
  };
11572
11581
  const updatedConfig = copyObject(defaultConfig);
11573
11582
  for (const key of Object.keys(defaultConfig)) {
@@ -11585,12 +11594,13 @@ function scheduleWhenIdle(callback, timeout = 500) {
11585
11594
  }
11586
11595
  }
11587
11596
  function highlightNode(node) {
11597
+ const highlightedNode = node;
11588
11598
  if (!node.style) return;
11589
- if (node.__oldStyles !== void 0) {
11599
+ if (highlightedNode.__oldStyles !== void 0) {
11590
11600
  return;
11591
11601
  }
11592
11602
  if (node.hasAttribute("style")) {
11593
- node.__oldStyles = node.style.cssText;
11603
+ highlightedNode.__oldStyles = node.style.cssText;
11594
11604
  }
11595
11605
  node.style.animation = "pulsate .5s infinite";
11596
11606
  node.style.outline = "solid red";
@@ -11618,10 +11628,11 @@ function highlightNode(node) {
11618
11628
  document.head.appendChild(styleTag);
11619
11629
  }
11620
11630
  function unhighlightNode(node) {
11631
+ const highlightedNode = node;
11621
11632
  if (!node.style || !node.hasAttribute("style")) return;
11622
- if (node.__oldStyles !== void 0) {
11623
- node.style.cssText = node.__oldStyles;
11624
- delete node.__oldStyles;
11633
+ if (highlightedNode.__oldStyles !== void 0) {
11634
+ node.style.cssText = highlightedNode.__oldStyles;
11635
+ delete highlightedNode.__oldStyles;
11625
11636
  } else {
11626
11637
  node.removeAttribute("style");
11627
11638
  }
@@ -12098,6 +12109,9 @@ var evalState = {
12098
12109
  sendContentMessage: null
12099
12110
  };
12100
12111
  function requestEval(code, snippetId) {
12112
+ if (!evalState.sendContentMessage) {
12113
+ return Promise.reject(new Error("AutoConsent is not initialized yet"));
12114
+ }
12101
12115
  const id = getRandomID();
12102
12116
  evalState.sendContentMessage({
12103
12117
  type: "eval",
@@ -12401,12 +12415,17 @@ var REJECT_PATTERNS_ENGLISH = [
12401
12415
  /^\s*(use|accept|allow|continue\s+with)?\s*only\s*(strictly)?\s*(necessary|essentials?|required)?\s*(cookies)?\s*$/is,
12402
12416
  // e.g. "do not sell or share my personal information", "do not sell my personal information"
12403
12417
  // often used in CCPA
12404
- /^\s*do\s+not\s+sell(\s+or\s+share)?\s*my\s*personal\s*information\s*$/is,
12418
+ /^\s*do\s+not\s+sell(\s+or\s+share)?\s*my\s*personal\s*info(rmation)?\s*$/is,
12405
12419
  "allow selection",
12406
- "disagree and close"
12420
+ "disagree and close",
12407
12421
  // These are impactful, but look error-prone
12422
+ // // e.g. "disagree"
12423
+ /^(i)?\s*disagree\s*(and\s+close)?$/i,
12408
12424
  // // e.g. "i do not agree"
12409
12425
  // /^\s*(i\s+)?do\s+not\s+agree\s*$/i,
12426
+ "no",
12427
+ /^no,? thanks$/is,
12428
+ /^opt[ -]out$/is
12410
12429
  ];
12411
12430
  var REJECT_PATTERNS_DUTCH = [
12412
12431
  "weigeren",
@@ -12982,6 +13001,64 @@ var NEVER_MATCH_PATTERNS = [
12982
13001
  /sostienici/is,
12983
13002
  /suscribir/is
12984
13003
  ];
13004
+ var SETTINGS_PATTERNS = [
13005
+ "settings",
13006
+ "preferences",
13007
+ /customi(s|z)e/is,
13008
+ "show details",
13009
+ "more options",
13010
+ /(manage|configure) (my|your) (preferences|choices|cookies)/is,
13011
+ "manage choices",
13012
+ /(cookie )?preference center/is,
13013
+ "change settings",
13014
+ "configure",
13015
+ "change my preferences",
13016
+ "cookie manager",
13017
+ "cookie preference",
13018
+ "let me choose",
13019
+ "cookieconsent preferences",
13020
+ /privacy choices/is,
13021
+ /(privacy|cookie|custom) settings/is,
13022
+ /cookies? (settings|preferences|setting)/is,
13023
+ /(manage|customize|customise|opt-out|edit).*(cookies|preferences|settings|options)/is,
13024
+ "cookie consent options",
13025
+ "privacy controls",
13026
+ "show purposes",
13027
+ // German
13028
+ "einstellungen"
13029
+ ];
13030
+ var ACCEPT_PATTERNS = [
13031
+ /^(accept|allow)( all)?( cookies)?$/is,
13032
+ /i (accept|allow)( all)?/is,
13033
+ "yes",
13034
+ /^(i )?agree$/is,
13035
+ "continue with all",
13036
+ "accept and continue",
13037
+ /accept all above/is,
13038
+ /^(accept|agree) and close/is,
13039
+ "accept continue",
13040
+ "agree proceed",
13041
+ "allow and continue",
13042
+ "close and accept",
13043
+ /accept all$/is,
13044
+ "im ok with that",
13045
+ "accept optional cookies",
13046
+ /^alle (cookies )?akzeptieren$/is
13047
+ ];
13048
+ var ACKNOWLEDGE_PATTERNS = [
13049
+ "ok",
13050
+ "close",
13051
+ "continue",
13052
+ "x",
13053
+ /^got it!?$/,
13054
+ "i understand",
13055
+ "dismiss",
13056
+ "okay",
13057
+ "acknowledge",
13058
+ /^close (banner|cookie notification)$/is,
13059
+ /understood$/is,
13060
+ "confirm my choices"
13061
+ ];
12985
13062
 
12986
13063
  // lib/heuristics.ts
12987
13064
  var BUTTON_LIKE_ELEMENT_SELECTOR = 'button, input[type="button"], input[type="submit"], a, [role="button"], [class*="button"]';
@@ -13000,48 +13077,61 @@ function checkHeuristicPatterns(allText, detectPatterns = DETECT_PATTERNS) {
13000
13077
  }
13001
13078
  return { patterns, snippets: snippets2 };
13002
13079
  }
13003
- function getActionablePopups(timeout = POPUP_SEARCH_MAX_TIME) {
13080
+ function getActionablePopups(mode = PopupHandlingModes.Reject, timeout = POPUP_SEARCH_MAX_TIME) {
13004
13081
  const popups = getPotentialPopups(timeout);
13005
13082
  const result = popups.reduce((acc, popup) => {
13006
13083
  const popupText = popup.text?.trim();
13007
13084
  if (popupText) {
13008
13085
  const { patterns } = checkHeuristicPatterns(popupText);
13009
13086
  if (patterns.length > 0) {
13010
- const { rejectButtons, otherButtons } = classifyButtons(popup.buttons);
13011
- if (rejectButtons.length > 0) {
13012
- acc.push({
13013
- ...popup,
13014
- rejectButtons,
13015
- otherButtons
13016
- });
13017
- }
13087
+ classifyButtons(popup.buttons);
13088
+ popup.regexClassification = classifyPopup(popup.buttons);
13089
+ acc.push({
13090
+ ...popup
13091
+ });
13018
13092
  }
13019
13093
  }
13020
13094
  return acc;
13021
13095
  }, []);
13022
- return result;
13096
+ return result.filter(
13097
+ (popup) => popup.regexClassification !== void 0 && popup.regexClassification !== PopupHandlingModes.None && popup.regexClassification <= mode
13098
+ ).sort((a, b) => (a.regexClassification ?? 0) - (b.regexClassification ?? 0));
13023
13099
  }
13024
13100
  function classifyButtons(buttons) {
13025
- const rejectButtons = [];
13026
- const otherButtons = [];
13027
13101
  for (const button of buttons) {
13028
- if (isRejectButton(button.text)) {
13029
- rejectButtons.push(button);
13030
- } else {
13031
- otherButtons.push(button);
13032
- }
13102
+ button.regexClassification = classifyButtonTextRegex(button.text);
13033
13103
  }
13034
- return {
13035
- rejectButtons,
13036
- otherButtons
13037
- };
13038
13104
  }
13039
- function isRejectButton(buttonText, rejectPatterns = REJECT_PATTERNS, neverMatchPatterns = NEVER_MATCH_PATTERNS) {
13105
+ function classifyPopup(buttons) {
13106
+ const { reject, settings, accept, acknowledge } = buttons.reduce(
13107
+ (acc, button) => {
13108
+ if (button.regexClassification && button.regexClassification !== "other") {
13109
+ acc[button.regexClassification]++;
13110
+ }
13111
+ return acc;
13112
+ },
13113
+ { reject: 0, settings: 0, accept: 0, acknowledge: 0 }
13114
+ );
13115
+ if (reject > 0) {
13116
+ return PopupHandlingModes.Reject;
13117
+ }
13118
+ if (settings > 0) {
13119
+ return PopupHandlingModes.None;
13120
+ }
13121
+ if (acknowledge > 0) {
13122
+ return PopupHandlingModes.Tier1;
13123
+ }
13124
+ if (accept > 0) {
13125
+ return accept === 1 ? PopupHandlingModes.Tier2 : PopupHandlingModes.None;
13126
+ }
13127
+ return PopupHandlingModes.None;
13128
+ }
13129
+ function testButtonMatches(buttonText, matchPatterns, neverMatchPatterns) {
13040
13130
  if (!buttonText) {
13041
13131
  return false;
13042
13132
  }
13043
13133
  const cleanedButtonText = cleanButtonText(buttonText);
13044
- return !neverMatchPatterns.some((p) => p.test(cleanedButtonText)) && rejectPatterns.some((p) => p instanceof RegExp && p.test(cleanedButtonText) || p === cleanedButtonText);
13134
+ return !neverMatchPatterns.some((p) => p instanceof RegExp && p.test(cleanedButtonText) || p === cleanedButtonText) && matchPatterns.some((p) => p instanceof RegExp && p.test(cleanedButtonText) || p === cleanedButtonText);
13045
13135
  }
13046
13136
  function cleanButtonText(buttonText) {
13047
13137
  let result = buttonText.toLowerCase();
@@ -13055,6 +13145,21 @@ function cleanButtonText(buttonText) {
13055
13145
  result = result.trim();
13056
13146
  return result;
13057
13147
  }
13148
+ function classifyButtonTextRegex(buttonText) {
13149
+ if (testButtonMatches(buttonText, REJECT_PATTERNS, NEVER_MATCH_PATTERNS)) {
13150
+ return "reject";
13151
+ }
13152
+ if (testButtonMatches(buttonText, SETTINGS_PATTERNS, NEVER_MATCH_PATTERNS)) {
13153
+ return "settings";
13154
+ }
13155
+ if (testButtonMatches(buttonText, ACCEPT_PATTERNS, NEVER_MATCH_PATTERNS)) {
13156
+ return "accept";
13157
+ }
13158
+ if (testButtonMatches(buttonText, ACKNOWLEDGE_PATTERNS, NEVER_MATCH_PATTERNS)) {
13159
+ return "acknowledge";
13160
+ }
13161
+ return "other";
13162
+ }
13058
13163
  function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME) {
13059
13164
  const isFramed = !isTopFrame();
13060
13165
  if (isFramed && window.parent && window.parent !== window.top) {
@@ -13500,7 +13605,7 @@ var AutoConsentCMP = class extends AutoConsentCMPBase {
13500
13605
  }
13501
13606
  };
13502
13607
  var AutoConsentHeuristicCMP = class extends AutoConsentCMPBase {
13503
- constructor(autoconsentInstance) {
13608
+ constructor(autoconsentInstance, mode = PopupHandlingModes.Reject) {
13504
13609
  super(autoconsentInstance);
13505
13610
  this.popups = [];
13506
13611
  this.name = "HEURISTIC";
@@ -13509,6 +13614,7 @@ var AutoConsentHeuristicCMP = class extends AutoConsentCMPBase {
13509
13614
  frame: false
13510
13615
  // do not run in iframes for security reasons
13511
13616
  };
13617
+ this.mode = mode;
13512
13618
  }
13513
13619
  get hasSelfTest() {
13514
13620
  return true;
@@ -13522,25 +13628,33 @@ var AutoConsentHeuristicCMP = class extends AutoConsentCMPBase {
13522
13628
  async detectCmp() {
13523
13629
  await new Promise((resolve) => setTimeout(resolve, 0));
13524
13630
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorStart");
13525
- this.popups = getActionablePopups(this.autoconsent.config.heuristicPopupSearchTimeout);
13631
+ this.popups = getActionablePopups(this.mode, this.autoconsent.config.heuristicPopupSearchTimeout);
13526
13632
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorEnd");
13527
13633
  this.autoconsent.config.performanceLoggingEnabled && performance.measure("heuristicDetector", "heuristicDetectorStart", "heuristicDetectorEnd");
13528
13634
  if (this.popups.length > 0) {
13635
+ this.name = `HEURISTIC-TIER${this.popups[0].regexClassification}`;
13529
13636
  return Promise.resolve(true);
13530
13637
  }
13531
13638
  return Promise.resolve(false);
13532
13639
  }
13533
13640
  async detectPopup() {
13534
13641
  if (this.popups.length > 0) {
13535
- if (this.popups.length > 1 || this.popups[0].rejectButtons && this.popups[0].rejectButtons.length > 1) {
13536
- this.autoconsent.config.logs.errors && console.warn("Heuristic found multiple reject buttons");
13642
+ if (this.popups.length > 1) {
13643
+ this.autoconsent.config.logs.errors && console.warn("Heuristic found multiple popups");
13537
13644
  }
13538
13645
  return true;
13539
13646
  }
13540
13647
  return false;
13541
13648
  }
13649
+ getTargetButton() {
13650
+ const popup = this.popups[0];
13651
+ const level = popup.regexClassification;
13652
+ const buttons = popup.buttons;
13653
+ const targetButtonType = level === PopupHandlingModes.Reject ? "reject" : level === PopupHandlingModes.Tier1 ? "acknowledge" : "accept";
13654
+ return buttons.find((button) => button.regexClassification === targetButtonType);
13655
+ }
13542
13656
  optOut() {
13543
- const button = this.popups[0]?.rejectButtons?.[0];
13657
+ const button = this.getTargetButton();
13544
13658
  if (button) {
13545
13659
  return this.clickElement(button.element);
13546
13660
  }
@@ -13553,7 +13667,7 @@ var AutoConsentHeuristicCMP = class extends AutoConsentCMPBase {
13553
13667
  throw new Error("Not Implemented");
13554
13668
  }
13555
13669
  async test() {
13556
- const button = this.popups[0].rejectButtons?.[0];
13670
+ const button = this.getTargetButton();
13557
13671
  if (button) {
13558
13672
  await this.wait(500);
13559
13673
  return !isElementVisible(button.element);
@@ -14234,7 +14348,7 @@ var Uniconsent = class extends AutoConsentCMPBase {
14234
14348
  async optOut() {
14235
14349
  await this.waitForElement(".unic button", 1e3);
14236
14350
  document.querySelectorAll(".unic button").forEach((button) => {
14237
- const text = button.textContent;
14351
+ const text = button.textContent || "";
14238
14352
  if (text.includes("Manage Options") || text.includes("Optionen verwalten")) {
14239
14353
  button.click();
14240
14354
  }
@@ -14247,7 +14361,7 @@ var Uniconsent = class extends AutoConsentCMPBase {
14247
14361
  }
14248
14362
  });
14249
14363
  for (const b of document.querySelectorAll(".unic button")) {
14250
- const text = b.textContent;
14364
+ const text = b.textContent || "";
14251
14365
  for (const pattern of ["Confirm Choices", "Save Choices", "Auswahl speichern"]) {
14252
14366
  if (text.includes(pattern)) {
14253
14367
  b.click();
@@ -14304,6 +14418,9 @@ var Conversant = class extends AutoConsentCMPBase {
14304
14418
  item.querySelector(".cmp-accordion-item-title").click();
14305
14419
  await waitFor(() => !!item.querySelector(".cmp-accordion-item-content.cmp-active"), 10, 50);
14306
14420
  const content = item.querySelector(".cmp-accordion-item-content.cmp-active");
14421
+ if (!content) {
14422
+ return false;
14423
+ }
14307
14424
  content.querySelectorAll(".cmp-toggle-actions .cmp-toggle-deny:not(.cmp-toggle-deny--active)").forEach((e) => e.click());
14308
14425
  content.querySelectorAll(".cmp-toggle-actions .cmp-toggle-checkbox:not(.cmp-toggle-checkbox--active)").forEach((e) => e.click());
14309
14426
  }
@@ -15023,7 +15140,7 @@ var AutoConsent = class {
15023
15140
  }
15024
15141
  }
15025
15142
  });
15026
- const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this)] : [];
15143
+ const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this, this.config.heuristicMode)] : [];
15027
15144
  const rulesPriorityStages = [
15028
15145
  ["site-specific", siteSpecificRules],
15029
15146
  ["generic", genericRules],