@duckduckgo/autoconsent 16.59.0 → 16.61.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 (216) 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 +107 -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/airbnb.json +32 -0
  43. package/rules/autoconsent/amazon-music.json +13 -0
  44. package/rules/autoconsent/annunci69.json +19 -0
  45. package/rules/autoconsent/appconsent.json +6 -1
  46. package/rules/autoconsent/arbeidsplassen-nav-no.json +33 -0
  47. package/rules/autoconsent/att.json +36 -0
  48. package/rules/autoconsent/auctionet.json +39 -0
  49. package/rules/autoconsent/autodoc.json +18 -0
  50. package/rules/autoconsent/bbc.com-orb-cosmetic.json +34 -0
  51. package/rules/autoconsent/bbc.com.json +8 -5
  52. package/rules/autoconsent/bokadirekt.se.json +50 -0
  53. package/rules/autoconsent/bouvet-cookiepanel.json +29 -0
  54. package/rules/autoconsent/com-consent-layer.json +10 -0
  55. package/rules/autoconsent/consent-studio.json +25 -13
  56. package/rules/autoconsent/cookielegit.json +21 -0
  57. package/rules/autoconsent/cookieprompter.json +21 -0
  58. package/rules/autoconsent/cookiereports.json +10 -0
  59. package/rules/autoconsent/cookietractor.json +37 -0
  60. package/rules/autoconsent/de-volksbank.json +14 -0
  61. package/rules/autoconsent/dpgmedia-nl.json +20 -8
  62. package/rules/autoconsent/eon.pl.json +18 -0
  63. package/rules/autoconsent/eroticiracconti-it.json +13 -0
  64. package/rules/autoconsent/ersatzteile-24.json +49 -0
  65. package/rules/autoconsent/fastweb-cookie-center.json +13 -0
  66. package/rules/autoconsent/futurabolzano-it.json +35 -0
  67. package/rules/autoconsent/gobasic.json +10 -0
  68. package/rules/autoconsent/gravito.json +23 -16
  69. package/rules/autoconsent/hacon-next-gen.json +41 -0
  70. package/rules/autoconsent/ing-de.json +31 -0
  71. package/rules/autoconsent/interia.json +1 -4
  72. package/rules/autoconsent/intesasanpaolo.json +37 -0
  73. package/rules/autoconsent/jobindex.json +40 -0
  74. package/rules/autoconsent/kaspersky-cookies-notification.json +9 -0
  75. package/rules/autoconsent/lantmateriet.json +13 -0
  76. package/rules/autoconsent/livsmedelsverket.json +18 -0
  77. package/rules/autoconsent/mainichi.json +39 -0
  78. package/rules/autoconsent/mango.json +56 -0
  79. package/rules/autoconsent/nederlandseloterij.json +35 -0
  80. package/rules/autoconsent/newsbreak.json +16 -0
  81. package/rules/autoconsent/nhsapp.json +10 -0
  82. package/rules/autoconsent/nordea.json +21 -0
  83. package/rules/autoconsent/nordnet.json +33 -0
  84. package/rules/autoconsent/ok.json +4 -12
  85. package/rules/autoconsent/onlineradiobox.json +35 -0
  86. package/rules/autoconsent/pe-cookies.json +30 -0
  87. package/rules/autoconsent/pensionsmyndigheten.json +13 -0
  88. package/rules/autoconsent/pro-euclp-cookies-box.json +31 -0
  89. package/rules/autoconsent/pubfactory.json +45 -0
  90. package/rules/autoconsent/puesc-gov-pl.json +37 -0
  91. package/rules/autoconsent/rambler.json +30 -0
  92. package/rules/autoconsent/rejestr-io.json +36 -0
  93. package/rules/autoconsent/sas.json +6 -6
  94. package/rules/autoconsent/scb.se.json +13 -0
  95. package/rules/autoconsent/schumacher.com.json +53 -0
  96. package/rules/autoconsent/sitevision.json +18 -1
  97. package/rules/autoconsent/skatteverket-se.json +33 -0
  98. package/rules/autoconsent/smile-insurances.json +13 -0
  99. package/rules/autoconsent/studentapan.json +16 -0
  100. package/rules/autoconsent/svt.se.json +7 -7
  101. package/rules/autoconsent/temu.json +12 -16
  102. package/rules/autoconsent/thecode.json +21 -0
  103. package/rules/autoconsent/tv2reg-cookie-notice.json +39 -0
  104. package/rules/autoconsent/uk-parliament.json +19 -0
  105. package/rules/autoconsent/wirtualna-polska.json +13 -10
  106. package/rules/autoconsent/xenforo-cookie-consent.json +62 -0
  107. package/rules/autoconsent/zorginstituut-cookiewarning.json +33 -0
  108. package/rules/compact-rules.json +1 -1
  109. package/rules/rule-index.json +1 -1
  110. package/rules/rules.json +1 -1
  111. package/tests/aeolservice.spec.ts +5 -0
  112. package/tests/airbnb.spec.ts +3 -0
  113. package/tests/amazon-music.spec.ts +9 -0
  114. package/tests/annunci69.spec.ts +5 -0
  115. package/tests/appconsent.spec.ts +6 -0
  116. package/tests/arbeidsplassen-nav-no.spec.ts +3 -0
  117. package/tests/att.spec.ts +3 -0
  118. package/tests/auctionet.spec.ts +3 -0
  119. package/tests/autodoc.spec.ts +8 -0
  120. package/tests/bbc.com-orb-cosmetic.spec.ts +8 -0
  121. package/tests/bbc.com.spec.ts +11 -9
  122. package/tests/bokadirekt.se.spec.ts +3 -0
  123. package/tests/bouvet-cookiepanel.spec.ts +9 -0
  124. package/tests/com-consent-layer.spec.ts +6 -0
  125. package/tests/consent-studio.spec.ts +5 -0
  126. package/tests/cookiebot.spec.ts +1 -0
  127. package/tests/cookielegit.spec.ts +3 -0
  128. package/tests/cookieprompter.spec.ts +9 -0
  129. package/tests/cookiereports.spec.ts +13 -0
  130. package/tests/cookietractor.spec.ts +12 -0
  131. package/tests/de-volksbank.spec.ts +5 -0
  132. package/tests/dpgmedia-nl.spec.ts +15 -1
  133. package/tests/eon.pl.spec.ts +3 -0
  134. package/tests/eroticiracconti-it.spec.ts +3 -0
  135. package/tests/ersatzteile-24.spec.ts +6 -0
  136. package/tests/fastweb-cookie-center.spec.ts +3 -0
  137. package/tests/futurabolzano-it.spec.ts +3 -0
  138. package/tests/gobasic.spec.ts +3 -0
  139. package/tests/gravito.spec.ts +7 -1
  140. package/tests/hacon-next-gen.spec.ts +6 -0
  141. package/tests/ing-de.spec.ts +3 -0
  142. package/tests/interia.spec.ts +1 -1
  143. package/tests/intesasanpaolo.spec.ts +3 -0
  144. package/tests/jobindex.spec.ts +3 -0
  145. package/tests/kaspersky-cookies-notification.spec.ts +8 -0
  146. package/tests/lantmateriet.spec.ts +3 -0
  147. package/tests/livsmedelsverket.spec.ts +5 -0
  148. package/tests/mainichi.spec.ts +10 -0
  149. package/tests/mango.spec.ts +3 -0
  150. package/tests/nederlandseloterij.spec.ts +9 -0
  151. package/tests/newsbreak.spec.ts +3 -0
  152. package/tests/nhsapp.spec.ts +3 -0
  153. package/tests/nordea.spec.ts +6 -0
  154. package/tests/nordnet.spec.ts +8 -0
  155. package/tests/onlineradiobox.spec.ts +5 -0
  156. package/tests/pe-cookies.spec.ts +6 -0
  157. package/tests/pensionsmyndigheten.spec.ts +3 -0
  158. package/tests/pro-euclp-cookies-box.spec.ts +8 -0
  159. package/tests/pubfactory.spec.ts +10 -0
  160. package/tests/puesc-gov-pl.spec.ts +3 -0
  161. package/tests/rambler.spec.ts +3 -0
  162. package/tests/rejestr-io.spec.ts +3 -0
  163. package/tests/sas.spec.ts +1 -1
  164. package/tests/scb.se.spec.ts +4 -0
  165. package/tests/schumacher.com.spec.ts +6 -0
  166. package/tests/sitevision-cookie-consent.spec.ts +1 -0
  167. package/tests/sitevision.spec.ts +7 -0
  168. package/tests/skatteverket-se.spec.ts +3 -0
  169. package/tests/smile-insurances.spec.ts +6 -0
  170. package/tests/studentapan.spec.ts +3 -0
  171. package/tests/svt.se.spec.ts +1 -3
  172. package/tests/temu.spec.ts +3 -1
  173. package/tests/thecode.spec.ts +3 -0
  174. package/tests/tv2reg-cookie-notice.spec.ts +16 -0
  175. package/tests/uk-parliament.spec.ts +8 -0
  176. package/tests/wirtualna-polska.spec.ts +6 -1
  177. package/tests/xenforo-cookie-consent.spec.ts +6 -0
  178. package/tests/zorginstituut-cookiewarning.spec.ts +10 -0
  179. package/tests-wtr/heuristics/get-potential-popups.test.ts +330 -9
  180. package/tests-wtr/heuristics/heuristics-utils.test.ts +98 -0
  181. package/rules/autoconsent/twitter.json +0 -34
  182. package/rules/generated/auto_AU_flysas.com_r1s.json +0 -40
  183. package/rules/generated/auto_CH_airbnb.de_83z.json +0 -40
  184. package/rules/generated/auto_CH_autodoc.de_krd.json +0 -40
  185. package/rules/generated/auto_CH_svtplay.se_d39.json +0 -40
  186. package/rules/generated/auto_GB_parliamentlive.tv_r3v.json +0 -28
  187. package/rules/generated/auto_NL_asnbank.nl_e28.json +0 -40
  188. package/rules/generated/auto_NL_eurojackpot.nederlandseloterij.nl_7hr.json +0 -40
  189. package/rules/generated/auto_NL_lotto.nederlandseloterij.nl_7c8.json +0 -40
  190. package/rules/generated/auto_NL_nederlandseloterij.nl_b60.json +0 -40
  191. package/rules/generated/auto_NL_staatsloterij.nederlandseloterij.nl_197.json +0 -40
  192. package/rules/generated/auto_NO_arronet.se_vo5.json +0 -40
  193. package/rules/generated/auto_NO_liseberg.se_qy4.json +0 -40
  194. package/rules/generated/auto_NO_nordnet.se_2en.json +0 -40
  195. package/rules/generated/auto_NO_regeringen.se_uhv.json +0 -40
  196. package/rules/generated/auto_NO_svtplay.se_ujn.json +0 -40
  197. package/rules/generated/auto_NO_uu.se_eea.json +0 -40
  198. package/rules/generated/auto_US_newsbreak.com_0.json +0 -28
  199. package/tests/generated/auto_AU_flysas.com_r1s.spec.ts +0 -2
  200. package/tests/generated/auto_CH_airbnb.de_83z.spec.ts +0 -2
  201. package/tests/generated/auto_CH_autodoc.de_krd.spec.ts +0 -2
  202. package/tests/generated/auto_CH_svtplay.se_d39.spec.ts +0 -2
  203. package/tests/generated/auto_GB_parliamentlive.tv_r3v.spec.ts +0 -6
  204. package/tests/generated/auto_NL_asnbank.nl_e28.spec.ts +0 -6
  205. package/tests/generated/auto_NL_eurojackpot.nederlandseloterij.nl_7hr.spec.ts +0 -6
  206. package/tests/generated/auto_NL_lotto.nederlandseloterij.nl_7c8.spec.ts +0 -6
  207. package/tests/generated/auto_NL_nederlandseloterij.nl_b60.spec.ts +0 -6
  208. package/tests/generated/auto_NL_staatsloterij.nederlandseloterij.nl_197.spec.ts +0 -6
  209. package/tests/generated/auto_NO_arronet.se_vo5.spec.ts +0 -2
  210. package/tests/generated/auto_NO_liseberg.se_qy4.spec.ts +0 -2
  211. package/tests/generated/auto_NO_nordnet.se_2en.spec.ts +0 -2
  212. package/tests/generated/auto_NO_regeringen.se_uhv.spec.ts +0 -6
  213. package/tests/generated/auto_NO_svtplay.se_ujn.spec.ts +0 -2
  214. package/tests/generated/auto_NO_uu.se_eea.spec.ts +0 -2
  215. package/tests/generated/auto_US_newsbreak.com_0.spec.ts +0 -2
  216. package/tests/twitter.spec.ts +0 -10
@@ -336,6 +336,27 @@
336
336
  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,
337
337
  EVAL_COOKIEINFORMATION_0: () => CookieInformation.declineAllCategories() || true,
338
338
  EVAL_COOKIEINFORMATION_1: () => CookieInformation.submitAllCategories() || true,
339
+ // banner button order and meaning are configurable per site, so set the consent levels through the public API
340
+ EVAL_COOKIEREPORTS_OPT_OUT: () => {
341
+ const cr = window._cookiereports;
342
+ if (typeof cr?.saveConsent !== "function") return false;
343
+ cr.saveConsent({ 1: true, 2: false, 3: false, 4: false, 5: false });
344
+ cr.processConsent("banner", { trigger: "ok" });
345
+ cr.hideBanner();
346
+ return true;
347
+ },
348
+ EVAL_COOKIEREPORTS_OPT_IN: () => {
349
+ const cr = window._cookiereports;
350
+ if (typeof cr?.saveConsent !== "function") return false;
351
+ cr.saveConsent({ 1: true, 2: true, 3: true, 4: true, 5: true });
352
+ cr.processConsent("banner", { trigger: "ok" });
353
+ cr.hideBanner();
354
+ return true;
355
+ },
356
+ EVAL_COOKIEREPORTS_TEST: () => {
357
+ const levels = window._cookiereports?.loadConsent?.();
358
+ return !!levels && Object.keys(levels).length > 0 && Object.values(levels).every((v) => !v);
359
+ },
339
360
  // turn the single-button form into a decline submission, same fields as the CMP's own decline form
340
361
  EVAL_EE_CONSENT_DECLINE_0: () => document.querySelectorAll('#cookieConsentFormAccept input[name^="ee:cookies_"]').forEach((i) => i.value = "n") || true,
341
362
  EVAL_ETSY_0: () => document.querySelectorAll(".gdpr-overlay-body input").forEach((toggle) => {
@@ -586,10 +607,20 @@
586
607
  // "файлы cookie" / "cookie-файлы" / "куки-файлы" in any phrasing
587
608
  /(?:файл[а-яё]*[\s-]+(?:cookie|куки)|(?:cookie|куки)[\s-]+файл[а-яё]*)/gi,
588
609
  // Italian (IT)
589
- /usiamo.{0,20}cookie/gi,
610
+ // e.g. "usiamo i cookie", "utilizziamo cookie tecnici", "questo sito utilizza i cookie", "noi e i nostri partner utilizziamo i cookie"
611
+ /(?:usiamo|utilizziamo).{0,20}cookie/gi,
612
+ /sito.{0,50}(?:usa|utilizza).{0,20}cookie/gi,
613
+ /noi e i nostri.{0,20}partner.{0,100}cookie/gi,
614
+ /(?:archiviare|memorizzare) e\/o accedere a informazioni su un dispositivo/gi,
615
+ // Swedish (SV)
616
+ // e.g. "vi använder cookies", "vi använder spårningstekniker såsom cookies", "webbplatsen använder kakor"
617
+ /(?:vi|webbplatsen|webbplats|sajten|sidan)\s+använder.{0,60}(?:cookies|kakor)/gi,
590
618
  // Japanese (JA)
591
619
  // e.g. "当サイトはCookieを使用しています", "クッキー(Cookie)を利用しています"
592
- /(?:cookie|クッキー).{0,10}を(?:使用|利用)/gi
620
+ /(?:cookie|クッキー).{0,10}を(?:使用|利用)/gi,
621
+ // Danish (DA)
622
+ // e.g. "vi bruger cookies", "hjemmesiden bruger cookies til at ..."
623
+ /bruger cookies/gi
593
624
  ];
594
625
  var REJECT_PATTERNS_ENGLISH = [
595
626
  // e.g. "reject", "reject all", "reject all cookies", "deny all", "refuse cookies", "decline",
@@ -723,6 +754,14 @@
723
754
  "till\xE5t inte cookies",
724
755
  "jag accepterar endast grundl\xE4ggande kakor"
725
756
  ];
757
+ var REJECT_PATTERNS_DANISH = [
758
+ // afvis / afvis alle / afvis alle cookies (reject verbs)
759
+ /(^|\s)afvis/is,
760
+ // (accept/allow) only necessary cookies
761
+ /^(accepter\s+|tillad\s+)?(kun|udelukkende)\s+(strengt\s+)?nødvendige( cookies)?$/is,
762
+ // "nej tak" (no thanks)
763
+ /^nej(,?\s+tak)?$/is
764
+ ];
726
765
  var REJECT_PATTERNS_CATALAN = [/(^|\s)rebutj/is, "no accepto", "no, gr\xE0cies"];
727
766
  var REJECT_PATTERNS_GALICIAN = [/(^|\s)rexeitar/is];
728
767
  var REJECT_PATTERNS_BASQUE = [/(^|\s)(baztertu|ukatu)/is];
@@ -769,6 +808,7 @@
769
808
  ...REJECT_PATTERNS_BRAZILIAN_PORTUGUESE,
770
809
  ...REJECT_PATTERNS_SPANISH,
771
810
  ...REJECT_PATTERNS_SWEDISH,
811
+ ...REJECT_PATTERNS_DANISH,
772
812
  ...REJECT_PATTERNS_CATALAN,
773
813
  ...REJECT_PATTERNS_GALICIAN,
774
814
  ...REJECT_PATTERNS_BASQUE,
@@ -1118,7 +1158,8 @@
1118
1158
  "kabul et",
1119
1159
  // Italian (IT)
1120
1160
  "accetta",
1121
- "accetta tutti i cookie"
1161
+ "accetta tutti i cookie",
1162
+ "accetto tutti i cookie"
1122
1163
  ];
1123
1164
  var ACKNOWLEDGE_PATTERNS = [
1124
1165
  // close / dismiss the banner/dialog/message (multilingual). The negative lookahead avoids
@@ -1194,6 +1235,10 @@
1194
1235
  }
1195
1236
  return { patterns, snippets: snippets2 };
1196
1237
  }
1238
+ function hasHeuristicPatterns(allText, detectPatterns = DETECT_PATTERNS) {
1239
+ const text = allText.slice(0, TEXT_LIMIT);
1240
+ return detectPatterns.some((p) => text.search(p) !== -1);
1241
+ }
1197
1242
  function classifyButtons(buttons) {
1198
1243
  for (const button of buttons) {
1199
1244
  button.regexClassification = classifyButtonTextRegex(button.text);
@@ -1270,57 +1315,135 @@
1270
1315
  var POPUP_SEARCH_MAX_TIME = 100;
1271
1316
  function getActionablePopups(mode = "reject", timeout = POPUP_SEARCH_MAX_TIME) {
1272
1317
  const acceptedLevels = mode === "tier2" ? ["reject", "tier1", "tier2"] : mode === "tier1" ? ["reject", "tier1"] : ["reject"];
1273
- if (acceptedLevels.length === 0) {
1274
- return [];
1275
- }
1276
- const popups = getPotentialPopups(timeout);
1277
- const result = popups.reduce((acc, popup) => {
1318
+ const popups = getPotentialPopups(timeout).filter((popup) => {
1278
1319
  const popupText = popup.text?.trim();
1279
- if (popupText) {
1280
- if (isExcludedPopup(popupText)) {
1281
- return acc;
1282
- }
1283
- const { patterns } = checkHeuristicPatterns(popupText);
1284
- if (patterns.length > 0) {
1285
- classifyButtons(popup.buttons);
1286
- popup.regexClassification = classifyPopup(popup.buttons);
1287
- acc.push({
1288
- ...popup
1289
- });
1290
- }
1320
+ if (!popupText || isExcludedPopup(popupText) || !hasHeuristicPatterns(popupText)) {
1321
+ return false;
1291
1322
  }
1292
- return acc;
1293
- }, []);
1294
- return result.filter((popup) => popup.regexClassification !== void 0 && acceptedLevels.includes(popup.regexClassification)).sort((a, b) => (a.regexClassification ?? "") > (b.regexClassification ?? "") ? 1 : -1);
1323
+ popup.regexClassification = classifyPopup(popup.buttons);
1324
+ return popup.regexClassification !== void 0 && acceptedLevels.includes(popup.regexClassification);
1325
+ });
1326
+ return popups.sort((a, b) => (a.regexClassification ?? "") > (b.regexClassification ?? "") ? 1 : -1);
1295
1327
  }
1296
1328
  function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME) {
1297
- const isFramed = !isTopFrame();
1298
- if (isFramed && window.parent && window.parent !== window.top) {
1299
- return [];
1300
- }
1301
- return collectPotentialPopups(isFramed, timeout);
1302
- }
1303
- function collectPotentialPopups(isFramed, timeout = POPUP_SEARCH_MAX_TIME) {
1304
- let elements = [];
1305
- if (!isFramed) {
1306
- elements = getPopupLikeElements(timeout);
1307
- } else {
1308
- const doc = document.body || document.documentElement;
1309
- if (doc && isElementVisible(doc) && doc.innerText) {
1310
- elements.push(doc);
1329
+ if (!isTopFrame()) {
1330
+ if (window.parent && window.parent !== window.top) {
1331
+ return [];
1311
1332
  }
1333
+ const frameElement = getFramePopupElement(document);
1334
+ const popup = frameElement && toPopupData(frameElement);
1335
+ return popup ? [popup] : [];
1312
1336
  }
1337
+ const deadline = performance.now() + timeout;
1338
+ const { elements, shadowHosts } = getPopupLikeElements(deadline);
1313
1339
  const potentialPopups = [];
1340
+ const expanded = [];
1314
1341
  for (const el of elements) {
1315
- if (el.innerText) {
1316
- potentialPopups.push({
1317
- text: el.innerText,
1318
- element: el,
1319
- buttons: getButtonData(el)
1320
- });
1342
+ const popup = toPopupData(el, shadowHosts);
1343
+ if (!popup) {
1344
+ continue;
1345
+ }
1346
+ const root = performance.now() > deadline ? popup : expandPopupFragment(popup, shadowHosts);
1347
+ if (potentialPopups.some((p) => p.element === root.element)) {
1348
+ continue;
1349
+ }
1350
+ if (root !== popup) {
1351
+ expanded.push(root.element);
1352
+ }
1353
+ potentialPopups.push(root);
1354
+ }
1355
+ return potentialPopups.filter((p) => !expanded.some((root) => root !== p.element && composedContains(root, p.element)));
1356
+ }
1357
+ function toPopupData(el, shadowHosts, text = getElementText(el)) {
1358
+ if (!text) {
1359
+ return null;
1360
+ }
1361
+ const buttons = getButtonData(el, shadowHosts);
1362
+ classifyButtons(buttons);
1363
+ return { text, element: el, buttons };
1364
+ }
1365
+ function hasPopupButtons(text, buttons) {
1366
+ return !isOnlyButtonText(text, buttons) && buttons.some((b) => b.regexClassification !== "other");
1367
+ }
1368
+ function isOnlyButtonText(text, buttons) {
1369
+ let rest = text;
1370
+ for (const button of buttons) {
1371
+ rest = rest.replace(button.text, "");
1372
+ }
1373
+ return rest.replace(/\s+/g, "").length < 10;
1374
+ }
1375
+ var MAX_FRAGMENT_EXPANSION_DEPTH = 6;
1376
+ function expandPopupFragment(popup, shadowHosts) {
1377
+ if (hasHeuristicPatterns(popup.text) && hasPopupButtons(popup.text, popup.buttons)) {
1378
+ return popup;
1379
+ }
1380
+ let el = getComposedParent(popup.element);
1381
+ for (let depth = 0; el && depth < MAX_FRAGMENT_EXPANSION_DEPTH; depth++, el = getComposedParent(el)) {
1382
+ if (el === document.body || el === document.documentElement || el.getBoundingClientRect().height > window.innerHeight) {
1383
+ break;
1384
+ }
1385
+ const text = getElementText(el);
1386
+ if (!text || !hasHeuristicPatterns(text)) {
1387
+ continue;
1388
+ }
1389
+ const root = toPopupData(el, shadowHosts, text);
1390
+ if (root && hasPopupButtons(text, root.buttons)) {
1391
+ return root;
1392
+ }
1393
+ }
1394
+ return popup;
1395
+ }
1396
+ function getComposedParent(el) {
1397
+ if (el.parentElement) {
1398
+ return el.parentElement;
1399
+ }
1400
+ const parent = el.parentNode;
1401
+ return parent instanceof ShadowRoot && parent.host instanceof HTMLElement ? parent.host : null;
1402
+ }
1403
+ function getFramePopupElement(doc) {
1404
+ const el = doc.body || doc.documentElement;
1405
+ const win = doc.defaultView;
1406
+ if (!el || !win || win.innerWidth === 0 || win.innerHeight === 0 || !isStyleVisible(el)) {
1407
+ return null;
1408
+ }
1409
+ return getElementText(el).trim() ? el : null;
1410
+ }
1411
+ function isStyleVisible(el) {
1412
+ return typeof el.checkVisibility !== "function" || el.checkVisibility({ checkVisibilityCSS: true });
1413
+ }
1414
+ function getElementText(el) {
1415
+ const text = el.innerText || "";
1416
+ if (!el.shadowRoot) {
1417
+ return text;
1418
+ }
1419
+ const shadowTexts = Array.from(el.shadowRoot.children, (child) => isRenderedTextElement(child) ? getElementText(child) : "");
1420
+ return [text, ...shadowTexts].filter((t) => t.trim()).join("\n");
1421
+ }
1422
+ function isRenderedTextElement(el) {
1423
+ return el instanceof HTMLElement && !["STYLE", "SCRIPT", "TEMPLATE", "NOSCRIPT", "LINK", "SLOT"].includes(el.tagName) && window.getComputedStyle(el).display !== "none";
1424
+ }
1425
+ function composedContains(container, node) {
1426
+ if (container.contains(node)) {
1427
+ return true;
1428
+ }
1429
+ let root = node.getRootNode();
1430
+ while (root instanceof ShadowRoot) {
1431
+ if (container.contains(root.host)) {
1432
+ return true;
1321
1433
  }
1434
+ root = root.host.getRootNode();
1435
+ }
1436
+ return false;
1437
+ }
1438
+ function isPopupCandidateVisible(el) {
1439
+ return isElementVisible(el) && isStyleVisible(el) && isOnScreen(el);
1440
+ }
1441
+ function isOnScreen(el) {
1442
+ const rect = el.getBoundingClientRect();
1443
+ if (rect.width === 0 || rect.height === 0) {
1444
+ return window.getComputedStyle(el).overflow === "visible" && Array.from(el.children).some((child) => isOnScreen(child));
1322
1445
  }
1323
- return potentialPopups;
1446
+ return rect.bottom > 0 && rect.right > 0 && rect.top < window.innerHeight && rect.left < window.innerWidth;
1324
1447
  }
1325
1448
  function isDialogLikeElement(node) {
1326
1449
  if (node.tagName === "DIALOG" && node.hasAttribute("open")) {
@@ -1331,72 +1454,95 @@
1331
1454
  }
1332
1455
  return false;
1333
1456
  }
1334
- function getPopupLikeElements(timeout = POPUP_SEARCH_MAX_TIME) {
1335
- const startTime = performance.now();
1336
- const walker = document.createTreeWalker(
1337
- document.documentElement,
1338
- NodeFilter.SHOW_ELEMENT,
1339
- // visit only element nodes
1340
- {
1341
- acceptNode(node) {
1342
- if (node.tagName === "BODY") {
1343
- return NodeFilter.FILTER_SKIP;
1344
- }
1345
- if (isElementVisible(node)) {
1346
- const cssPosition = window.getComputedStyle(node).position;
1347
- if (cssPosition === "fixed" || cssPosition === "sticky") {
1348
- return NodeFilter.FILTER_ACCEPT;
1457
+ function getPopupLikeElements(deadline) {
1458
+ const found = [];
1459
+ const shadowHosts = [];
1460
+ const roots = [document.documentElement];
1461
+ for (let i = 0; i < roots.length; i++) {
1462
+ if (i > 0 && performance.now() > deadline) {
1463
+ break;
1464
+ }
1465
+ const walker = document.createTreeWalker(
1466
+ roots[i],
1467
+ NodeFilter.SHOW_ELEMENT,
1468
+ // visit only element nodes
1469
+ {
1470
+ acceptNode(node) {
1471
+ if (node.shadowRoot) {
1472
+ shadowHosts.push(node);
1473
+ roots.push(node.shadowRoot);
1474
+ }
1475
+ if (node.tagName === "BODY") {
1476
+ return NodeFilter.FILTER_SKIP;
1349
1477
  }
1350
- if (isDialogLikeElement(node)) {
1478
+ const style = window.getComputedStyle(node);
1479
+ if (style.display === "none") {
1480
+ return NodeFilter.FILTER_REJECT;
1481
+ }
1482
+ const cssPosition = style.position;
1483
+ if ((cssPosition === "fixed" || cssPosition === "sticky" || isDialogLikeElement(node)) && isPopupCandidateVisible(node)) {
1351
1484
  return NodeFilter.FILTER_ACCEPT;
1352
1485
  }
1486
+ if (performance.now() > deadline) {
1487
+ return NodeFilter.FILTER_REJECT;
1488
+ }
1489
+ return NodeFilter.FILTER_SKIP;
1353
1490
  }
1354
- if (performance.now() - startTime > timeout) {
1355
- return NodeFilter.FILTER_REJECT;
1356
- }
1357
- return NodeFilter.FILTER_SKIP;
1358
1491
  }
1492
+ );
1493
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
1494
+ found.push(node);
1359
1495
  }
1360
- );
1361
- const found = [];
1362
- for (let node = walker.nextNode(); node; node = walker.nextNode()) {
1363
- found.push(node);
1364
1496
  }
1365
- return excludeContainers(found.filter((element) => element.innerText?.trim()));
1497
+ return { elements: excludeContainers(found.filter((element) => getElementText(element).trim())), shadowHosts };
1366
1498
  }
1367
- function getButtonData(el) {
1368
- const actionableButtons = excludeContainers(getButtonLikeElements(el)).filter(
1369
- (b) => isElementVisible(b) && !isDisabled(b) && (b.innerText?.trim() || // <input> values do not appear in innerText
1370
- b instanceof HTMLInputElement && ["submit", "button"].includes(b.type) && b.value?.trim())
1371
- );
1372
- return actionableButtons.map((b) => ({
1373
- text: (b.innerText || b.textContent || "").trim() || b.value?.trim() || "",
1374
- element: b
1375
- }));
1499
+ function getButtonData(el, shadowHosts) {
1500
+ const buttons = [];
1501
+ for (const b of excludeContainers(getButtonLikeElements(el, shadowHosts))) {
1502
+ if (!isElementVisible(b) || isDisabled(b)) {
1503
+ continue;
1504
+ }
1505
+ const text = getElementText(b).trim() || // <input> values do not appear in innerText
1506
+ (b instanceof HTMLInputElement && ["submit", "button"].includes(b.type) ? b.value?.trim() : "");
1507
+ if (text) {
1508
+ buttons.push({ text, element: b });
1509
+ }
1510
+ }
1511
+ return buttons;
1376
1512
  }
1377
- function getButtonLikeElements(el) {
1378
- return Array.from(el.querySelectorAll(BUTTON_LIKE_ELEMENT_SELECTOR));
1513
+ function getButtonLikeElements(el, shadowHosts = findShadowHosts(el)) {
1514
+ const buttons = Array.from(el.querySelectorAll(BUTTON_LIKE_ELEMENT_SELECTOR));
1515
+ for (const host of shadowHosts) {
1516
+ if (composedContains(el, host)) {
1517
+ buttons.push(...host.shadowRoot.querySelectorAll(BUTTON_LIKE_ELEMENT_SELECTOR));
1518
+ }
1519
+ }
1520
+ return buttons;
1521
+ }
1522
+ function findShadowHosts(root) {
1523
+ const hosts = [];
1524
+ const candidates = root instanceof HTMLElement ? [root, ...root.querySelectorAll("*")] : root.querySelectorAll("*");
1525
+ for (const el of candidates) {
1526
+ if (el.shadowRoot) {
1527
+ hosts.push(el, ...findShadowHosts(el.shadowRoot));
1528
+ }
1529
+ }
1530
+ return hosts;
1379
1531
  }
1380
1532
  function isDisabled(el) {
1381
1533
  return "disabled" in el && Boolean(el.disabled) || el.hasAttribute("disabled");
1382
1534
  }
1383
1535
  function excludeContainers(elements) {
1384
- const results = [];
1385
- if (elements.length > 0) {
1386
- for (let i = elements.length - 1; i >= 0; i--) {
1387
- let container = false;
1388
- for (let j = 0; j < elements.length; j++) {
1389
- if (i !== j && elements[i].contains(elements[j])) {
1390
- container = true;
1391
- break;
1392
- }
1393
- }
1394
- if (!container) {
1395
- results.push(elements[i]);
1536
+ const candidates = new Set(elements);
1537
+ const containers = /* @__PURE__ */ new Set();
1538
+ for (const el of elements) {
1539
+ for (let node = el.parentNode; node; node = node.parentNode || (node instanceof ShadowRoot ? node.host : null)) {
1540
+ if (candidates.has(node)) {
1541
+ containers.add(node);
1396
1542
  }
1397
1543
  }
1398
1544
  }
1399
- return results;
1545
+ return elements.filter((el) => !containers.has(el)).reverse();
1400
1546
  }
1401
1547
 
1402
1548
  // lib/cmps/base.ts
@@ -2190,6 +2336,18 @@
2190
2336
  get isCosmetic() {
2191
2337
  return false;
2192
2338
  }
2339
+ // Some sites pre-render the banner before the SDK loads, and clicks before then may be lost.
2340
+ // Sites that copy the markup without the SDK never get #onetrust-style, so only wait when the page loads the SDK.
2341
+ async waitForSdk() {
2342
+ if (this.elementExists("#onetrust-style")) {
2343
+ return;
2344
+ }
2345
+ const sdkRef = /cookielaw\.org|otSDKStub|otBannerSdk/;
2346
+ const sdkExpected = Array.from(document.scripts).some((s) => sdkRef.test(s.src) || !s.src && sdkRef.test(s.textContent || ""));
2347
+ if (sdkExpected) {
2348
+ await this.waitForElement("#onetrust-style", 1e4);
2349
+ }
2350
+ }
2193
2351
  async detectCmp() {
2194
2352
  return this.elementExists("#onetrust-banner-sdk") || this.elementVisible("#onetrust-pc-sdk", "any");
2195
2353
  }
@@ -2198,6 +2356,7 @@
2198
2356
  }
2199
2357
  async optOut() {
2200
2358
  await this.wait(500);
2359
+ await this.waitForSdk();
2201
2360
  if (this.elementVisible("#onetrust-reject-all-handler", "any")) {
2202
2361
  return await this.click("#onetrust-reject-all-handler");
2203
2362
  }
@@ -2237,6 +2396,7 @@
2237
2396
  return true;
2238
2397
  }
2239
2398
  async optIn() {
2399
+ await this.waitForSdk();
2240
2400
  return await this.click("#onetrust-accept-btn-handler,#accept-recommended-btn-handler,.js-accept-cookies");
2241
2401
  }
2242
2402
  async test() {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Autoconsent",
4
- "version": "2026.10.21",
4
+ "version": "2026.10.23",
5
5
  "background": {
6
6
  "scripts": [
7
7
  "background.bundle.js"