@duckduckgo/autoconsent 16.59.0 → 16.60.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 (184) hide show
  1. package/.agents/lib/regional-testing/harness.mjs +4 -172
  2. package/.agents/skills/oxylabs-testing/SKILL.md +4 -4
  3. package/.agents/skills/oxylabs-testing/scripts/oxylabs.mjs +17 -164
  4. package/.agents/skills/oxylabs-testing/scripts/oxylabs.test.mjs +6 -3
  5. package/.agents/skills/verify-pr/SKILL.md +1 -1
  6. package/AGENTS.md +1 -1
  7. package/CHANGELOG.md +83 -0
  8. package/Jenkinsfile +36 -1
  9. package/dist/addon-firefox/background.bundle.js +21 -0
  10. package/dist/addon-firefox/compact-rules.json +1 -1
  11. package/dist/addon-firefox/content.bundle.js +253 -93
  12. package/dist/addon-firefox/manifest.json +1 -1
  13. package/dist/addon-firefox/rule-index.json +1 -1
  14. package/dist/addon-firefox/rules.json +1 -1
  15. package/dist/addon-mv3/background.bundle.js +21 -0
  16. package/dist/addon-mv3/compact-rules.json +1 -1
  17. package/dist/addon-mv3/content.bundle.js +253 -93
  18. package/dist/addon-mv3/manifest.json +1 -1
  19. package/dist/addon-mv3/rule-index.json +1 -1
  20. package/dist/addon-mv3/rules.json +1 -1
  21. package/dist/autoconsent.cjs.js +253 -93
  22. package/dist/autoconsent.esm.js +253 -93
  23. package/dist/autoconsent.playwright.js +253 -93
  24. package/dist/autoconsent.standalone.js +254 -94
  25. package/dist/heuristics.cjs.js +29 -3
  26. package/dist/heuristics.mjs +28 -3
  27. package/dist/types/cmps/onetrust.d.ts +1 -0
  28. package/dist/types/eval-snippets.d.ts +3 -0
  29. package/dist/types/heuristic-classify.d.ts +1 -0
  30. package/dist/types/heuristics.d.ts +10 -1
  31. package/lib/cmps/onetrust.ts +15 -0
  32. package/lib/eval-snippets.ts +21 -0
  33. package/lib/heuristic-classify.ts +6 -0
  34. package/lib/heuristic-patterns.ts +26 -1
  35. package/lib/heuristics.ts +256 -104
  36. package/package.json +1 -1
  37. package/playwright/isolated-world.mjs +217 -0
  38. package/playwright/oxylabs.mjs +196 -0
  39. package/playwright/runner.ts +183 -39
  40. package/playwright.config.ts +4 -1
  41. package/rules/autoconsent/aeolservice.json +39 -0
  42. package/rules/autoconsent/amazon-music.json +13 -0
  43. package/rules/autoconsent/annunci69.json +19 -0
  44. package/rules/autoconsent/arbeidsplassen-nav-no.json +33 -0
  45. package/rules/autoconsent/att.json +36 -0
  46. package/rules/autoconsent/auctionet.json +39 -0
  47. package/rules/autoconsent/autodoc.json +18 -0
  48. package/rules/autoconsent/bbc.com-orb-cosmetic.json +34 -0
  49. package/rules/autoconsent/bbc.com.json +8 -5
  50. package/rules/autoconsent/bokadirekt.se.json +50 -0
  51. package/rules/autoconsent/bouvet-cookiepanel.json +29 -0
  52. package/rules/autoconsent/consent-studio.json +25 -13
  53. package/rules/autoconsent/cookielegit.json +21 -0
  54. package/rules/autoconsent/cookieprompter.json +21 -0
  55. package/rules/autoconsent/cookiereports.json +10 -0
  56. package/rules/autoconsent/cookietractor.json +37 -0
  57. package/rules/autoconsent/de-volksbank.json +14 -0
  58. package/rules/autoconsent/dpgmedia-nl.json +20 -8
  59. package/rules/autoconsent/eon.pl.json +18 -0
  60. package/rules/autoconsent/eroticiracconti-it.json +13 -0
  61. package/rules/autoconsent/ersatzteile-24.json +49 -0
  62. package/rules/autoconsent/gravito.json +23 -16
  63. package/rules/autoconsent/hacon-next-gen.json +41 -0
  64. package/rules/autoconsent/ing-de.json +31 -0
  65. package/rules/autoconsent/interia.json +1 -4
  66. package/rules/autoconsent/intesasanpaolo.json +37 -0
  67. package/rules/autoconsent/jobindex.json +40 -0
  68. package/rules/autoconsent/kaspersky-cookies-notification.json +9 -0
  69. package/rules/autoconsent/lantmateriet.json +13 -0
  70. package/rules/autoconsent/mainichi.json +39 -0
  71. package/rules/autoconsent/mango.json +56 -0
  72. package/rules/autoconsent/nederlandseloterij.json +35 -0
  73. package/rules/autoconsent/nordea.json +21 -0
  74. package/rules/autoconsent/nordnet.json +33 -0
  75. package/rules/autoconsent/ok.json +4 -12
  76. package/rules/autoconsent/onlineradiobox.json +35 -0
  77. package/rules/autoconsent/pe-cookies.json +30 -0
  78. package/rules/autoconsent/pensionsmyndigheten.json +13 -0
  79. package/rules/autoconsent/pro-euclp-cookies-box.json +31 -0
  80. package/rules/autoconsent/pubfactory.json +45 -0
  81. package/rules/autoconsent/puesc-gov-pl.json +37 -0
  82. package/rules/autoconsent/rambler.json +30 -0
  83. package/rules/autoconsent/rejestr-io.json +36 -0
  84. package/rules/autoconsent/schumacher.com.json +53 -0
  85. package/rules/autoconsent/sitevision.json +18 -1
  86. package/rules/autoconsent/skatteverket-se.json +33 -0
  87. package/rules/autoconsent/smile-insurances.json +13 -0
  88. package/rules/autoconsent/svt.se.json +7 -7
  89. package/rules/autoconsent/temu.json +12 -16
  90. package/rules/autoconsent/thecode.json +21 -0
  91. package/rules/autoconsent/uk-parliament.json +19 -0
  92. package/rules/autoconsent/wirtualna-polska.json +13 -10
  93. package/rules/autoconsent/xenforo-cookie-consent.json +62 -0
  94. package/rules/autoconsent/zorginstituut-cookiewarning.json +33 -0
  95. package/rules/compact-rules.json +1 -1
  96. package/rules/rule-index.json +1 -1
  97. package/rules/rules.json +1 -1
  98. package/tests/aeolservice.spec.ts +5 -0
  99. package/tests/amazon-music.spec.ts +9 -0
  100. package/tests/annunci69.spec.ts +5 -0
  101. package/tests/arbeidsplassen-nav-no.spec.ts +3 -0
  102. package/tests/att.spec.ts +3 -0
  103. package/tests/auctionet.spec.ts +3 -0
  104. package/tests/autodoc.spec.ts +8 -0
  105. package/tests/bbc.com-orb-cosmetic.spec.ts +8 -0
  106. package/tests/bbc.com.spec.ts +11 -9
  107. package/tests/bokadirekt.se.spec.ts +3 -0
  108. package/tests/bouvet-cookiepanel.spec.ts +9 -0
  109. package/tests/consent-studio.spec.ts +5 -0
  110. package/tests/cookiebot.spec.ts +1 -0
  111. package/tests/cookielegit.spec.ts +3 -0
  112. package/tests/cookieprompter.spec.ts +9 -0
  113. package/tests/cookiereports.spec.ts +13 -0
  114. package/tests/cookietractor.spec.ts +12 -0
  115. package/tests/de-volksbank.spec.ts +5 -0
  116. package/tests/dpgmedia-nl.spec.ts +15 -1
  117. package/tests/eon.pl.spec.ts +3 -0
  118. package/tests/eroticiracconti-it.spec.ts +3 -0
  119. package/tests/ersatzteile-24.spec.ts +6 -0
  120. package/tests/gravito.spec.ts +7 -1
  121. package/tests/hacon-next-gen.spec.ts +6 -0
  122. package/tests/ing-de.spec.ts +3 -0
  123. package/tests/interia.spec.ts +1 -1
  124. package/tests/intesasanpaolo.spec.ts +3 -0
  125. package/tests/jobindex.spec.ts +3 -0
  126. package/tests/kaspersky-cookies-notification.spec.ts +8 -0
  127. package/tests/lantmateriet.spec.ts +3 -0
  128. package/tests/mainichi.spec.ts +10 -0
  129. package/tests/mango.spec.ts +3 -0
  130. package/tests/nederlandseloterij.spec.ts +9 -0
  131. package/tests/nordea.spec.ts +6 -0
  132. package/tests/nordnet.spec.ts +8 -0
  133. package/tests/onlineradiobox.spec.ts +5 -0
  134. package/tests/pe-cookies.spec.ts +6 -0
  135. package/tests/pensionsmyndigheten.spec.ts +3 -0
  136. package/tests/pro-euclp-cookies-box.spec.ts +8 -0
  137. package/tests/pubfactory.spec.ts +10 -0
  138. package/tests/puesc-gov-pl.spec.ts +3 -0
  139. package/tests/rambler.spec.ts +3 -0
  140. package/tests/rejestr-io.spec.ts +3 -0
  141. package/tests/schumacher.com.spec.ts +6 -0
  142. package/tests/sitevision-cookie-consent.spec.ts +1 -0
  143. package/tests/sitevision.spec.ts +7 -0
  144. package/tests/skatteverket-se.spec.ts +3 -0
  145. package/tests/smile-insurances.spec.ts +6 -0
  146. package/tests/svt.se.spec.ts +1 -3
  147. package/tests/temu.spec.ts +3 -1
  148. package/tests/thecode.spec.ts +3 -0
  149. package/tests/uk-parliament.spec.ts +8 -0
  150. package/tests/wirtualna-polska.spec.ts +6 -1
  151. package/tests/xenforo-cookie-consent.spec.ts +6 -0
  152. package/tests/zorginstituut-cookiewarning.spec.ts +10 -0
  153. package/tests-wtr/heuristics/get-potential-popups.test.ts +330 -9
  154. package/tests-wtr/heuristics/heuristics-utils.test.ts +98 -0
  155. package/rules/autoconsent/twitter.json +0 -34
  156. package/rules/generated/auto_CH_autodoc.de_krd.json +0 -40
  157. package/rules/generated/auto_CH_svtplay.se_d39.json +0 -40
  158. package/rules/generated/auto_GB_parliamentlive.tv_r3v.json +0 -28
  159. package/rules/generated/auto_NL_asnbank.nl_e28.json +0 -40
  160. package/rules/generated/auto_NL_eurojackpot.nederlandseloterij.nl_7hr.json +0 -40
  161. package/rules/generated/auto_NL_lotto.nederlandseloterij.nl_7c8.json +0 -40
  162. package/rules/generated/auto_NL_nederlandseloterij.nl_b60.json +0 -40
  163. package/rules/generated/auto_NL_staatsloterij.nederlandseloterij.nl_197.json +0 -40
  164. package/rules/generated/auto_NO_arronet.se_vo5.json +0 -40
  165. package/rules/generated/auto_NO_liseberg.se_qy4.json +0 -40
  166. package/rules/generated/auto_NO_nordnet.se_2en.json +0 -40
  167. package/rules/generated/auto_NO_regeringen.se_uhv.json +0 -40
  168. package/rules/generated/auto_NO_svtplay.se_ujn.json +0 -40
  169. package/rules/generated/auto_NO_uu.se_eea.json +0 -40
  170. package/tests/generated/auto_CH_autodoc.de_krd.spec.ts +0 -2
  171. package/tests/generated/auto_CH_svtplay.se_d39.spec.ts +0 -2
  172. package/tests/generated/auto_GB_parliamentlive.tv_r3v.spec.ts +0 -6
  173. package/tests/generated/auto_NL_asnbank.nl_e28.spec.ts +0 -6
  174. package/tests/generated/auto_NL_eurojackpot.nederlandseloterij.nl_7hr.spec.ts +0 -6
  175. package/tests/generated/auto_NL_lotto.nederlandseloterij.nl_7c8.spec.ts +0 -6
  176. package/tests/generated/auto_NL_nederlandseloterij.nl_b60.spec.ts +0 -6
  177. package/tests/generated/auto_NL_staatsloterij.nederlandseloterij.nl_197.spec.ts +0 -6
  178. package/tests/generated/auto_NO_arronet.se_vo5.spec.ts +0 -2
  179. package/tests/generated/auto_NO_liseberg.se_qy4.spec.ts +0 -2
  180. package/tests/generated/auto_NO_nordnet.se_2en.spec.ts +0 -2
  181. package/tests/generated/auto_NO_regeringen.se_uhv.spec.ts +0 -6
  182. package/tests/generated/auto_NO_svtplay.se_ujn.spec.ts +0 -2
  183. package/tests/generated/auto_NO_uu.se_eea.spec.ts +0 -2
  184. package/tests/twitter.spec.ts +0 -10
@@ -372,6 +372,27 @@ var snippets = {
372
372
  EVAL_COOKIEFIRST_1: () => document.querySelectorAll("button[data-cookiefirst-accent-color=true][role=checkbox]:not([disabled])").forEach((i) => i.getAttribute("aria-checked") === "true" && i.click()) || true,
373
373
  EVAL_COOKIEINFORMATION_0: () => CookieInformation.declineAllCategories() || true,
374
374
  EVAL_COOKIEINFORMATION_1: () => CookieInformation.submitAllCategories() || true,
375
+ // banner button order and meaning are configurable per site, so set the consent levels through the public API
376
+ EVAL_COOKIEREPORTS_OPT_OUT: () => {
377
+ const cr = window._cookiereports;
378
+ if (typeof cr?.saveConsent !== "function") return false;
379
+ cr.saveConsent({ 1: true, 2: false, 3: false, 4: false, 5: false });
380
+ cr.processConsent("banner", { trigger: "ok" });
381
+ cr.hideBanner();
382
+ return true;
383
+ },
384
+ EVAL_COOKIEREPORTS_OPT_IN: () => {
385
+ const cr = window._cookiereports;
386
+ if (typeof cr?.saveConsent !== "function") return false;
387
+ cr.saveConsent({ 1: true, 2: true, 3: true, 4: true, 5: true });
388
+ cr.processConsent("banner", { trigger: "ok" });
389
+ cr.hideBanner();
390
+ return true;
391
+ },
392
+ EVAL_COOKIEREPORTS_TEST: () => {
393
+ const levels = window._cookiereports?.loadConsent?.();
394
+ return !!levels && Object.keys(levels).length > 0 && Object.values(levels).every((v) => !v);
395
+ },
375
396
  // turn the single-button form into a decline submission, same fields as the CMP's own decline form
376
397
  EVAL_EE_CONSENT_DECLINE_0: () => document.querySelectorAll('#cookieConsentFormAccept input[name^="ee:cookies_"]').forEach((i) => i.value = "n") || true,
377
398
  EVAL_ETSY_0: () => document.querySelectorAll(".gdpr-overlay-body input").forEach((toggle) => {
@@ -622,10 +643,20 @@ var DETECT_PATTERNS = [
622
643
  // "файлы cookie" / "cookie-файлы" / "куки-файлы" in any phrasing
623
644
  /(?:файл[а-яё]*[\s-]+(?:cookie|куки)|(?:cookie|куки)[\s-]+файл[а-яё]*)/gi,
624
645
  // Italian (IT)
625
- /usiamo.{0,20}cookie/gi,
646
+ // e.g. "usiamo i cookie", "utilizziamo cookie tecnici", "questo sito utilizza i cookie", "noi e i nostri partner utilizziamo i cookie"
647
+ /(?:usiamo|utilizziamo).{0,20}cookie/gi,
648
+ /sito.{0,50}(?:usa|utilizza).{0,20}cookie/gi,
649
+ /noi e i nostri.{0,20}partner.{0,100}cookie/gi,
650
+ /(?:archiviare|memorizzare) e\/o accedere a informazioni su un dispositivo/gi,
651
+ // Swedish (SV)
652
+ // e.g. "vi använder cookies", "vi använder spårningstekniker såsom cookies", "webbplatsen använder kakor"
653
+ /(?:vi|webbplatsen|webbplats|sajten|sidan)\s+använder.{0,60}(?:cookies|kakor)/gi,
626
654
  // Japanese (JA)
627
655
  // e.g. "当サイトはCookieを使用しています", "クッキー(Cookie)を利用しています"
628
- /(?:cookie|クッキー).{0,10}を(?:使用|利用)/gi
656
+ /(?:cookie|クッキー).{0,10}を(?:使用|利用)/gi,
657
+ // Danish (DA)
658
+ // e.g. "vi bruger cookies", "hjemmesiden bruger cookies til at ..."
659
+ /bruger cookies/gi
629
660
  ];
630
661
  var REJECT_PATTERNS_ENGLISH = [
631
662
  // e.g. "reject", "reject all", "reject all cookies", "deny all", "refuse cookies", "decline",
@@ -759,6 +790,14 @@ var REJECT_PATTERNS_SWEDISH = [
759
790
  "till\xE5t inte cookies",
760
791
  "jag accepterar endast grundl\xE4ggande kakor"
761
792
  ];
793
+ var REJECT_PATTERNS_DANISH = [
794
+ // afvis / afvis alle / afvis alle cookies (reject verbs)
795
+ /(^|\s)afvis/is,
796
+ // (accept/allow) only necessary cookies
797
+ /^(accepter\s+|tillad\s+)?(kun|udelukkende)\s+(strengt\s+)?nødvendige( cookies)?$/is,
798
+ // "nej tak" (no thanks)
799
+ /^nej(,?\s+tak)?$/is
800
+ ];
762
801
  var REJECT_PATTERNS_CATALAN = [/(^|\s)rebutj/is, "no accepto", "no, gr\xE0cies"];
763
802
  var REJECT_PATTERNS_GALICIAN = [/(^|\s)rexeitar/is];
764
803
  var REJECT_PATTERNS_BASQUE = [/(^|\s)(baztertu|ukatu)/is];
@@ -805,6 +844,7 @@ var REJECT_PATTERNS = [
805
844
  ...REJECT_PATTERNS_BRAZILIAN_PORTUGUESE,
806
845
  ...REJECT_PATTERNS_SPANISH,
807
846
  ...REJECT_PATTERNS_SWEDISH,
847
+ ...REJECT_PATTERNS_DANISH,
808
848
  ...REJECT_PATTERNS_CATALAN,
809
849
  ...REJECT_PATTERNS_GALICIAN,
810
850
  ...REJECT_PATTERNS_BASQUE,
@@ -1154,7 +1194,8 @@ var ACCEPT_PATTERNS = [
1154
1194
  "kabul et",
1155
1195
  // Italian (IT)
1156
1196
  "accetta",
1157
- "accetta tutti i cookie"
1197
+ "accetta tutti i cookie",
1198
+ "accetto tutti i cookie"
1158
1199
  ];
1159
1200
  var ACKNOWLEDGE_PATTERNS = [
1160
1201
  // close / dismiss the banner/dialog/message (multilingual). The negative lookahead avoids
@@ -1230,6 +1271,10 @@ function checkHeuristicPatterns(allText, detectPatterns = DETECT_PATTERNS) {
1230
1271
  }
1231
1272
  return { patterns, snippets: snippets2 };
1232
1273
  }
1274
+ function hasHeuristicPatterns(allText, detectPatterns = DETECT_PATTERNS) {
1275
+ const text = allText.slice(0, TEXT_LIMIT);
1276
+ return detectPatterns.some((p) => text.search(p) !== -1);
1277
+ }
1233
1278
  function classifyButtons(buttons) {
1234
1279
  for (const button of buttons) {
1235
1280
  button.regexClassification = classifyButtonTextRegex(button.text);
@@ -1306,57 +1351,135 @@ var BUTTON_LIKE_ELEMENT_SELECTOR = 'button, input[type="button"], input[type="su
1306
1351
  var POPUP_SEARCH_MAX_TIME = 100;
1307
1352
  function getActionablePopups(mode = "reject", timeout = POPUP_SEARCH_MAX_TIME) {
1308
1353
  const acceptedLevels = mode === "tier2" ? ["reject", "tier1", "tier2"] : mode === "tier1" ? ["reject", "tier1"] : ["reject"];
1309
- if (acceptedLevels.length === 0) {
1310
- return [];
1311
- }
1312
- const popups = getPotentialPopups(timeout);
1313
- const result = popups.reduce((acc, popup) => {
1354
+ const popups = getPotentialPopups(timeout).filter((popup) => {
1314
1355
  const popupText = popup.text?.trim();
1315
- if (popupText) {
1316
- if (isExcludedPopup(popupText)) {
1317
- return acc;
1318
- }
1319
- const { patterns } = checkHeuristicPatterns(popupText);
1320
- if (patterns.length > 0) {
1321
- classifyButtons(popup.buttons);
1322
- popup.regexClassification = classifyPopup(popup.buttons);
1323
- acc.push({
1324
- ...popup
1325
- });
1326
- }
1356
+ if (!popupText || isExcludedPopup(popupText) || !hasHeuristicPatterns(popupText)) {
1357
+ return false;
1327
1358
  }
1328
- return acc;
1329
- }, []);
1330
- return result.filter((popup) => popup.regexClassification !== void 0 && acceptedLevels.includes(popup.regexClassification)).sort((a, b) => (a.regexClassification ?? "") > (b.regexClassification ?? "") ? 1 : -1);
1359
+ popup.regexClassification = classifyPopup(popup.buttons);
1360
+ return popup.regexClassification !== void 0 && acceptedLevels.includes(popup.regexClassification);
1361
+ });
1362
+ return popups.sort((a, b) => (a.regexClassification ?? "") > (b.regexClassification ?? "") ? 1 : -1);
1331
1363
  }
1332
1364
  function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME) {
1333
- const isFramed = !isTopFrame();
1334
- if (isFramed && window.parent && window.parent !== window.top) {
1335
- return [];
1336
- }
1337
- return collectPotentialPopups(isFramed, timeout);
1338
- }
1339
- function collectPotentialPopups(isFramed, timeout = POPUP_SEARCH_MAX_TIME) {
1340
- let elements = [];
1341
- if (!isFramed) {
1342
- elements = getPopupLikeElements(timeout);
1343
- } else {
1344
- const doc = document.body || document.documentElement;
1345
- if (doc && isElementVisible(doc) && doc.innerText) {
1346
- elements.push(doc);
1365
+ if (!isTopFrame()) {
1366
+ if (window.parent && window.parent !== window.top) {
1367
+ return [];
1347
1368
  }
1369
+ const frameElement = getFramePopupElement(document);
1370
+ const popup = frameElement && toPopupData(frameElement);
1371
+ return popup ? [popup] : [];
1348
1372
  }
1373
+ const deadline = performance.now() + timeout;
1374
+ const { elements, shadowHosts } = getPopupLikeElements(deadline);
1349
1375
  const potentialPopups = [];
1376
+ const expanded = [];
1350
1377
  for (const el of elements) {
1351
- if (el.innerText) {
1352
- potentialPopups.push({
1353
- text: el.innerText,
1354
- element: el,
1355
- buttons: getButtonData(el)
1356
- });
1378
+ const popup = toPopupData(el, shadowHosts);
1379
+ if (!popup) {
1380
+ continue;
1381
+ }
1382
+ const root = performance.now() > deadline ? popup : expandPopupFragment(popup, shadowHosts);
1383
+ if (potentialPopups.some((p) => p.element === root.element)) {
1384
+ continue;
1385
+ }
1386
+ if (root !== popup) {
1387
+ expanded.push(root.element);
1388
+ }
1389
+ potentialPopups.push(root);
1390
+ }
1391
+ return potentialPopups.filter((p) => !expanded.some((root) => root !== p.element && composedContains(root, p.element)));
1392
+ }
1393
+ function toPopupData(el, shadowHosts, text = getElementText(el)) {
1394
+ if (!text) {
1395
+ return null;
1396
+ }
1397
+ const buttons = getButtonData(el, shadowHosts);
1398
+ classifyButtons(buttons);
1399
+ return { text, element: el, buttons };
1400
+ }
1401
+ function hasPopupButtons(text, buttons) {
1402
+ return !isOnlyButtonText(text, buttons) && buttons.some((b) => b.regexClassification !== "other");
1403
+ }
1404
+ function isOnlyButtonText(text, buttons) {
1405
+ let rest = text;
1406
+ for (const button of buttons) {
1407
+ rest = rest.replace(button.text, "");
1408
+ }
1409
+ return rest.replace(/\s+/g, "").length < 10;
1410
+ }
1411
+ var MAX_FRAGMENT_EXPANSION_DEPTH = 6;
1412
+ function expandPopupFragment(popup, shadowHosts) {
1413
+ if (hasHeuristicPatterns(popup.text) && hasPopupButtons(popup.text, popup.buttons)) {
1414
+ return popup;
1415
+ }
1416
+ let el = getComposedParent(popup.element);
1417
+ for (let depth = 0; el && depth < MAX_FRAGMENT_EXPANSION_DEPTH; depth++, el = getComposedParent(el)) {
1418
+ if (el === document.body || el === document.documentElement || el.getBoundingClientRect().height > window.innerHeight) {
1419
+ break;
1420
+ }
1421
+ const text = getElementText(el);
1422
+ if (!text || !hasHeuristicPatterns(text)) {
1423
+ continue;
1424
+ }
1425
+ const root = toPopupData(el, shadowHosts, text);
1426
+ if (root && hasPopupButtons(text, root.buttons)) {
1427
+ return root;
1428
+ }
1429
+ }
1430
+ return popup;
1431
+ }
1432
+ function getComposedParent(el) {
1433
+ if (el.parentElement) {
1434
+ return el.parentElement;
1435
+ }
1436
+ const parent = el.parentNode;
1437
+ return parent instanceof ShadowRoot && parent.host instanceof HTMLElement ? parent.host : null;
1438
+ }
1439
+ function getFramePopupElement(doc) {
1440
+ const el = doc.body || doc.documentElement;
1441
+ const win = doc.defaultView;
1442
+ if (!el || !win || win.innerWidth === 0 || win.innerHeight === 0 || !isStyleVisible(el)) {
1443
+ return null;
1444
+ }
1445
+ return getElementText(el).trim() ? el : null;
1446
+ }
1447
+ function isStyleVisible(el) {
1448
+ return typeof el.checkVisibility !== "function" || el.checkVisibility({ checkVisibilityCSS: true });
1449
+ }
1450
+ function getElementText(el) {
1451
+ const text = el.innerText || "";
1452
+ if (!el.shadowRoot) {
1453
+ return text;
1454
+ }
1455
+ const shadowTexts = Array.from(el.shadowRoot.children, (child) => isRenderedTextElement(child) ? getElementText(child) : "");
1456
+ return [text, ...shadowTexts].filter((t) => t.trim()).join("\n");
1457
+ }
1458
+ function isRenderedTextElement(el) {
1459
+ return el instanceof HTMLElement && !["STYLE", "SCRIPT", "TEMPLATE", "NOSCRIPT", "LINK", "SLOT"].includes(el.tagName) && window.getComputedStyle(el).display !== "none";
1460
+ }
1461
+ function composedContains(container, node) {
1462
+ if (container.contains(node)) {
1463
+ return true;
1464
+ }
1465
+ let root = node.getRootNode();
1466
+ while (root instanceof ShadowRoot) {
1467
+ if (container.contains(root.host)) {
1468
+ return true;
1357
1469
  }
1470
+ root = root.host.getRootNode();
1471
+ }
1472
+ return false;
1473
+ }
1474
+ function isPopupCandidateVisible(el) {
1475
+ return isElementVisible(el) && isStyleVisible(el) && isOnScreen(el);
1476
+ }
1477
+ function isOnScreen(el) {
1478
+ const rect = el.getBoundingClientRect();
1479
+ if (rect.width === 0 || rect.height === 0) {
1480
+ return window.getComputedStyle(el).overflow === "visible" && Array.from(el.children).some((child) => isOnScreen(child));
1358
1481
  }
1359
- return potentialPopups;
1482
+ return rect.bottom > 0 && rect.right > 0 && rect.top < window.innerHeight && rect.left < window.innerWidth;
1360
1483
  }
1361
1484
  function isDialogLikeElement(node) {
1362
1485
  if (node.tagName === "DIALOG" && node.hasAttribute("open")) {
@@ -1367,72 +1490,95 @@ function isDialogLikeElement(node) {
1367
1490
  }
1368
1491
  return false;
1369
1492
  }
1370
- function getPopupLikeElements(timeout = POPUP_SEARCH_MAX_TIME) {
1371
- const startTime = performance.now();
1372
- const walker = document.createTreeWalker(
1373
- document.documentElement,
1374
- NodeFilter.SHOW_ELEMENT,
1375
- // visit only element nodes
1376
- {
1377
- acceptNode(node) {
1378
- if (node.tagName === "BODY") {
1379
- return NodeFilter.FILTER_SKIP;
1380
- }
1381
- if (isElementVisible(node)) {
1382
- const cssPosition = window.getComputedStyle(node).position;
1383
- if (cssPosition === "fixed" || cssPosition === "sticky") {
1384
- return NodeFilter.FILTER_ACCEPT;
1493
+ function getPopupLikeElements(deadline) {
1494
+ const found = [];
1495
+ const shadowHosts = [];
1496
+ const roots = [document.documentElement];
1497
+ for (let i = 0; i < roots.length; i++) {
1498
+ if (i > 0 && performance.now() > deadline) {
1499
+ break;
1500
+ }
1501
+ const walker = document.createTreeWalker(
1502
+ roots[i],
1503
+ NodeFilter.SHOW_ELEMENT,
1504
+ // visit only element nodes
1505
+ {
1506
+ acceptNode(node) {
1507
+ if (node.shadowRoot) {
1508
+ shadowHosts.push(node);
1509
+ roots.push(node.shadowRoot);
1510
+ }
1511
+ if (node.tagName === "BODY") {
1512
+ return NodeFilter.FILTER_SKIP;
1385
1513
  }
1386
- if (isDialogLikeElement(node)) {
1514
+ const style = window.getComputedStyle(node);
1515
+ if (style.display === "none") {
1516
+ return NodeFilter.FILTER_REJECT;
1517
+ }
1518
+ const cssPosition = style.position;
1519
+ if ((cssPosition === "fixed" || cssPosition === "sticky" || isDialogLikeElement(node)) && isPopupCandidateVisible(node)) {
1387
1520
  return NodeFilter.FILTER_ACCEPT;
1388
1521
  }
1522
+ if (performance.now() > deadline) {
1523
+ return NodeFilter.FILTER_REJECT;
1524
+ }
1525
+ return NodeFilter.FILTER_SKIP;
1389
1526
  }
1390
- if (performance.now() - startTime > timeout) {
1391
- return NodeFilter.FILTER_REJECT;
1392
- }
1393
- return NodeFilter.FILTER_SKIP;
1394
1527
  }
1528
+ );
1529
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
1530
+ found.push(node);
1395
1531
  }
1396
- );
1397
- const found = [];
1398
- for (let node = walker.nextNode(); node; node = walker.nextNode()) {
1399
- found.push(node);
1400
1532
  }
1401
- return excludeContainers(found.filter((element) => element.innerText?.trim()));
1533
+ return { elements: excludeContainers(found.filter((element) => getElementText(element).trim())), shadowHosts };
1402
1534
  }
1403
- function getButtonData(el) {
1404
- const actionableButtons = excludeContainers(getButtonLikeElements(el)).filter(
1405
- (b) => isElementVisible(b) && !isDisabled(b) && (b.innerText?.trim() || // <input> values do not appear in innerText
1406
- b instanceof HTMLInputElement && ["submit", "button"].includes(b.type) && b.value?.trim())
1407
- );
1408
- return actionableButtons.map((b) => ({
1409
- text: (b.innerText || b.textContent || "").trim() || b.value?.trim() || "",
1410
- element: b
1411
- }));
1535
+ function getButtonData(el, shadowHosts) {
1536
+ const buttons = [];
1537
+ for (const b of excludeContainers(getButtonLikeElements(el, shadowHosts))) {
1538
+ if (!isElementVisible(b) || isDisabled(b)) {
1539
+ continue;
1540
+ }
1541
+ const text = getElementText(b).trim() || // <input> values do not appear in innerText
1542
+ (b instanceof HTMLInputElement && ["submit", "button"].includes(b.type) ? b.value?.trim() : "");
1543
+ if (text) {
1544
+ buttons.push({ text, element: b });
1545
+ }
1546
+ }
1547
+ return buttons;
1412
1548
  }
1413
- function getButtonLikeElements(el) {
1414
- return Array.from(el.querySelectorAll(BUTTON_LIKE_ELEMENT_SELECTOR));
1549
+ function getButtonLikeElements(el, shadowHosts = findShadowHosts(el)) {
1550
+ const buttons = Array.from(el.querySelectorAll(BUTTON_LIKE_ELEMENT_SELECTOR));
1551
+ for (const host of shadowHosts) {
1552
+ if (composedContains(el, host)) {
1553
+ buttons.push(...host.shadowRoot.querySelectorAll(BUTTON_LIKE_ELEMENT_SELECTOR));
1554
+ }
1555
+ }
1556
+ return buttons;
1557
+ }
1558
+ function findShadowHosts(root) {
1559
+ const hosts = [];
1560
+ const candidates = root instanceof HTMLElement ? [root, ...root.querySelectorAll("*")] : root.querySelectorAll("*");
1561
+ for (const el of candidates) {
1562
+ if (el.shadowRoot) {
1563
+ hosts.push(el, ...findShadowHosts(el.shadowRoot));
1564
+ }
1565
+ }
1566
+ return hosts;
1415
1567
  }
1416
1568
  function isDisabled(el) {
1417
1569
  return "disabled" in el && Boolean(el.disabled) || el.hasAttribute("disabled");
1418
1570
  }
1419
1571
  function excludeContainers(elements) {
1420
- const results = [];
1421
- if (elements.length > 0) {
1422
- for (let i = elements.length - 1; i >= 0; i--) {
1423
- let container = false;
1424
- for (let j = 0; j < elements.length; j++) {
1425
- if (i !== j && elements[i].contains(elements[j])) {
1426
- container = true;
1427
- break;
1428
- }
1429
- }
1430
- if (!container) {
1431
- results.push(elements[i]);
1572
+ const candidates = new Set(elements);
1573
+ const containers = /* @__PURE__ */ new Set();
1574
+ for (const el of elements) {
1575
+ for (let node = el.parentNode; node; node = node.parentNode || (node instanceof ShadowRoot ? node.host : null)) {
1576
+ if (candidates.has(node)) {
1577
+ containers.add(node);
1432
1578
  }
1433
1579
  }
1434
1580
  }
1435
- return results;
1581
+ return elements.filter((el) => !containers.has(el)).reverse();
1436
1582
  }
1437
1583
 
1438
1584
  // lib/cmps/base.ts
@@ -2226,6 +2372,18 @@ var Onetrust = class extends AutoConsentCMPBase {
2226
2372
  get isCosmetic() {
2227
2373
  return false;
2228
2374
  }
2375
+ // Some sites pre-render the banner before the SDK loads, and clicks before then may be lost.
2376
+ // Sites that copy the markup without the SDK never get #onetrust-style, so only wait when the page loads the SDK.
2377
+ async waitForSdk() {
2378
+ if (this.elementExists("#onetrust-style")) {
2379
+ return;
2380
+ }
2381
+ const sdkRef = /cookielaw\.org|otSDKStub|otBannerSdk/;
2382
+ const sdkExpected = Array.from(document.scripts).some((s) => sdkRef.test(s.src) || !s.src && sdkRef.test(s.textContent || ""));
2383
+ if (sdkExpected) {
2384
+ await this.waitForElement("#onetrust-style", 1e4);
2385
+ }
2386
+ }
2229
2387
  async detectCmp() {
2230
2388
  return this.elementExists("#onetrust-banner-sdk") || this.elementVisible("#onetrust-pc-sdk", "any");
2231
2389
  }
@@ -2234,6 +2392,7 @@ var Onetrust = class extends AutoConsentCMPBase {
2234
2392
  }
2235
2393
  async optOut() {
2236
2394
  await this.wait(500);
2395
+ await this.waitForSdk();
2237
2396
  if (this.elementVisible("#onetrust-reject-all-handler", "any")) {
2238
2397
  return await this.click("#onetrust-reject-all-handler");
2239
2398
  }
@@ -2273,6 +2432,7 @@ var Onetrust = class extends AutoConsentCMPBase {
2273
2432
  return true;
2274
2433
  }
2275
2434
  async optIn() {
2435
+ await this.waitForSdk();
2276
2436
  return await this.click("#onetrust-accept-btn-handler,#accept-recommended-btn-handler,.js-accept-cookies");
2277
2437
  }
2278
2438
  async test() {