@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
@@ -11412,6 +11412,14 @@ function extractFeaturesFromDOM(roots = [document.documentElement]) {
11412
11412
  };
11413
11413
  }
11414
11414
 
11415
+ // lib/types.ts
11416
+ var PopupHandlingModes = {
11417
+ None: -1,
11418
+ Reject: 0,
11419
+ Tier1: 1,
11420
+ Tier2: 2
11421
+ };
11422
+
11415
11423
  // lib/utils.ts
11416
11424
  function getStyleElement(styleOverrideElementId = "autoconsent-css-rules") {
11417
11425
  const styleSelector = `style#${styleOverrideElementId}`;
@@ -11496,7 +11504,8 @@ function normalizeConfig(providedConfig) {
11496
11504
  waits: false
11497
11505
  },
11498
11506
  performanceLoggingEnabled: false,
11499
- heuristicPopupSearchTimeout: 100
11507
+ heuristicPopupSearchTimeout: 100,
11508
+ heuristicMode: PopupHandlingModes.Reject
11500
11509
  };
11501
11510
  const updatedConfig = copyObject(defaultConfig);
11502
11511
  for (const key of Object.keys(defaultConfig)) {
@@ -11514,12 +11523,13 @@ function scheduleWhenIdle(callback, timeout = 500) {
11514
11523
  }
11515
11524
  }
11516
11525
  function highlightNode(node) {
11526
+ const highlightedNode = node;
11517
11527
  if (!node.style) return;
11518
- if (node.__oldStyles !== void 0) {
11528
+ if (highlightedNode.__oldStyles !== void 0) {
11519
11529
  return;
11520
11530
  }
11521
11531
  if (node.hasAttribute("style")) {
11522
- node.__oldStyles = node.style.cssText;
11532
+ highlightedNode.__oldStyles = node.style.cssText;
11523
11533
  }
11524
11534
  node.style.animation = "pulsate .5s infinite";
11525
11535
  node.style.outline = "solid red";
@@ -11547,10 +11557,11 @@ function highlightNode(node) {
11547
11557
  document.head.appendChild(styleTag);
11548
11558
  }
11549
11559
  function unhighlightNode(node) {
11560
+ const highlightedNode = node;
11550
11561
  if (!node.style || !node.hasAttribute("style")) return;
11551
- if (node.__oldStyles !== void 0) {
11552
- node.style.cssText = node.__oldStyles;
11553
- delete node.__oldStyles;
11562
+ if (highlightedNode.__oldStyles !== void 0) {
11563
+ node.style.cssText = highlightedNode.__oldStyles;
11564
+ delete highlightedNode.__oldStyles;
11554
11565
  } else {
11555
11566
  node.removeAttribute("style");
11556
11567
  }
@@ -12027,6 +12038,9 @@ var evalState = {
12027
12038
  sendContentMessage: null
12028
12039
  };
12029
12040
  function requestEval(code, snippetId) {
12041
+ if (!evalState.sendContentMessage) {
12042
+ return Promise.reject(new Error("AutoConsent is not initialized yet"));
12043
+ }
12030
12044
  const id = getRandomID();
12031
12045
  evalState.sendContentMessage({
12032
12046
  type: "eval",
@@ -12330,12 +12344,17 @@ var REJECT_PATTERNS_ENGLISH = [
12330
12344
  /^\s*(use|accept|allow|continue\s+with)?\s*only\s*(strictly)?\s*(necessary|essentials?|required)?\s*(cookies)?\s*$/is,
12331
12345
  // e.g. "do not sell or share my personal information", "do not sell my personal information"
12332
12346
  // often used in CCPA
12333
- /^\s*do\s+not\s+sell(\s+or\s+share)?\s*my\s*personal\s*information\s*$/is,
12347
+ /^\s*do\s+not\s+sell(\s+or\s+share)?\s*my\s*personal\s*info(rmation)?\s*$/is,
12334
12348
  "allow selection",
12335
- "disagree and close"
12349
+ "disagree and close",
12336
12350
  // These are impactful, but look error-prone
12351
+ // // e.g. "disagree"
12352
+ /^(i)?\s*disagree\s*(and\s+close)?$/i,
12337
12353
  // // e.g. "i do not agree"
12338
12354
  // /^\s*(i\s+)?do\s+not\s+agree\s*$/i,
12355
+ "no",
12356
+ /^no,? thanks$/is,
12357
+ /^opt[ -]out$/is
12339
12358
  ];
12340
12359
  var REJECT_PATTERNS_DUTCH = [
12341
12360
  "weigeren",
@@ -12911,6 +12930,64 @@ var NEVER_MATCH_PATTERNS = [
12911
12930
  /sostienici/is,
12912
12931
  /suscribir/is
12913
12932
  ];
12933
+ var SETTINGS_PATTERNS = [
12934
+ "settings",
12935
+ "preferences",
12936
+ /customi(s|z)e/is,
12937
+ "show details",
12938
+ "more options",
12939
+ /(manage|configure) (my|your) (preferences|choices|cookies)/is,
12940
+ "manage choices",
12941
+ /(cookie )?preference center/is,
12942
+ "change settings",
12943
+ "configure",
12944
+ "change my preferences",
12945
+ "cookie manager",
12946
+ "cookie preference",
12947
+ "let me choose",
12948
+ "cookieconsent preferences",
12949
+ /privacy choices/is,
12950
+ /(privacy|cookie|custom) settings/is,
12951
+ /cookies? (settings|preferences|setting)/is,
12952
+ /(manage|customize|customise|opt-out|edit).*(cookies|preferences|settings|options)/is,
12953
+ "cookie consent options",
12954
+ "privacy controls",
12955
+ "show purposes",
12956
+ // German
12957
+ "einstellungen"
12958
+ ];
12959
+ var ACCEPT_PATTERNS = [
12960
+ /^(accept|allow)( all)?( cookies)?$/is,
12961
+ /i (accept|allow)( all)?/is,
12962
+ "yes",
12963
+ /^(i )?agree$/is,
12964
+ "continue with all",
12965
+ "accept and continue",
12966
+ /accept all above/is,
12967
+ /^(accept|agree) and close/is,
12968
+ "accept continue",
12969
+ "agree proceed",
12970
+ "allow and continue",
12971
+ "close and accept",
12972
+ /accept all$/is,
12973
+ "im ok with that",
12974
+ "accept optional cookies",
12975
+ /^alle (cookies )?akzeptieren$/is
12976
+ ];
12977
+ var ACKNOWLEDGE_PATTERNS = [
12978
+ "ok",
12979
+ "close",
12980
+ "continue",
12981
+ "x",
12982
+ /^got it!?$/,
12983
+ "i understand",
12984
+ "dismiss",
12985
+ "okay",
12986
+ "acknowledge",
12987
+ /^close (banner|cookie notification)$/is,
12988
+ /understood$/is,
12989
+ "confirm my choices"
12990
+ ];
12914
12991
 
12915
12992
  // lib/heuristics.ts
12916
12993
  var BUTTON_LIKE_ELEMENT_SELECTOR = 'button, input[type="button"], input[type="submit"], a, [role="button"], [class*="button"]';
@@ -12929,48 +13006,61 @@ function checkHeuristicPatterns(allText, detectPatterns = DETECT_PATTERNS) {
12929
13006
  }
12930
13007
  return { patterns, snippets: snippets2 };
12931
13008
  }
12932
- function getActionablePopups(timeout = POPUP_SEARCH_MAX_TIME) {
13009
+ function getActionablePopups(mode = PopupHandlingModes.Reject, timeout = POPUP_SEARCH_MAX_TIME) {
12933
13010
  const popups = getPotentialPopups(timeout);
12934
13011
  const result = popups.reduce((acc, popup) => {
12935
13012
  const popupText = popup.text?.trim();
12936
13013
  if (popupText) {
12937
13014
  const { patterns } = checkHeuristicPatterns(popupText);
12938
13015
  if (patterns.length > 0) {
12939
- const { rejectButtons, otherButtons } = classifyButtons(popup.buttons);
12940
- if (rejectButtons.length > 0) {
12941
- acc.push({
12942
- ...popup,
12943
- rejectButtons,
12944
- otherButtons
12945
- });
12946
- }
13016
+ classifyButtons(popup.buttons);
13017
+ popup.regexClassification = classifyPopup(popup.buttons);
13018
+ acc.push({
13019
+ ...popup
13020
+ });
12947
13021
  }
12948
13022
  }
12949
13023
  return acc;
12950
13024
  }, []);
12951
- return result;
13025
+ return result.filter(
13026
+ (popup) => popup.regexClassification !== void 0 && popup.regexClassification !== PopupHandlingModes.None && popup.regexClassification <= mode
13027
+ ).sort((a, b) => (a.regexClassification ?? 0) - (b.regexClassification ?? 0));
12952
13028
  }
12953
13029
  function classifyButtons(buttons) {
12954
- const rejectButtons = [];
12955
- const otherButtons = [];
12956
13030
  for (const button of buttons) {
12957
- if (isRejectButton(button.text)) {
12958
- rejectButtons.push(button);
12959
- } else {
12960
- otherButtons.push(button);
12961
- }
13031
+ button.regexClassification = classifyButtonTextRegex(button.text);
12962
13032
  }
12963
- return {
12964
- rejectButtons,
12965
- otherButtons
12966
- };
12967
13033
  }
12968
- function isRejectButton(buttonText, rejectPatterns = REJECT_PATTERNS, neverMatchPatterns = NEVER_MATCH_PATTERNS) {
13034
+ function classifyPopup(buttons) {
13035
+ const { reject, settings, accept, acknowledge } = buttons.reduce(
13036
+ (acc, button) => {
13037
+ if (button.regexClassification && button.regexClassification !== "other") {
13038
+ acc[button.regexClassification]++;
13039
+ }
13040
+ return acc;
13041
+ },
13042
+ { reject: 0, settings: 0, accept: 0, acknowledge: 0 }
13043
+ );
13044
+ if (reject > 0) {
13045
+ return PopupHandlingModes.Reject;
13046
+ }
13047
+ if (settings > 0) {
13048
+ return PopupHandlingModes.None;
13049
+ }
13050
+ if (acknowledge > 0) {
13051
+ return PopupHandlingModes.Tier1;
13052
+ }
13053
+ if (accept > 0) {
13054
+ return accept === 1 ? PopupHandlingModes.Tier2 : PopupHandlingModes.None;
13055
+ }
13056
+ return PopupHandlingModes.None;
13057
+ }
13058
+ function testButtonMatches(buttonText, matchPatterns, neverMatchPatterns) {
12969
13059
  if (!buttonText) {
12970
13060
  return false;
12971
13061
  }
12972
13062
  const cleanedButtonText = cleanButtonText(buttonText);
12973
- return !neverMatchPatterns.some((p) => p.test(cleanedButtonText)) && rejectPatterns.some((p) => p instanceof RegExp && p.test(cleanedButtonText) || p === cleanedButtonText);
13063
+ return !neverMatchPatterns.some((p) => p instanceof RegExp && p.test(cleanedButtonText) || p === cleanedButtonText) && matchPatterns.some((p) => p instanceof RegExp && p.test(cleanedButtonText) || p === cleanedButtonText);
12974
13064
  }
12975
13065
  function cleanButtonText(buttonText) {
12976
13066
  let result = buttonText.toLowerCase();
@@ -12984,6 +13074,21 @@ function cleanButtonText(buttonText) {
12984
13074
  result = result.trim();
12985
13075
  return result;
12986
13076
  }
13077
+ function classifyButtonTextRegex(buttonText) {
13078
+ if (testButtonMatches(buttonText, REJECT_PATTERNS, NEVER_MATCH_PATTERNS)) {
13079
+ return "reject";
13080
+ }
13081
+ if (testButtonMatches(buttonText, SETTINGS_PATTERNS, NEVER_MATCH_PATTERNS)) {
13082
+ return "settings";
13083
+ }
13084
+ if (testButtonMatches(buttonText, ACCEPT_PATTERNS, NEVER_MATCH_PATTERNS)) {
13085
+ return "accept";
13086
+ }
13087
+ if (testButtonMatches(buttonText, ACKNOWLEDGE_PATTERNS, NEVER_MATCH_PATTERNS)) {
13088
+ return "acknowledge";
13089
+ }
13090
+ return "other";
13091
+ }
12987
13092
  function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME) {
12988
13093
  const isFramed = !isTopFrame();
12989
13094
  if (isFramed && window.parent && window.parent !== window.top) {
@@ -13429,7 +13534,7 @@ var AutoConsentCMP = class extends AutoConsentCMPBase {
13429
13534
  }
13430
13535
  };
13431
13536
  var AutoConsentHeuristicCMP = class extends AutoConsentCMPBase {
13432
- constructor(autoconsentInstance) {
13537
+ constructor(autoconsentInstance, mode = PopupHandlingModes.Reject) {
13433
13538
  super(autoconsentInstance);
13434
13539
  this.popups = [];
13435
13540
  this.name = "HEURISTIC";
@@ -13438,6 +13543,7 @@ var AutoConsentHeuristicCMP = class extends AutoConsentCMPBase {
13438
13543
  frame: false
13439
13544
  // do not run in iframes for security reasons
13440
13545
  };
13546
+ this.mode = mode;
13441
13547
  }
13442
13548
  get hasSelfTest() {
13443
13549
  return true;
@@ -13451,25 +13557,33 @@ var AutoConsentHeuristicCMP = class extends AutoConsentCMPBase {
13451
13557
  async detectCmp() {
13452
13558
  await new Promise((resolve) => setTimeout(resolve, 0));
13453
13559
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorStart");
13454
- this.popups = getActionablePopups(this.autoconsent.config.heuristicPopupSearchTimeout);
13560
+ this.popups = getActionablePopups(this.mode, this.autoconsent.config.heuristicPopupSearchTimeout);
13455
13561
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorEnd");
13456
13562
  this.autoconsent.config.performanceLoggingEnabled && performance.measure("heuristicDetector", "heuristicDetectorStart", "heuristicDetectorEnd");
13457
13563
  if (this.popups.length > 0) {
13564
+ this.name = `HEURISTIC-TIER${this.popups[0].regexClassification}`;
13458
13565
  return Promise.resolve(true);
13459
13566
  }
13460
13567
  return Promise.resolve(false);
13461
13568
  }
13462
13569
  async detectPopup() {
13463
13570
  if (this.popups.length > 0) {
13464
- if (this.popups.length > 1 || this.popups[0].rejectButtons && this.popups[0].rejectButtons.length > 1) {
13465
- this.autoconsent.config.logs.errors && console.warn("Heuristic found multiple reject buttons");
13571
+ if (this.popups.length > 1) {
13572
+ this.autoconsent.config.logs.errors && console.warn("Heuristic found multiple popups");
13466
13573
  }
13467
13574
  return true;
13468
13575
  }
13469
13576
  return false;
13470
13577
  }
13578
+ getTargetButton() {
13579
+ const popup = this.popups[0];
13580
+ const level = popup.regexClassification;
13581
+ const buttons = popup.buttons;
13582
+ const targetButtonType = level === PopupHandlingModes.Reject ? "reject" : level === PopupHandlingModes.Tier1 ? "acknowledge" : "accept";
13583
+ return buttons.find((button) => button.regexClassification === targetButtonType);
13584
+ }
13471
13585
  optOut() {
13472
- const button = this.popups[0]?.rejectButtons?.[0];
13586
+ const button = this.getTargetButton();
13473
13587
  if (button) {
13474
13588
  return this.clickElement(button.element);
13475
13589
  }
@@ -13482,7 +13596,7 @@ var AutoConsentHeuristicCMP = class extends AutoConsentCMPBase {
13482
13596
  throw new Error("Not Implemented");
13483
13597
  }
13484
13598
  async test() {
13485
- const button = this.popups[0].rejectButtons?.[0];
13599
+ const button = this.getTargetButton();
13486
13600
  if (button) {
13487
13601
  await this.wait(500);
13488
13602
  return !isElementVisible(button.element);
@@ -14163,7 +14277,7 @@ var Uniconsent = class extends AutoConsentCMPBase {
14163
14277
  async optOut() {
14164
14278
  await this.waitForElement(".unic button", 1e3);
14165
14279
  document.querySelectorAll(".unic button").forEach((button) => {
14166
- const text = button.textContent;
14280
+ const text = button.textContent || "";
14167
14281
  if (text.includes("Manage Options") || text.includes("Optionen verwalten")) {
14168
14282
  button.click();
14169
14283
  }
@@ -14176,7 +14290,7 @@ var Uniconsent = class extends AutoConsentCMPBase {
14176
14290
  }
14177
14291
  });
14178
14292
  for (const b of document.querySelectorAll(".unic button")) {
14179
- const text = b.textContent;
14293
+ const text = b.textContent || "";
14180
14294
  for (const pattern of ["Confirm Choices", "Save Choices", "Auswahl speichern"]) {
14181
14295
  if (text.includes(pattern)) {
14182
14296
  b.click();
@@ -14233,6 +14347,9 @@ var Conversant = class extends AutoConsentCMPBase {
14233
14347
  item.querySelector(".cmp-accordion-item-title").click();
14234
14348
  await waitFor(() => !!item.querySelector(".cmp-accordion-item-content.cmp-active"), 10, 50);
14235
14349
  const content = item.querySelector(".cmp-accordion-item-content.cmp-active");
14350
+ if (!content) {
14351
+ return false;
14352
+ }
14236
14353
  content.querySelectorAll(".cmp-toggle-actions .cmp-toggle-deny:not(.cmp-toggle-deny--active)").forEach((e) => e.click());
14237
14354
  content.querySelectorAll(".cmp-toggle-actions .cmp-toggle-checkbox:not(.cmp-toggle-checkbox--active)").forEach((e) => e.click());
14238
14355
  }
@@ -14952,7 +15069,7 @@ var AutoConsent = class {
14952
15069
  }
14953
15070
  }
14954
15071
  });
14955
- const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this)] : [];
15072
+ const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this, this.config.heuristicMode)] : [];
14956
15073
  const rulesPriorityStages = [
14957
15074
  ["site-specific", siteSpecificRules],
14958
15075
  ["generic", genericRules],