@duckduckgo/autoconsent 16.63.0 → 17.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 (180) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/dist/addon-firefox/background.bundle.js +1 -0
  3. package/dist/addon-firefox/compact-rules.json +1 -1
  4. package/dist/addon-firefox/content.bundle.js +177 -38
  5. package/dist/addon-firefox/manifest.json +1 -1
  6. package/dist/addon-firefox/popup.bundle.js +1 -0
  7. package/dist/addon-firefox/rule-index.json +1 -1
  8. package/dist/addon-firefox/rules.json +1 -1
  9. package/dist/addon-mv3/background.bundle.js +1 -0
  10. package/dist/addon-mv3/compact-rules.json +1 -1
  11. package/dist/addon-mv3/content.bundle.js +177 -38
  12. package/dist/addon-mv3/manifest.json +1 -1
  13. package/dist/addon-mv3/popup.bundle.js +1 -0
  14. package/dist/addon-mv3/rule-index.json +1 -1
  15. package/dist/addon-mv3/rules.json +1 -1
  16. package/dist/autoconsent.cjs.js +177 -38
  17. package/dist/autoconsent.esm.js +177 -38
  18. package/dist/autoconsent.playwright.js +180 -38
  19. package/dist/autoconsent.standalone.js +178 -39
  20. package/dist/heuristics.cjs.js +15 -3
  21. package/dist/heuristics.mjs +15 -3
  22. package/dist/types/heuristics.d.ts +12 -3
  23. package/dist/types/types.d.ts +12 -1
  24. package/lib/cmps/base.ts +5 -1
  25. package/lib/heuristic-patterns.ts +18 -2
  26. package/lib/heuristics.ts +240 -45
  27. package/lib/types.ts +16 -1
  28. package/lib/utils.ts +1 -0
  29. package/package.json +1 -1
  30. package/readme.md +1 -1
  31. package/rules/autoconsent/1pmobile.json +44 -0
  32. package/rules/autoconsent/amasty-gdpr-hyva.json +131 -0
  33. package/rules/autoconsent/amazonforum.json +86 -0
  34. package/rules/autoconsent/asrock.json +44 -0
  35. package/rules/autoconsent/babylist.json +23 -0
  36. package/rules/autoconsent/babylonbee.json +36 -0
  37. package/rules/autoconsent/bathnes-cookie-banner.json +31 -0
  38. package/rules/autoconsent/chairish.json +17 -0
  39. package/rules/autoconsent/chemicalbook.json +13 -0
  40. package/rules/autoconsent/chimnie-co-uk.json +21 -0
  41. package/rules/autoconsent/complianz-optin.json +5 -3
  42. package/rules/autoconsent/consentmo.json +5 -2
  43. package/rules/autoconsent/cookie-pref-form.json +48 -0
  44. package/rules/autoconsent/cookie-script.json +34 -15
  45. package/rules/autoconsent/endeavorhealth.json +31 -0
  46. package/rules/autoconsent/ensighten.json +3 -3
  47. package/rules/autoconsent/epaas.json +18 -1
  48. package/rules/autoconsent/fepblue-org.json +13 -0
  49. package/rules/autoconsent/fnsp-cookieconsent.json +30 -0
  50. package/rules/autoconsent/fod-fujitv.json +13 -0
  51. package/rules/autoconsent/fpl-com.json +34 -0
  52. package/rules/autoconsent/fuqer.json +11 -0
  53. package/rules/autoconsent/greatschools.json +13 -0
  54. package/rules/autoconsent/hamradio-com.json +13 -0
  55. package/rules/autoconsent/hkdir.json +33 -0
  56. package/rules/autoconsent/hubspot.json +7 -1
  57. package/rules/autoconsent/ibkr.json +16 -0
  58. package/rules/autoconsent/jetpens.json +44 -0
  59. package/rules/autoconsent/jokul-cookie-consent.json +28 -0
  60. package/rules/autoconsent/kanopy.json +88 -0
  61. package/rules/autoconsent/lego.json +27 -5
  62. package/rules/autoconsent/mainlinemenswear.json +39 -0
  63. package/rules/autoconsent/mars-cookies-prompt.json +20 -0
  64. package/rules/autoconsent/money-co-uk.json +21 -0
  65. package/rules/autoconsent/myjobquote.json +19 -0
  66. package/rules/autoconsent/niedziela-pl.json +35 -0
  67. package/rules/autoconsent/nikkeibp.json +10 -0
  68. package/rules/autoconsent/northwell-edu.json +29 -0
  69. package/rules/autoconsent/nts-org-uk.json +20 -0
  70. package/rules/autoconsent/nyu-langone.json +30 -0
  71. package/rules/autoconsent/ofsted-reports-cosmetic.json +13 -0
  72. package/rules/autoconsent/onecom-cookie-banner.json +25 -0
  73. package/rules/autoconsent/optum-legal-banner.json +10 -0
  74. package/rules/autoconsent/optum.json +35 -0
  75. package/rules/autoconsent/oslosportslager.no.json +34 -0
  76. package/rules/autoconsent/planned-parenthood.json +17 -0
  77. package/rules/autoconsent/plex.tv.json +52 -0
  78. package/rules/autoconsent/pornhub-compact-cookie-banner.json +1 -1
  79. package/rules/autoconsent/pornhub.json +1 -1
  80. package/rules/autoconsent/radio-canada.json +28 -0
  81. package/rules/autoconsent/regjeringen.no.json +13 -0
  82. package/rules/autoconsent/reviewsolicitors.json +41 -0
  83. package/rules/autoconsent/ring.json +1 -1
  84. package/rules/autoconsent/roll20.json +50 -0
  85. package/rules/autoconsent/sankei-digital.json +37 -0
  86. package/rules/autoconsent/sirdata.json +8 -3
  87. package/rules/autoconsent/site-notifications-cookie-usage.json +25 -0
  88. package/rules/autoconsent/sparebank1-optin.json +10 -0
  89. package/rules/autoconsent/texas-gov.json +22 -0
  90. package/rules/autoconsent/tiresize.com.json +17 -0
  91. package/rules/autoconsent/tri-table.json +6 -2
  92. package/rules/autoconsent/trillian.json +10 -0
  93. package/rules/autoconsent/tyreseeker.json +44 -0
  94. package/rules/autoconsent/uhcprovider.json +13 -0
  95. package/rules/autoconsent/united-online.json +66 -0
  96. package/rules/autoconsent/unitedutilities.json +52 -0
  97. package/rules/autoconsent/uttings.co.uk.json +22 -0
  98. package/rules/autoconsent/vinnova-se.json +50 -0
  99. package/rules/autoconsent/vortex-consent.json +33 -0
  100. package/rules/autoconsent/watchfinder.json +16 -0
  101. package/rules/compact-rules.json +1 -1
  102. package/rules/rule-index.json +1 -1
  103. package/rules/rules.json +1 -1
  104. package/tests/1pmobile.spec.ts +5 -0
  105. package/tests/amasty-gdpr-hyva.spec.ts +17 -0
  106. package/tests/amazonforum.spec.ts +9 -0
  107. package/tests/asrock.spec.ts +3 -0
  108. package/tests/babylist.spec.ts +3 -0
  109. package/tests/babylonbee.spec.ts +6 -0
  110. package/tests/bathnes-cookie-banner.spec.ts +3 -0
  111. package/tests/chairish.spec.ts +3 -0
  112. package/tests/chemicalbook.spec.ts +5 -0
  113. package/tests/chimnie-co-uk.spec.ts +3 -0
  114. package/tests/complianz-optin.spec.ts +2 -0
  115. package/tests/consentmo.spec.ts +2 -0
  116. package/tests/cookie-pref-form.spec.ts +8 -0
  117. package/tests/cookie-script.spec.ts +5 -0
  118. package/tests/endeavorhealth.spec.ts +3 -0
  119. package/tests/ensighten.spec.ts +7 -1
  120. package/tests/epaas.spec.ts +3 -0
  121. package/tests/fepblue-org.spec.ts +3 -0
  122. package/tests/fnsp-cookieconsent.spec.ts +9 -0
  123. package/tests/fod-fujitv.spec.ts +3 -0
  124. package/tests/fpl-com.spec.ts +3 -0
  125. package/tests/fuqer.spec.ts +3 -0
  126. package/tests/greatschools.spec.ts +3 -0
  127. package/tests/hamradio-com.spec.ts +3 -0
  128. package/tests/hkdir.spec.ts +10 -0
  129. package/tests/hubspot.spec.ts +6 -1
  130. package/tests/ibkr.spec.ts +18 -0
  131. package/tests/jetpens.spec.ts +5 -0
  132. package/tests/jokul-cookie-consent.spec.ts +3 -0
  133. package/tests/kanopy.spec.ts +5 -0
  134. package/tests/mainlinemenswear.spec.ts +3 -0
  135. package/tests/mars-cookies-prompt.spec.ts +3 -0
  136. package/tests/money-co-uk.spec.ts +3 -0
  137. package/tests/myjobquote.spec.ts +5 -0
  138. package/tests/niedziela-pl.spec.ts +3 -0
  139. package/tests/nikkeibp.spec.ts +8 -0
  140. package/tests/northwell-edu.spec.ts +3 -0
  141. package/tests/nts-org-uk.spec.ts +3 -0
  142. package/tests/nyu-langone.spec.ts +3 -0
  143. package/tests/ofsted-reports-cosmetic.spec.ts +3 -0
  144. package/tests/onecom-cookie-banner.spec.ts +5 -0
  145. package/tests/optum-legal-banner.spec.ts +9 -0
  146. package/tests/optum.spec.ts +6 -0
  147. package/tests/oslosportslager.no.spec.ts +3 -0
  148. package/tests/planned-parenthood.spec.ts +3 -0
  149. package/tests/plex.tv.spec.ts +5 -0
  150. package/tests/pornhub.spec.ts +1 -1
  151. package/tests/radio-canada.spec.ts +15 -0
  152. package/tests/regjeringen.no.spec.ts +3 -0
  153. package/tests/reviewsolicitors.spec.ts +3 -0
  154. package/tests/roll20.spec.ts +3 -0
  155. package/tests/sankei-digital.spec.ts +9 -0
  156. package/tests/sirdata.spec.ts +13 -1
  157. package/tests/site-notifications-cookie-usage.spec.ts +3 -0
  158. package/tests/sparebank1-optin.spec.ts +8 -0
  159. package/tests/texas-gov.spec.ts +3 -0
  160. package/tests/tiresize.com.spec.ts +3 -0
  161. package/tests/tri-table.spec.ts +1 -1
  162. package/tests/trillian.spec.ts +15 -0
  163. package/tests/tyreseeker.spec.ts +3 -0
  164. package/tests/uhcprovider.spec.ts +3 -0
  165. package/tests/united-online.spec.ts +4 -0
  166. package/tests/unitedutilities.spec.ts +11 -0
  167. package/tests/uttings.co.uk.spec.ts +3 -0
  168. package/tests/vinnova-se.spec.ts +6 -0
  169. package/tests/vortex-consent.spec.ts +18 -0
  170. package/tests/watchfinder.spec.ts +3 -0
  171. package/tests-wtr/heuristics/get-actionable-popups.ts +41 -7
  172. package/tests-wtr/heuristics/get-potential-popups.test.ts +235 -4
  173. package/tests-wtr/heuristics/heuristic-cmp.ts +16 -1
  174. package/tests-wtr/heuristics/heuristics-utils.test.ts +34 -4
  175. package/rules/generated/auto_CA_plex.tv_ee4.json +0 -40
  176. package/rules/generated/auto_CA_support.plex.tv_gn3.json +0 -40
  177. package/rules/generated/auto_US_interactivebrokers.com_0.json +0 -28
  178. package/tests/generated/auto_CA_plex.tv_ee4.spec.ts +0 -2
  179. package/tests/generated/auto_CA_support.plex.tv_gn3.spec.ts +0 -6
  180. package/tests/generated/auto_US_interactivebrokers.com_0.spec.ts +0 -3
@@ -151,6 +151,7 @@
151
151
  },
152
152
  performanceLoggingEnabled: false,
153
153
  heuristicPopupSearchTimeout: 100,
154
+ enableHeuristicHitTest: false,
154
155
  heuristicMode: "off"
155
156
  // heuristic disabled by default
156
157
  };
@@ -462,8 +463,8 @@
462
463
  // "only necessary" is probably too broad
463
464
  /(?:by continuing.{0,100}cookie)|(?:cookie.{0,100}by continuing)/gi,
464
465
  /(?:by continuing.{0,100}privacy)|(?:privacy.{0,100}by continuing)/gi,
465
- // a single qualifier is common: "optional", "essential", "necessary", "analytics", ...
466
- /we (?:use|serve)(?: \w+)? cookies/gi,
466
+ // up to two qualifiers are common: "optional", "essential", "analytics", "google analytics", "third party", ...
467
+ /we (?:use|serve)(?: \w+){0,2} cookies/gi,
467
468
  /we are using cookies/gi,
468
469
  /use of cookies/gi,
469
470
  /website uses cookies to enhance your browsing experience/gi,
@@ -620,7 +621,10 @@
620
621
  /(?:cookie|クッキー).{0,10}を(?:使用|利用)/gi,
621
622
  // Danish (DA)
622
623
  // e.g. "vi bruger cookies", "hjemmesiden bruger cookies til at ..."
623
- /bruger cookies/gi
624
+ /bruger cookies/gi,
625
+ // Norwegian (NO)
626
+ // e.g. "vi bruker informasjonskapsler", "kan vi bruke valgfrie informasjonskapsler", "nettstedet bruker cookies"
627
+ /\b(?:vi|nettstedet|nettsiden|siden)\s+bruker?\s.{0,60}?(?:informasjonskapsler|cookies)/gi
624
628
  ];
625
629
  var REJECT_PATTERNS_ENGLISH = [
626
630
  // e.g. "reject", "reject all", "reject all cookies", "deny all", "refuse cookies", "decline",
@@ -762,6 +766,14 @@
762
766
  // "nej tak" (no thanks)
763
767
  /^nej(,?\s+tak)?$/is
764
768
  ];
769
+ var REJECT_PATTERNS_NORWEGIAN = [
770
+ // avvis / avslå (reject verbs)
771
+ /(^|\s)(avvis|avslå)/is,
772
+ // (accept/allow/use) only necessary cookies
773
+ /^(godta\s+|tillat\s+|aksepter\s+|bruk\s+)?(kun|bare)\s+(strengt\s+)?nødvendige( (informasjonskapsler|cookies))?$/is,
774
+ // "nei" / "nei takk" (no thanks)
775
+ /^nei(,?\s+takk)?$/is
776
+ ];
765
777
  var REJECT_PATTERNS_CATALAN = [
766
778
  /(^|\s)rebutj/is,
767
779
  "no accepto",
@@ -817,6 +829,7 @@
817
829
  ...REJECT_PATTERNS_SPANISH,
818
830
  ...REJECT_PATTERNS_SWEDISH,
819
831
  ...REJECT_PATTERNS_DANISH,
832
+ ...REJECT_PATTERNS_NORWEGIAN,
820
833
  ...REJECT_PATTERNS_CATALAN,
821
834
  ...REJECT_PATTERNS_GALICIAN,
822
835
  ...REJECT_PATTERNS_BASQUE,
@@ -1326,28 +1339,70 @@
1326
1339
  // lib/heuristics.ts
1327
1340
  var BUTTON_LIKE_ELEMENT_SELECTOR = 'button, input[type="button"], input[type="submit"], a, [role="button"], [class*="button"]';
1328
1341
  var POPUP_SEARCH_MAX_TIME = 100;
1329
- function getActionablePopups(mode = "reject", timeout = POPUP_SEARCH_MAX_TIME) {
1330
- const acceptedLevels = mode === "tier2" ? ["reject", "tier1", "tier2"] : mode === "tier1" ? ["reject", "tier1"] : ["reject"];
1331
- const popups = getPotentialPopups(timeout).filter((popup) => {
1332
- const popupText = popup.text?.trim();
1333
- if (!popupText || isExcludedPopup(popupText) || !hasHeuristicPatterns(popupText)) {
1334
- return false;
1335
- }
1336
- popup.regexClassification = classifyPopup(popup.buttons);
1337
- return popup.regexClassification !== void 0 && acceptedLevels.includes(popup.regexClassification);
1338
- });
1339
- return popups.sort((a, b) => (a.regexClassification ?? "") > (b.regexClassification ?? "") ? 1 : -1);
1342
+ var ACCEPTED_LEVELS = {
1343
+ off: [],
1344
+ reject: ["reject"],
1345
+ tier1: ["reject", "tier1"],
1346
+ tier2: ["reject", "tier1", "tier2"]
1347
+ };
1348
+ function getActionablePopups(mode = "reject", timeout = POPUP_SEARCH_MAX_TIME, hitTest = false) {
1349
+ const acceptedLevels = ACCEPTED_LEVELS[mode];
1350
+ const isActionable = (popup) => acceptedLevels.includes(popup.regexClassification) && isConsentPopup(popup);
1351
+ const popups = searchPopups(timeout, hitTest, DEFAULT_HIT_TEST_GRID, true).filter(isActionable);
1352
+ return popups.sort((a, b) => classificationRank(a) - classificationRank(b));
1340
1353
  }
1341
- function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME) {
1354
+ function searchPopups(timeout, hitTest, hitTestGrid, stopOnReject = false) {
1342
1355
  if (!isTopFrame()) {
1343
- if (window.parent && window.parent !== window.top) {
1344
- return [];
1345
- }
1346
- const frameElement = getFramePopupElement(document);
1347
- const popup = frameElement && toPopupData(frameElement);
1348
- return popup ? [popup] : [];
1356
+ return getFramePopups();
1349
1357
  }
1350
1358
  const deadline = performance.now() + timeout;
1359
+ const hitTestPopups = hitTest ? findPopupsByHitTest(deadline, hitTestGrid) : [];
1360
+ if (stopOnReject && hitTestPopups.some((p) => p.regexClassification === "reject" && isConsentPopup(p))) {
1361
+ return hitTestPopups;
1362
+ }
1363
+ const walkPopups = collectPositionedPopups(deadline);
1364
+ return [...walkPopups, ...mergeWalkOverlaps(hitTestPopups, walkPopups)];
1365
+ }
1366
+ var consentPopups = /* @__PURE__ */ new WeakMap();
1367
+ function classificationRank(popup) {
1368
+ return ACCEPTED_LEVELS.tier2.indexOf(popup.regexClassification);
1369
+ }
1370
+ function isConsentPopup(popup) {
1371
+ if (!ACCEPTED_LEVELS.tier2.includes(popup.regexClassification)) {
1372
+ return false;
1373
+ }
1374
+ let result = consentPopups.get(popup);
1375
+ if (result === void 0) {
1376
+ const popupText = popup.text.trim();
1377
+ result = !!popupText && !isExcludedPopup(popupText) && hasHeuristicPatterns(popupText);
1378
+ consentPopups.set(popup, result);
1379
+ }
1380
+ return result;
1381
+ }
1382
+ function mergeWalkOverlaps(hitTestPopups, walkPopups) {
1383
+ return hitTestPopups.filter((h) => {
1384
+ const hitIsConsent = isConsentPopup(h);
1385
+ const absorbs = (p) => !hitIsConsent || isConsentPopup(p) && classificationRank(p) <= classificationRank(h);
1386
+ const walkPopup = walkPopups.find((p) => composedOverlaps(p.element, h.element) && absorbs(p));
1387
+ if (walkPopup) {
1388
+ (walkPopup.hitPoints ??= []).push(...h.hitPoints ?? []);
1389
+ }
1390
+ return !walkPopup;
1391
+ });
1392
+ }
1393
+ function getFramePopups() {
1394
+ if (window.parent && window.parent !== window.top) {
1395
+ return [];
1396
+ }
1397
+ const frameElement = getFramePopupElement(document);
1398
+ const popup = frameElement && toPopupData(frameElement);
1399
+ if (!popup) {
1400
+ return [];
1401
+ }
1402
+ popup.source = "frame";
1403
+ return [popup];
1404
+ }
1405
+ function collectPositionedPopups(deadline) {
1351
1406
  const { elements, shadowHosts } = getPopupLikeElements(deadline);
1352
1407
  const potentialPopups = [];
1353
1408
  const expanded = [];
@@ -1363,17 +1418,86 @@
1363
1418
  if (root !== popup) {
1364
1419
  expanded.push(root.element);
1365
1420
  }
1421
+ root.source = "walk";
1366
1422
  potentialPopups.push(root);
1367
1423
  }
1368
1424
  return potentialPopups.filter((p) => !expanded.some((root) => root !== p.element && composedContains(root, p.element)));
1369
1425
  }
1426
+ var DEFAULT_HIT_TEST_GRID = {
1427
+ columns: [0.1, 0.5, 0.9],
1428
+ // banners sit along the top and bottom edges and modals in the middle; rows 150px in added almost nothing in crawls
1429
+ edgeRows: [30, 90]
1430
+ };
1431
+ function findPopupsByHitTest(deadline, grid) {
1432
+ const climbed = /* @__PURE__ */ new Map();
1433
+ const found = [];
1434
+ const toPopup = (el) => el.querySelector(TEXT_ENTRY_SELECTOR) ? null : toPopupRoot(el);
1435
+ for (const [x, y] of getHitTestPoints(grid)) {
1436
+ if (performance.now() > deadline) {
1437
+ return found;
1438
+ }
1439
+ const path = [];
1440
+ let reached = null;
1441
+ const shouldStop = (el) => {
1442
+ if (climbed.has(el)) {
1443
+ reached = climbed.get(el) ?? null;
1444
+ return true;
1445
+ }
1446
+ const overlapped = found.find((p) => composedOverlaps(p.element, el));
1447
+ if (overlapped) {
1448
+ reached = composedContains(overlapped.element, el) ? overlapped : null;
1449
+ return true;
1450
+ }
1451
+ path.push(el);
1452
+ return false;
1453
+ };
1454
+ const hit = closestHTMLElement(deepElementFromPoint(x, y));
1455
+ const popup = climbToPopup(hit, MAX_HIT_TEST_CLIMB_DEPTH, toPopup, shouldStop);
1456
+ if (popup) {
1457
+ popup.source = "hitTest";
1458
+ found.push(popup);
1459
+ reached = popup;
1460
+ }
1461
+ for (const el of path) {
1462
+ climbed.set(el, reached);
1463
+ }
1464
+ if (reached) {
1465
+ (reached.hitPoints ??= []).push([x, y]);
1466
+ }
1467
+ }
1468
+ return found;
1469
+ }
1470
+ function getHitTestPoints(grid) {
1471
+ const { innerWidth: width, innerHeight: height } = window;
1472
+ const rows = [...grid.edgeRows, height / 2, ...grid.edgeRows.map((y) => height - y)].map((y) => Math.round(y));
1473
+ const ys = [...new Set(rows)].filter((y) => y >= 0 && y < height);
1474
+ return ys.flatMap((y) => grid.columns.map((column) => [Math.round(column * width), y]));
1475
+ }
1476
+ var TEXT_ENTRY_SELECTOR = 'textarea, input:not([type]), input[type="text"], input[type="email"], input[type="password"], input[type="search"], input[type="tel"], input[type="number"], input[type="url"]';
1477
+ function deepElementFromPoint(x, y) {
1478
+ let el = document.elementFromPoint(x, y);
1479
+ while (el?.shadowRoot) {
1480
+ const inner = el.shadowRoot.elementFromPoint(x, y);
1481
+ if (!inner || inner === el) {
1482
+ break;
1483
+ }
1484
+ el = inner;
1485
+ }
1486
+ return el;
1487
+ }
1488
+ function closestHTMLElement(el) {
1489
+ while (el && !(el instanceof HTMLElement)) {
1490
+ el = el.parentElement;
1491
+ }
1492
+ return el;
1493
+ }
1370
1494
  function toPopupData(el, shadowHosts, text = getElementText(el)) {
1371
1495
  if (!text) {
1372
1496
  return null;
1373
1497
  }
1374
1498
  const buttons = getButtonData(el, shadowHosts);
1375
1499
  classifyButtons(buttons);
1376
- return { text, element: el, buttons };
1500
+ return { text, element: el, buttons, regexClassification: classifyPopup(buttons) };
1377
1501
  }
1378
1502
  function hasPopupButtons(text, buttons) {
1379
1503
  return !isOnlyButtonText(text, buttons) && buttons.some((b) => b.regexClassification !== "other");
@@ -1385,26 +1509,34 @@
1385
1509
  }
1386
1510
  return rest.replace(/\s+/g, "").length < 10;
1387
1511
  }
1512
+ function toPopupRoot(el, shadowHosts) {
1513
+ const text = getElementText(el);
1514
+ if (!text || !hasHeuristicPatterns(text)) {
1515
+ return null;
1516
+ }
1517
+ const popup = toPopupData(el, shadowHosts, text);
1518
+ return popup && hasPopupButtons(text, popup.buttons) ? popup : null;
1519
+ }
1388
1520
  var MAX_FRAGMENT_EXPANSION_DEPTH = 6;
1521
+ var MAX_HIT_TEST_CLIMB_DEPTH = 8;
1522
+ function climbToPopup(el, maxDepth, toPopup, shouldStop) {
1523
+ for (let depth = 0; el && depth < maxDepth; depth++, el = getComposedParent(el)) {
1524
+ if (shouldStop?.(el) || el === document.body || el === document.documentElement || el.getBoundingClientRect().height > window.innerHeight) {
1525
+ return null;
1526
+ }
1527
+ const popup = toPopup(el);
1528
+ if (popup) {
1529
+ return popup;
1530
+ }
1531
+ }
1532
+ return null;
1533
+ }
1389
1534
  function expandPopupFragment(popup, shadowHosts) {
1390
1535
  if (hasHeuristicPatterns(popup.text) && hasPopupButtons(popup.text, popup.buttons)) {
1391
1536
  return popup;
1392
1537
  }
1393
- let el = getComposedParent(popup.element);
1394
- for (let depth = 0; el && depth < MAX_FRAGMENT_EXPANSION_DEPTH; depth++, el = getComposedParent(el)) {
1395
- if (el === document.body || el === document.documentElement || el.getBoundingClientRect().height > window.innerHeight) {
1396
- break;
1397
- }
1398
- const text = getElementText(el);
1399
- if (!text || !hasHeuristicPatterns(text)) {
1400
- continue;
1401
- }
1402
- const root = toPopupData(el, shadowHosts, text);
1403
- if (root && hasPopupButtons(text, root.buttons)) {
1404
- return root;
1405
- }
1406
- }
1407
- return popup;
1538
+ const parent = getComposedParent(popup.element);
1539
+ return climbToPopup(parent, MAX_FRAGMENT_EXPANSION_DEPTH, (el) => toPopupRoot(el, shadowHosts)) ?? popup;
1408
1540
  }
1409
1541
  function getComposedParent(el) {
1410
1542
  if (el.parentElement) {
@@ -1435,6 +1567,9 @@
1435
1567
  function isRenderedTextElement(el) {
1436
1568
  return el instanceof HTMLElement && !["STYLE", "SCRIPT", "TEMPLATE", "NOSCRIPT", "LINK", "SLOT"].includes(el.tagName) && window.getComputedStyle(el).display !== "none";
1437
1569
  }
1570
+ function composedOverlaps(a, b) {
1571
+ return composedContains(a, b) || composedContains(b, a);
1572
+ }
1438
1573
  function composedContains(container, node) {
1439
1574
  if (container.contains(node)) {
1440
1575
  return true;
@@ -1926,7 +2061,11 @@
1926
2061
  async detectCmp() {
1927
2062
  await new Promise((resolve) => setTimeout(resolve, 0));
1928
2063
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorStart");
1929
- this.popups = getActionablePopups(this.mode, this.autoconsent.config.heuristicPopupSearchTimeout);
2064
+ this.popups = getActionablePopups(
2065
+ this.mode,
2066
+ this.autoconsent.config.heuristicPopupSearchTimeout,
2067
+ this.autoconsent.config.enableHeuristicHitTest
2068
+ );
1930
2069
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorEnd");
1931
2070
  this.autoconsent.config.performanceLoggingEnabled && performance.measure("heuristicDetector", "heuristicDetectorStart", "heuristicDetectorEnd");
1932
2071
  if (this.popups.length > 0) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Autoconsent",
4
- "version": "2026.10.27",
4
+ "version": "2026.10.29",
5
5
  "background": {
6
6
  "scripts": [
7
7
  "background.bundle.js"
@@ -800,6 +800,7 @@
800
800
  },
801
801
  performanceLoggingEnabled: false,
802
802
  heuristicPopupSearchTimeout: 100,
803
+ enableHeuristicHitTest: false,
803
804
  heuristicMode: "off"
804
805
  // heuristic disabled by default
805
806
  };