@duckduckgo/autoconsent 16.30.0 → 16.32.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 (40) hide show
  1. package/.github/workflows/asana-sync.yml +1 -1
  2. package/CHANGELOG.md +39 -0
  3. package/dist/addon-firefox/compact-rules.json +1 -1
  4. package/dist/addon-firefox/content.bundle.js +47 -10
  5. package/dist/addon-firefox/manifest.json +1 -1
  6. package/dist/addon-firefox/rule-index.json +1 -1
  7. package/dist/addon-firefox/rules.json +1 -1
  8. package/dist/addon-mv3/compact-rules.json +1 -1
  9. package/dist/addon-mv3/content.bundle.js +47 -10
  10. package/dist/addon-mv3/manifest.json +1 -1
  11. package/dist/addon-mv3/rule-index.json +1 -1
  12. package/dist/addon-mv3/rules.json +1 -1
  13. package/dist/autoconsent.cjs.js +47 -10
  14. package/dist/autoconsent.esm.js +47 -10
  15. package/dist/autoconsent.playwright.js +47 -10
  16. package/dist/autoconsent.standalone.js +48 -11
  17. package/lib/cmps/cookiebot.ts +4 -0
  18. package/lib/heuristic-patterns.ts +45 -8
  19. package/lib/heuristics.ts +2 -1
  20. package/package.json +1 -1
  21. package/rules/autoconsent/doordash-storefront.json +34 -0
  22. package/rules/autoconsent/walmart-ca.json +44 -0
  23. package/rules/autoconsent/xnxx-com.json +1 -1
  24. package/rules/autoconsent/xvideos.json +1 -1
  25. package/rules/compact-rules.json +1 -1
  26. package/rules/rule-index.json +1 -1
  27. package/rules/rules.json +1 -1
  28. package/tests/cookiebot.spec.ts +4 -0
  29. package/tests/doordash-storefront.spec.ts +6 -0
  30. package/tests/walmart-ca.spec.ts +3 -0
  31. package/tests/xnxx.spec.ts +8 -2
  32. package/tests/xvideos.spec.ts +3 -3
  33. package/tests-wtr/heuristics/get-actionable-popups.html +15 -0
  34. package/tests-wtr/heuristics/get-actionable-popups.ts +20 -0
  35. package/tests-wtr/heuristics/heuristic-cmp.html +17 -0
  36. package/tests-wtr/heuristics/heuristic-cmp.ts +34 -0
  37. package/tests-wtr/heuristics/heuristics-utils.test.ts +135 -0
  38. package/tests-wtr/rules/cmp-test-urls.json +0 -1
  39. package/rules/generated/auto_CA_algonquincollege.com_o8v.json +0 -40
  40. package/tests/generated/auto_CA_algonquincollege.com_o8v.spec.ts +0 -6
@@ -10,6 +10,10 @@ generateCMPTests(
10
10
  // "https://www.ab-in-den-urlaub.de/", // often blocked by botwall
11
11
  'https://www.vatera.hu/',
12
12
  'https://www.bax-shop.nl/',
13
+ // Dialogs whose only decline control is CybotCookiebotDialogBodyButtonDecline
14
+ 'https://www.drukwerkdeal.nl/',
15
+ 'https://www.freelance.de/',
16
+ 'https://www.printdeal.be/',
13
17
  ],
14
18
  {
15
19
  skipRegions: ['US'],
@@ -0,0 +1,6 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ // DoorDash only serves the banner in some markets; Canada is the one covered by the test regions.
4
+ generateCMPTests('doordash-storefront', ['https://order.online/store/25309440', 'https://earlsca.order.online/store/-98335/'], {
5
+ onlyRegions: ['CA'],
6
+ });
@@ -0,0 +1,3 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('walmart-ca', ['https://www.walmart.ca/en', 'https://www.walmart.ca/fr'], { testOptIn: false });
@@ -1,5 +1,11 @@
1
1
  import generateCMPTests from '../playwright/runner';
2
2
 
3
- generateCMPTests('xnxx-com', ['https://xnxx.com/', 'https://xvideos.com/'], {
4
- onlyRegions: ['US'],
3
+ // the disclaimer is only shown without a preceding age gate in the UK
4
+ generateCMPTests('xnxx-com', ['https://www.xnxx.com/'], {
5
+ onlyRegions: ['GB'],
6
+ });
7
+
8
+ // the same rule also covers a bottom cookie bar used by an unrelated group of sites
9
+ generateCMPTests('xnxx-com', ['https://newtabtvsearch.com/', 'https://socialnewpagessearch.com/'], {
10
+ onlyRegions: ['GB', 'DE'],
5
11
  });
@@ -1,5 +1,5 @@
1
1
  import generateCMPTests from '../playwright/runner';
2
2
 
3
- generateCMPTests('xvideos', [
4
- // 'https://www.xvideos.com/' // pop-up appears only after choosing the region
5
- ]);
3
+ generateCMPTests('xvideos', ['https://www.xvideos.com/'], {
4
+ onlyRegions: ['GB'],
5
+ });
@@ -98,6 +98,21 @@
98
98
  <button>Accept All</button>
99
99
  </div>
100
100
 
101
+ <div id="popup-accept-with-empty-fixed-child" class="popup">
102
+ <p>We use cookies to improve your experience.</p>
103
+ <button>Accept All</button>
104
+ <div style="position: fixed"></div>
105
+ </div>
106
+
107
+ <div id="popup-with-contentful-fixed-child" class="popup">
108
+ <p>We use cookies to improve your experience.</p>
109
+ <button>Reject All</button>
110
+ <div id="contentful-fixed-child" style="position: fixed">
111
+ <p>We use cookies to improve your experience.</p>
112
+ <button>Accept All</button>
113
+ </div>
114
+ </div>
115
+
101
116
  <div id="popup-acknowledge-only" class="popup">
102
117
  <p>We use cookies to improve your experience.</p>
103
118
  <button>OK</button>
@@ -71,6 +71,26 @@ describe('getActionablePopups', () => {
71
71
  expect(popups[0].buttons.filter((b) => b.regexClassification === 'accept')).to.have.length(1);
72
72
  });
73
73
 
74
+ it('keeps a contentful popup containing an empty fixed child', () => {
75
+ showPopup('popup-accept-with-empty-fixed-child');
76
+
77
+ const popups = getActionablePopups('tier2');
78
+
79
+ expect(popups).to.have.length(1);
80
+ expect(popups[0].element.id).to.equal('popup-accept-with-empty-fixed-child');
81
+ expect(popups[0].regexClassification).to.equal('tier2');
82
+ });
83
+
84
+ it('prefers a contentful fixed child over its contentful container', () => {
85
+ showPopup('popup-with-contentful-fixed-child');
86
+
87
+ const popups = getActionablePopups('tier2');
88
+
89
+ expect(popups).to.have.length(1);
90
+ expect(popups[0].element.id).to.equal('contentful-fixed-child');
91
+ expect(popups[0].regexClassification).to.equal('tier2');
92
+ });
93
+
74
94
  it('finds acknowledge-only popup with no reject buttons', () => {
75
95
  showPopup('popup-acknowledge-only');
76
96
 
@@ -34,6 +34,23 @@
34
34
  <button>Reject All</button>
35
35
  </div>
36
36
 
37
+ <div id="popup-reject-russian-cookie-word" class="popup">
38
+ <p>Мы используем куки, чтобы сайт работал лучше.</p>
39
+ <button>Принять всё</button>
40
+ <button>Отклонить всё</button>
41
+ </div>
42
+
43
+ <div id="popup-acknowledge-only-russian" class="popup">
44
+ <p>Мы используем куки для улучшения работы сайта.</p>
45
+ <button>Понятно</button>
46
+ </div>
47
+
48
+ <div id="popup-accept-settings-russian" class="popup">
49
+ <p>Мы используем файлы cookie для улучшения работы сайта.</p>
50
+ <button>Принять всё</button>
51
+ <button>Настроить</button>
52
+ </div>
53
+
37
54
  <div id="popup-acknowledge-only" class="popup">
38
55
  <p>We use cookies to improve your experience.</p>
39
56
  <button>OK</button>
@@ -40,6 +40,40 @@ describe('AutoConsentHeuristicCMP', () => {
40
40
  expect(cmp.name).to.equal('HEURISTIC-REJECT');
41
41
  });
42
42
 
43
+ it('detects a Russian popup using the Cyrillic word for cookies', async () => {
44
+ const autoconsent = createAutoconsent('tier2');
45
+ const cmp = new AutoConsentHeuristicCMP(autoconsent, 'tier2');
46
+ showPopup('popup-reject-russian-cookie-word');
47
+
48
+ const detected = await cmp.detectCmp();
49
+
50
+ expect(detected).to.be.true;
51
+ expect(cmp.name).to.equal('HEURISTIC-REJECT');
52
+ expect(cmp.getTargetButton()?.text).to.equal('Отклонить всё');
53
+ });
54
+
55
+ it('detects a Russian acknowledge-only popup as HEURISTIC-TIER1', async () => {
56
+ const autoconsent = createAutoconsent('tier2');
57
+ const cmp = new AutoConsentHeuristicCMP(autoconsent, 'tier2');
58
+ showPopup('popup-acknowledge-only-russian');
59
+
60
+ const detected = await cmp.detectCmp();
61
+
62
+ expect(detected).to.be.true;
63
+ expect(cmp.name).to.equal('HEURISTIC-TIER1');
64
+ expect(cmp.getTargetButton()?.text).to.equal('Понятно');
65
+ });
66
+
67
+ it('does not detect a Russian popup with accept and settings buttons', async () => {
68
+ const autoconsent = createAutoconsent('tier2');
69
+ const cmp = new AutoConsentHeuristicCMP(autoconsent, 'tier2');
70
+ showPopup('popup-accept-settings-russian');
71
+
72
+ const detected = await cmp.detectCmp();
73
+
74
+ expect(detected).to.be.false;
75
+ });
76
+
43
77
  it('detects acknowledge-only popup as HEURISTIC-TIER1', async () => {
44
78
  const autoconsent = createAutoconsent('tier2');
45
79
  const cmp = new AutoConsentHeuristicCMP(autoconsent, 'tier2');
@@ -4,6 +4,7 @@ import {
4
4
  cleanButtonText,
5
5
  classifyButtonTextRegex,
6
6
  classifyButtons,
7
+ getActionablePopups,
7
8
  isDisabled,
8
9
  excludeContainers,
9
10
  getButtonData,
@@ -60,6 +61,40 @@ describe('checkHeuristicPatterns', () => {
60
61
  expect(snippets.length).to.be.greaterThan(0);
61
62
  expect(snippets.every((s) => typeof s === 'string')).to.be.true;
62
63
  });
64
+
65
+ it('detects website cookie experience notices', () => {
66
+ const { patterns, snippets } = checkHeuristicPatterns(
67
+ 'The Algonquin College website uses cookies to enhance your browsing experience.',
68
+ );
69
+
70
+ expect(patterns.length).to.be.greaterThan(0);
71
+ expect(snippets).to.include('website uses cookies to enhance your browsing experience');
72
+ });
73
+ });
74
+
75
+ describe('checkHeuristicPatterns with Russian popups', () => {
76
+ it('detects Russian cookie notices', () => {
77
+ const texts = [
78
+ 'Мы используем файлы cookie для улучшения работы сайта. Правила сайта используют технологии cookie.',
79
+ 'Этот сайт использует cookie, чтобы вам было удобнее',
80
+ 'Продолжая работу с сайтом, вы соглашаетесь с использованием файлов cookie',
81
+ 'Мы используем куки, чтобы сайт работал лучше',
82
+ 'Этот сайт использует куки-файлы',
83
+ 'Сайт использует технологии куки. Продолжая, вы принимаете обработку файлов куки',
84
+ 'Мы применяем файлы cookie, чтобы предложить вам лучший сервис',
85
+ 'Сайт собирает cookie для аналитики',
86
+ 'Сохраняем куки на вашем устройстве',
87
+ 'На сайте установлены cookie-файлы',
88
+ 'Оставаясь на сайте, вы соглашаетесь с политикой в отношении файлов cookie',
89
+ ];
90
+ for (const text of texts) {
91
+ expect(checkHeuristicPatterns(text).patterns.length, text).to.be.greaterThan(0);
92
+ }
93
+ });
94
+
95
+ it('does not detect unrelated Russian text', () => {
96
+ expect(checkHeuristicPatterns('Дешевые авиабилеты онлайн и бронирование отелей').patterns).to.have.length(0);
97
+ });
63
98
  });
64
99
 
65
100
  describe('isExcludedPopup', () => {
@@ -155,10 +190,82 @@ describe('classifyButtonTextRegex', () => {
155
190
  expect(classifyButtonTextRegex('no')).to.equal('reject');
156
191
  });
157
192
 
193
+ it('classifies essential-only buttons as reject choices', () => {
194
+ expect(classifyButtonTextRegex('Essential Only')).to.equal('reject');
195
+ expect(classifyButtonTextRegex('Only use essential cookies')).to.equal('reject');
196
+ expect(classifyButtonTextRegex('Only accept necessary cookies')).to.equal('reject');
197
+ });
198
+
199
+ it('does not classify bare essential category labels as reject choices', () => {
200
+ expect(classifyButtonTextRegex('Essential')).to.equal('other');
201
+ });
202
+
158
203
  it('does not match partial exact string patterns', () => {
159
204
  expect(classifyButtonTextRegex('no problem')).to.equal('other');
160
205
  });
161
206
 
207
+ it('matches Russian reject buttons', () => {
208
+ expect(classifyButtonTextRegex('Отклонить всё')).to.equal('reject');
209
+ expect(classifyButtonTextRegex('Отклонить все файлы cookie')).to.equal('reject');
210
+ expect(classifyButtonTextRegex('Отказаться')).to.equal('reject');
211
+ expect(classifyButtonTextRegex('Только необходимые')).to.equal('reject');
212
+ expect(classifyButtonTextRegex('Принимать только необходимые файлы cookie')).to.equal('reject');
213
+ expect(classifyButtonTextRegex('Только необходимые куки')).to.equal('reject');
214
+ expect(classifyButtonTextRegex('Отклонить куки')).to.equal('reject');
215
+ expect(classifyButtonTextRegex('Не принимаю')).to.equal('reject');
216
+ });
217
+
218
+ it('matches Russian settings buttons', () => {
219
+ expect(classifyButtonTextRegex('Настроить')).to.equal('settings');
220
+ expect(classifyButtonTextRegex('Настройки')).to.equal('settings');
221
+ expect(classifyButtonTextRegex('Настроить файлы cookie')).to.equal('settings');
222
+ expect(classifyButtonTextRegex('Настройки куки')).to.equal('settings');
223
+ expect(classifyButtonTextRegex('Параметры конфиденциальности')).to.equal('settings');
224
+ expect(classifyButtonTextRegex('Мои предпочтения')).to.equal('settings');
225
+ expect(classifyButtonTextRegex('Управление файлами cookie')).to.equal('settings');
226
+ expect(classifyButtonTextRegex('Изменить настройки')).to.equal('settings');
227
+ expect(classifyButtonTextRegex('Подробные настройки')).to.equal('settings');
228
+ });
229
+
230
+ it('does not treat Russian policy links as settings buttons', () => {
231
+ expect(classifyButtonTextRegex('Подробнее')).to.equal('other');
232
+ expect(classifyButtonTextRegex('Подробнее о cookie')).to.equal('other');
233
+ expect(classifyButtonTextRegex('Подробнее о файлах cookie')).to.equal('other');
234
+ expect(classifyButtonTextRegex('Политика конфиденциальности')).to.equal('other');
235
+ });
236
+
237
+ it('matches Russian acknowledge buttons', () => {
238
+ expect(classifyButtonTextRegex('Понятно')).to.equal('acknowledge');
239
+ expect(classifyButtonTextRegex('Всё понятно')).to.equal('acknowledge');
240
+ expect(classifyButtonTextRegex('Хорошо')).to.equal('acknowledge');
241
+ expect(classifyButtonTextRegex('Закрыть')).to.equal('acknowledge');
242
+ expect(classifyButtonTextRegex('Закрыть уведомление о cookie')).to.equal('acknowledge');
243
+ expect(classifyButtonTextRegex('Больше не показывать')).to.equal('acknowledge');
244
+ expect(classifyButtonTextRegex('Продолжить')).to.equal('acknowledge');
245
+ });
246
+
247
+ it('treats Russian confirm and save buttons as acknowledge rather than accept', () => {
248
+ expect(classifyButtonTextRegex('Подтвердить')).to.equal('acknowledge');
249
+ expect(classifyButtonTextRegex('Подтверждаю')).to.equal('acknowledge');
250
+ expect(classifyButtonTextRegex('Подтверждаю выбор')).to.equal('acknowledge');
251
+ expect(classifyButtonTextRegex('Подтвердить мой выбор')).to.equal('acknowledge');
252
+ expect(classifyButtonTextRegex('Сохранить настройки')).to.equal('acknowledge');
253
+ expect(classifyButtonTextRegex('Сохранить выбор')).to.equal('acknowledge');
254
+ expect(classifyButtonTextRegex('Сохранить и закрыть')).to.equal('acknowledge');
255
+ });
256
+
257
+ it('does not treat Russian subscription buttons as reject', () => {
258
+ expect(classifyButtonTextRegex('Отказаться от подписки')).to.equal('other');
259
+ });
260
+
261
+ it('matches Russian accept buttons', () => {
262
+ expect(classifyButtonTextRegex('Принять всё')).to.equal('accept');
263
+ expect(classifyButtonTextRegex('Принять все файлы cookie')).to.equal('accept');
264
+ expect(classifyButtonTextRegex('Разрешить куки')).to.equal('accept');
265
+ expect(classifyButtonTextRegex('Принять куки-файлы')).to.equal('accept');
266
+ expect(classifyButtonTextRegex('Я согласен')).to.equal('accept');
267
+ });
268
+
162
269
  it('does not treat revoke links as reject buttons', () => {
163
270
  expect(classifyButtonTextRegex('Widerrufen')).to.equal('other');
164
271
  });
@@ -368,6 +475,34 @@ describe('excludeContainers', () => {
368
475
  });
369
476
  });
370
477
 
478
+ describe('getActionablePopups', () => {
479
+ let container: HTMLDivElement;
480
+
481
+ beforeEach(() => {
482
+ container = document.createElement('div');
483
+ document.body.appendChild(container);
484
+ });
485
+
486
+ afterEach(() => {
487
+ container.remove();
488
+ });
489
+
490
+ it('keeps text-bearing popups that contain decorative sticky children', () => {
491
+ container.innerHTML = `
492
+ <div style="position: fixed; display: block;">
493
+ <p>This website uses cookies to enhance your browsing experience.</p>
494
+ <div style="position: sticky;"></div>
495
+ <button>Essential Only</button>
496
+ </div>
497
+ `;
498
+
499
+ const popups = getActionablePopups('tier2');
500
+
501
+ expect(popups).to.have.length(1);
502
+ expect(popups[0].regexClassification).to.equal('reject');
503
+ });
504
+ });
505
+
371
506
  describe('getButtonData', () => {
372
507
  let container: HTMLDivElement;
373
508
 
@@ -276,7 +276,6 @@
276
276
  ["auto_CA_407etr.com_wrd", "https://www.407etr.com/"],
277
277
  ["auto_CA_admission.umontreal.ca_vi5_+3", "https://umontreal.ca/"],
278
278
  ["auto_CA_airbnb.ca_d52_+1", "https://airbnb.co.uk/"],
279
- ["auto_CA_algonquincollege.com_o8v", "http://www.algonquincollege.com/"],
280
279
  ["auto_CA_arte.tv_7nv", "https://www.arte.tv/"],
281
280
  ["auto_CA_babel.hathitrust.org_i6g", "http://www.babel.hathitrust.org/"],
282
281
  ["auto_CA_bahia-principe.com_nll", "http://bahia-principe.com/"],
@@ -1,40 +0,0 @@
1
- {
2
- "name": "auto_CA_algonquincollege.com_o8v",
3
- "cosmetic": false,
4
- "_metadata": {
5
- "vendorUrl": "http://www.algonquincollege.com/"
6
- },
7
- "runContext": {
8
- "main": true,
9
- "frame": false,
10
- "urlPattern": "^https?://(www\\.)?algonquincollege\\.com/"
11
- },
12
- "prehideSelectors": [],
13
- "detectCmp": [
14
- {
15
- "exists": "body > div#accn-cookie-consent-wrapper > div#accn-cookie-consent > div:nth-child(2)#accn-statement-scroller > div:nth-child(2):not([id]) > div:nth-child(3):not([id]) > button:not([id])"
16
- }
17
- ],
18
- "detectPopup": [
19
- {
20
- "visible": "body > div#accn-cookie-consent-wrapper > div#accn-cookie-consent > div:nth-child(2)#accn-statement-scroller > div:nth-child(2):not([id]) > div:nth-child(3):not([id]) > button:not([id])"
21
- }
22
- ],
23
- "optIn": [],
24
- "optOut": [
25
- {
26
- "wait": 500
27
- },
28
- {
29
- "waitForThenClick": "body > div#accn-cookie-consent-wrapper > div#accn-cookie-consent > div:nth-child(2)#accn-statement-scroller > div:nth-child(2):not([id]) > div:nth-child(3):not([id]) > button:not([id])",
30
- "comment": "Essential Only"
31
- }
32
- ],
33
- "test": [
34
- {
35
- "waitForVisible": "body > div#accn-cookie-consent-wrapper > div#accn-cookie-consent > div:nth-child(2)#accn-statement-scroller > div:nth-child(2):not([id]) > div:nth-child(3):not([id]) > button:not([id])",
36
- "timeout": 1000,
37
- "check": "none"
38
- }
39
- ]
40
- }
@@ -1,6 +0,0 @@
1
- import generateCMPTests from '../../playwright/runner';
2
- generateCMPTests('auto_CA_algonquincollege.com_o8v', ['https://www.algonquincollege.com/'], {
3
- testOptIn: false,
4
- testSelfTest: true,
5
- onlyRegions: ['CA'],
6
- });