@duckduckgo/autoconsent 16.27.0 → 16.29.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 (51) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +40 -0
  3. package/dist/addon-firefox/compact-rules.json +1 -1
  4. package/dist/addon-firefox/content.bundle.js +43 -6
  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 +43 -6
  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 +43 -6
  14. package/dist/autoconsent.esm.js +43 -6
  15. package/dist/autoconsent.playwright.js +43 -6
  16. package/dist/autoconsent.standalone.js +44 -7
  17. package/dist/types/cmps/base.d.ts +2 -1
  18. package/dist/types/dom-actions.d.ts +3 -1
  19. package/dist/types/rules.d.ts +16 -2
  20. package/dist/types/types.d.ts +1 -1
  21. package/dist/types/utils.d.ts +1 -0
  22. package/docs/rule-syntax.md +26 -1
  23. package/lib/cmps/base.ts +10 -3
  24. package/lib/dom-actions.ts +33 -3
  25. package/lib/rules.ts +19 -2
  26. package/lib/types.ts +7 -1
  27. package/lib/utils.ts +16 -0
  28. package/package.json +1 -1
  29. package/rules/autoconsent/amazon-pay.json +13 -0
  30. package/rules/autoconsent/bbva.json +12 -0
  31. package/rules/autoconsent/deliveroo.json +15 -0
  32. package/rules/autoconsent/ebay.json +34 -0
  33. package/rules/autoconsent/ecbeing.json +10 -0
  34. package/rules/autoconsent/stright.json +29 -0
  35. package/rules/autoconsent/theguardian.com.json +13 -3
  36. package/rules/autoconsent/wpconsent.json +30 -0
  37. package/rules/compact-rules.json +1 -1
  38. package/rules/rule-index.json +1 -1
  39. package/rules/rules.json +1 -1
  40. package/tests/amazon-pay.spec.ts +3 -0
  41. package/tests/bbva.spec.ts +4 -0
  42. package/tests/deliveroo.spec.ts +3 -0
  43. package/tests/ecbeing.spec.ts +3 -0
  44. package/tests/stright.spec.ts +8 -0
  45. package/tests/wpconsent.spec.ts +5 -0
  46. package/tests-wtr/dom-actions/dom-actions.stylesheet.html +13 -0
  47. package/tests-wtr/dom-actions/dom-actions.stylesheet.ts +26 -0
  48. package/tests-wtr/dom-actions/dom-actions.wait-for-then-click.html +14 -0
  49. package/tests-wtr/dom-actions/dom-actions.wait-for-then-click.ts +106 -0
  50. package/tests-wtr/utils/append-stylesheet-rule.html +11 -0
  51. package/tests-wtr/utils/append-stylesheet-rule.test.ts +29 -0
@@ -29,9 +29,10 @@ export default class AutoConsentCMPBase implements AutoCMP, DomActionsProvider {
29
29
  elementVisible(selector: ElementSelector, check?: VisibilityCheck): boolean;
30
30
  waitForElement(selector: ElementSelector, timeout?: number): Promise<boolean>;
31
31
  waitForVisible(selector: ElementSelector, timeout?: number, check?: VisibilityCheck): Promise<boolean>;
32
- waitForThenClick(selector: ElementSelector, timeout?: number, all?: boolean): Promise<boolean>;
32
+ waitForThenClick(selector: ElementSelector, timeout?: number, all?: boolean, retries?: number, retryInterval?: number): Promise<boolean>;
33
33
  wait(ms: number): Promise<true>;
34
34
  hide(selector: string, method?: HideMethod): boolean;
35
+ stylesheet(cssRule: string, stylesheetId?: string): boolean;
35
36
  removeClass(selector: ElementSelector, className: string): boolean;
36
37
  setStyle(selector: ElementSelector, css: string): boolean;
37
38
  addStyle(selector: ElementSelector, css: string): boolean;
@@ -1,6 +1,7 @@
1
1
  import { ElementSelector, HideMethod, VisibilityCheck } from './rules';
2
2
  import { DomActionsProvider } from './types';
3
3
  import AutoConsent from './web';
4
+ export declare const DEFAULT_CLICK_RETRY_INTERVAL = 300;
4
5
  export declare class DomActions implements DomActionsProvider {
5
6
  autoconsentInstance: AutoConsent;
6
7
  constructor(autoconsentInstance: AutoConsent);
@@ -10,10 +11,11 @@ export declare class DomActions implements DomActionsProvider {
10
11
  elementVisible(selector: ElementSelector, check?: VisibilityCheck): boolean;
11
12
  waitForElement(selector: ElementSelector, timeout?: number): Promise<boolean>;
12
13
  waitForVisible(selector: ElementSelector, timeout?: number, check?: VisibilityCheck): Promise<boolean>;
13
- waitForThenClick(selector: ElementSelector, timeout?: number, all?: boolean): Promise<boolean>;
14
+ waitForThenClick(selector: ElementSelector, timeout?: number, all?: boolean, retries?: number, retryInterval?: number): Promise<boolean>;
14
15
  wait(ms: number): Promise<true>;
15
16
  cookieContains(substring: string): boolean;
16
17
  hide(selector: string, method?: HideMethod): boolean;
18
+ stylesheet(cssRule: string, stylesheetId?: string): boolean;
17
19
  removeClass(selector: ElementSelector, className: string): boolean;
18
20
  setStyle(selector: ElementSelector, css: string): boolean;
19
21
  addStyle(selector: ElementSelector, css: string): boolean;
@@ -1,5 +1,5 @@
1
1
  import { snippets } from './eval-snippets';
2
- export declare const SUPPORTED_RULE_STEP_VERSION = 2;
2
+ export declare const SUPPORTED_RULE_STEP_VERSION = 3;
3
3
  export type AutoConsentCMPRule = {
4
4
  readonly name: string;
5
5
  /**
@@ -33,7 +33,7 @@ export type ElementSelector = string | string[];
33
33
  export type AutoConsentRuleStep = {
34
34
  optional?: boolean;
35
35
  comment?: string;
36
- } & Partial<ElementExistsRule> & Partial<ElementVisibleRule> & Partial<EvalRule> & Partial<WaitForRule> & Partial<WaitForVisibleRule> & Partial<ClickRule> & Partial<WaitForThenClickRule> & Partial<WaitRule> & Partial<HideRule> & Partial<IfRule> & Partial<AnyRule> & Partial<NegatedRule> & Partial<CookieContainsRule> & Partial<RemoveClassRule> & Partial<SetStyleRule> & Partial<AddStyleRule>;
36
+ } & Partial<ElementExistsRule> & Partial<ElementVisibleRule> & Partial<EvalRule> & Partial<WaitForRule> & Partial<WaitForVisibleRule> & Partial<ClickRule> & Partial<WaitForThenClickRule> & Partial<WaitRule> & Partial<HideRule> & Partial<IfRule> & Partial<AnyRule> & Partial<NegatedRule> & Partial<CookieContainsRule> & Partial<RemoveClassRule> & Partial<SetStyleRule> & Partial<AddStyleRule> & Partial<StylesheetRule>;
37
37
  export type NegatedRule = {
38
38
  negated: boolean;
39
39
  };
@@ -68,6 +68,16 @@ export type WaitForThenClickRule = {
68
68
  waitForThenClick: ElementSelector;
69
69
  timeout?: number;
70
70
  all?: boolean;
71
+ /**
72
+ * Number of additional click attempts if the element is still visible after the first click.
73
+ * Defaults to 0 (no retries). Only use this on elements that are expected to disappear once the
74
+ * click is handled, otherwise the extra clicks are wasted (or harmful).
75
+ */
76
+ retry?: number;
77
+ /**
78
+ * How long to wait (in ms) for the element to disappear before clicking it again.
79
+ */
80
+ retryInterval?: number;
71
81
  };
72
82
  export type WaitRule = {
73
83
  wait: number;
@@ -97,3 +107,7 @@ export type AddStyleRule = {
97
107
  addStyle: string;
98
108
  selector: ElementSelector;
99
109
  };
110
+ export type StylesheetRule = {
111
+ stylesheet: string;
112
+ stylesheetId?: string;
113
+ };
@@ -26,7 +26,7 @@ export interface DomActionsProvider {
26
26
  elementVisible(selector: ElementSelector, check: VisibilityCheck): boolean;
27
27
  waitForElement(selector: ElementSelector, timeout?: number): Promise<boolean>;
28
28
  waitForVisible(selector: ElementSelector, timeout?: number, check?: VisibilityCheck): Promise<boolean>;
29
- waitForThenClick(selector: ElementSelector, timeout?: number, all?: boolean): Promise<boolean>;
29
+ waitForThenClick(selector: ElementSelector, timeout?: number, all?: boolean, retries?: number, retryInterval?: number): Promise<boolean>;
30
30
  wait(ms: number): Promise<true>;
31
31
  hide(selector: string, method: HideMethod): boolean;
32
32
  removeClass(selector: ElementSelector, className: string): boolean;
@@ -3,6 +3,7 @@ import { Config } from './types';
3
3
  export declare function getStyleElement(styleOverrideElementId?: string): HTMLStyleElement;
4
4
  export declare function getHidingStyle(method: HideMethod): string;
5
5
  export declare function hideElements(styleEl: HTMLStyleElement, selector: string, method?: HideMethod): boolean;
6
+ export declare function appendStylesheetRule(styleEl: HTMLStyleElement, cssRule: string, marker?: string): boolean;
6
7
  export declare function waitFor(predicate: () => Promise<boolean> | boolean, maxTimes: number, interval: number): Promise<boolean>;
7
8
  export declare function isElementVisible(elem: HTMLElement): boolean;
8
9
  export declare function copyObject(data: any): any;
@@ -103,11 +103,22 @@ Click on an element returned by `selector`. If `all` is `true`, all matching ele
103
103
  {
104
104
  "waitForThenClick": ElementSelector,
105
105
  "timeout": 1000,
106
- "all": true | false
106
+ "all": true | false,
107
+ "retry": 0,
108
+ "retryInterval": 300
107
109
  }
108
110
  ```
109
111
  Combines `waitFor` and `click`.
110
112
 
113
+ `retry` is the number of extra click attempts if the element is still visible after the click, `0` by
114
+ default. It works around CMPs that insert a button before its click handler is attached.
115
+ Between attempts the step waits up to `retryInterval` ms (300 by default) for the element to
116
+ disappear, and stops retrying as soon as it does. Prefer this over an unconditional `wait` before the
117
+ click: nothing is added to the step's duration when the first click works.
118
+
119
+ **Only use `retry` on elements that are expected to go away** once the click is handled — a button that
120
+ stays visible (e.g. a toggle) would be clicked repeatedly.
121
+
111
122
  ## Unconditional wait
112
123
  ```javascript
113
124
  {
@@ -154,6 +165,18 @@ Set the inline style of the elements matched by the `selector`. `style-string` i
154
165
  ```
155
166
  Append the inline style of the elements matched by the `selector`. `style-string` is a string of CSS properties and values. The style is appended to the existing inline style, separated by a semicolon.
156
167
 
168
+ ## Stylesheet
169
+
170
+ ```javascript
171
+ {
172
+ "stylesheet": "css-rule-string",
173
+ "stylesheetId": "unique-marker"
174
+ }
175
+ ```
176
+ Append a CSS rule (e.g. `".overlay { display: none !important; }"`) to the style element injected by autoconsent. Unlike `setStyle`/`addStyle`, which modify inline styles of currently matched elements, an appended stylesheet rule keeps applying to elements matched later — useful when the page re-adds classes or inline styles after the opt-out (e.g. scroll locks reapplied on back/forward navigation).
177
+
178
+ The optional `stylesheetId` is a marker used to prevent duplicate inserts when the rule runs multiple times; if omitted, the CSS rule text itself is used for deduplication.
179
+
157
180
  ## Cookie match
158
181
  ```javascript
159
182
  {
@@ -241,11 +264,13 @@ The `minimumRuleStepVersion` field solves this: clients compare the rule's decla
241
264
  |---------|-----------------|
242
265
  | 1 | All original step types (`exists`, `visible`, `waitFor`, `waitForVisible`, `click`, `waitForThenClick`, `wait`, `hide`, `if`/`then`/`else`, `any`, `eval`, `cookieContains`, `negated`) |
243
266
  | 2 | `removeClass`, `setStyle`, `addStyle` |
267
+ | 3 | `stylesheet` |
244
268
 
245
269
  ### When to set it
246
270
 
247
271
  - If a rule only uses version-1 step types, omit the field (defaults to `1`).
248
272
  - If a rule uses `removeClass`, `setStyle`, or `addStyle`, set `"minimumRuleStepVersion": 2`.
273
+ - If a rule uses `stylesheet`, set `"minimumRuleStepVersion": 3`.
249
274
  - When a future version introduces new step types, any rule using them must set `minimumRuleStepVersion` to the corresponding version number.
250
275
 
251
276
  ### Adding new step types
package/lib/cmps/base.ts CHANGED
@@ -180,12 +180,12 @@ export default class AutoConsentCMPBase implements AutoCMP, DomActionsProvider {
180
180
  return this.autoconsent.domActions.waitForVisible(selector, timeout, check);
181
181
  }
182
182
 
183
- async waitForThenClick(selector: ElementSelector, timeout?: number, all?: boolean) {
183
+ async waitForThenClick(selector: ElementSelector, timeout?: number, all?: boolean, retries?: number, retryInterval?: number) {
184
184
  if (this.autoconsent.config.visualTest) {
185
185
  await this.highlightElements(this.elementSelector(selector), all);
186
186
  }
187
187
  this.autoconsent.updateState({ clicks: this.autoconsent.state.clicks + 1 });
188
- return this.autoconsent.domActions.waitForThenClick(selector, timeout, all);
188
+ return this.autoconsent.domActions.waitForThenClick(selector, timeout, all, retries, retryInterval);
189
189
  }
190
190
 
191
191
  wait(ms: number) {
@@ -196,6 +196,10 @@ export default class AutoConsentCMPBase implements AutoCMP, DomActionsProvider {
196
196
  return this.autoconsent.domActions.hide(selector, method);
197
197
  }
198
198
 
199
+ stylesheet(cssRule: string, stylesheetId?: string) {
200
+ return this.autoconsent.domActions.stylesheet(cssRule, stylesheetId);
201
+ }
202
+
199
203
  removeClass(selector: ElementSelector, className: string) {
200
204
  return this.autoconsent.domActions.removeClass(selector, className);
201
205
  }
@@ -332,7 +336,7 @@ export class AutoConsentCMP extends AutoConsentCMPBase {
332
336
  results.push(this.click(rule.click, rule.all));
333
337
  }
334
338
  if (rule.waitForThenClick) {
335
- results.push(this.waitForThenClick(rule.waitForThenClick, rule.timeout, rule.all));
339
+ results.push(this.waitForThenClick(rule.waitForThenClick, rule.timeout, rule.all, rule.retry, rule.retryInterval));
336
340
  }
337
341
  if (rule.wait) {
338
342
  results.push(this.wait(rule.wait));
@@ -340,6 +344,9 @@ export class AutoConsentCMP extends AutoConsentCMPBase {
340
344
  if (rule.hide) {
341
345
  results.push(this.hide(rule.hide, rule.method));
342
346
  }
347
+ if (rule.stylesheet !== undefined) {
348
+ results.push(this.stylesheet(rule.stylesheet, rule.stylesheetId));
349
+ }
343
350
  if (rule.removeClass !== undefined) {
344
351
  results.push(rule.selector ? this.removeClass(rule.selector, rule.removeClass) : false);
345
352
  }
@@ -1,8 +1,13 @@
1
1
  import { ElementSelector, HideMethod, VisibilityCheck } from './rules';
2
2
  import { DomActionsProvider } from './types';
3
- import { getStyleElement, hideElements, isElementVisible, waitFor } from './utils';
3
+ import { getStyleElement, hideElements, appendStylesheetRule, isElementVisible, waitFor } from './utils';
4
4
  import AutoConsent from './web';
5
5
 
6
+ // Default for the `retryInterval` option of `waitForThenClick`.
7
+ export const DEFAULT_CLICK_RETRY_INTERVAL = 300;
8
+ // How often we check if the element is gone while waiting out a `retryInterval`.
9
+ const CLICK_RETRY_POLL_INTERVAL = 50;
10
+
6
11
  export class DomActions implements DomActionsProvider {
7
12
  constructor(public autoconsentInstance: AutoConsent) {}
8
13
 
@@ -66,9 +71,28 @@ export class DomActions implements DomActionsProvider {
66
71
  return waitFor(() => this.elementVisible(selector, check), times, interval);
67
72
  }
68
73
 
69
- async waitForThenClick(selector: ElementSelector, timeout = 10000, all = false): Promise<boolean> {
74
+ async waitForThenClick(
75
+ selector: ElementSelector,
76
+ timeout = 10000,
77
+ all = false,
78
+ retries = 0,
79
+ retryInterval = DEFAULT_CLICK_RETRY_INTERVAL,
80
+ ): Promise<boolean> {
70
81
  await this.waitForElement(selector, timeout);
71
- return await this.click(selector, all);
82
+ let clicked = await this.click(selector, all);
83
+ // Some CMPs insert the button before its click handler is attached, so the first click is
84
+ // silently dropped. There is usually no DOM signal for this, but a click that was handled
85
+ // makes the element go away: if it is still visible, we assume the click was lost and repeat it.
86
+ for (let attempt = 0; clicked && attempt < retries; attempt++) {
87
+ const pollTimes = Math.ceil(retryInterval / CLICK_RETRY_POLL_INTERVAL);
88
+ const isGone = await waitFor(() => !this.elementVisible(selector, 'any'), pollTimes, CLICK_RETRY_POLL_INTERVAL);
89
+ if (isGone) {
90
+ break;
91
+ }
92
+ this.autoconsentInstance.config.logs.rulesteps && console.log('[waitForThenClick] retrying click', selector);
93
+ clicked = await this.click(selector, all);
94
+ }
95
+ return clicked;
72
96
  }
73
97
 
74
98
  wait(ms: number): Promise<true> {
@@ -91,6 +115,12 @@ export class DomActions implements DomActionsProvider {
91
115
  return hideElements(styleEl, selector, method);
92
116
  }
93
117
 
118
+ stylesheet(cssRule: string, stylesheetId?: string): boolean {
119
+ this.autoconsentInstance.config.logs.rulesteps && console.log('[stylesheet]', cssRule, stylesheetId);
120
+ const styleEl = getStyleElement();
121
+ return appendStylesheetRule(styleEl, cssRule, stylesheetId);
122
+ }
123
+
94
124
  removeClass(selector: ElementSelector, className: string): boolean {
95
125
  const elements = this.elementSelector(selector);
96
126
  this.autoconsentInstance.config.logs.rulesteps && console.log('[removeClass]', selector, className, elements);
package/lib/rules.ts CHANGED
@@ -2,7 +2,8 @@ import { snippets } from './eval-snippets';
2
2
 
3
3
  // 1 - the first version of the rule step format
4
4
  // 2 - added removeClass, setStyle, addStyle
5
- export const SUPPORTED_RULE_STEP_VERSION = 2;
5
+ // 3 - added stylesheet
6
+ export const SUPPORTED_RULE_STEP_VERSION = 3;
6
7
 
7
8
  export type AutoConsentCMPRule = {
8
9
  readonly name: string;
@@ -52,7 +53,8 @@ export type AutoConsentRuleStep = { optional?: boolean; comment?: string } & Par
52
53
  Partial<CookieContainsRule> &
53
54
  Partial<RemoveClassRule> &
54
55
  Partial<SetStyleRule> &
55
- Partial<AddStyleRule>;
56
+ Partial<AddStyleRule> &
57
+ Partial<StylesheetRule>;
56
58
 
57
59
  export type NegatedRule = {
58
60
  negated: boolean;
@@ -97,6 +99,16 @@ export type WaitForThenClickRule = {
97
99
  waitForThenClick: ElementSelector;
98
100
  timeout?: number;
99
101
  all?: boolean;
102
+ /**
103
+ * Number of additional click attempts if the element is still visible after the first click.
104
+ * Defaults to 0 (no retries). Only use this on elements that are expected to disappear once the
105
+ * click is handled, otherwise the extra clicks are wasted (or harmful).
106
+ */
107
+ retry?: number;
108
+ /**
109
+ * How long to wait (in ms) for the element to disappear before clicking it again.
110
+ */
111
+ retryInterval?: number;
100
112
  };
101
113
 
102
114
  export type WaitRule = {
@@ -134,3 +146,8 @@ export type AddStyleRule = {
134
146
  addStyle: string;
135
147
  selector: ElementSelector;
136
148
  };
149
+
150
+ export type StylesheetRule = {
151
+ stylesheet: string;
152
+ stylesheetId?: string;
153
+ };
package/lib/types.ts CHANGED
@@ -29,7 +29,13 @@ export interface DomActionsProvider {
29
29
  elementVisible(selector: ElementSelector, check: VisibilityCheck): boolean;
30
30
  waitForElement(selector: ElementSelector, timeout?: number): Promise<boolean>;
31
31
  waitForVisible(selector: ElementSelector, timeout?: number, check?: VisibilityCheck): Promise<boolean>;
32
- waitForThenClick(selector: ElementSelector, timeout?: number, all?: boolean): Promise<boolean>;
32
+ waitForThenClick(
33
+ selector: ElementSelector,
34
+ timeout?: number,
35
+ all?: boolean,
36
+ retries?: number,
37
+ retryInterval?: number,
38
+ ): Promise<boolean>;
33
39
  wait(ms: number): Promise<true>;
34
40
  hide(selector: string, method: HideMethod): boolean;
35
41
  removeClass(selector: ElementSelector, className: string): boolean;
package/lib/utils.ts CHANGED
@@ -32,6 +32,22 @@ export function hideElements(styleEl: HTMLStyleElement, selector: string, method
32
32
  return false;
33
33
  }
34
34
 
35
+ // append a stylesheet rule once; marker is used to avoid duplicate inserts on re-runs
36
+ export function appendStylesheetRule(styleEl: HTMLStyleElement, cssRule: string, marker?: string): boolean {
37
+ if (!(styleEl instanceof HTMLStyleElement) || cssRule.length === 0) {
38
+ return false;
39
+ }
40
+ const markerComment = marker ? `/* autoconsent:${marker} */` : '';
41
+ if (markerComment && styleEl.innerText.includes(markerComment)) {
42
+ return true;
43
+ }
44
+ if (!markerComment && styleEl.innerText.includes(cssRule.trim())) {
45
+ return true;
46
+ }
47
+ styleEl.innerText += `${markerComment}${markerComment ? ' ' : ''}${cssRule} `;
48
+ return true;
49
+ }
50
+
35
51
  export async function waitFor(predicate: () => Promise<boolean> | boolean, maxTimes: number, interval: number): Promise<boolean> {
36
52
  const result = await predicate();
37
53
  if (!result && maxTimes > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckduckgo/autoconsent",
3
- "version": "16.27.0",
3
+ "version": "16.29.0",
4
4
  "description": "",
5
5
  "types": "./dist/types/web.d.ts",
6
6
  "exports": {
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "amazon-pay",
3
+ "vendorUrl": "https://pay.amazon.com/",
4
+ "runContext": {
5
+ "urlPattern": "^https?://pay\\.amazon\\."
6
+ },
7
+ "prehideSelectors": [".cookie-menu.js-cookie-banner"],
8
+ "detectCmp": [{ "exists": ".cookie-menu.js-cookie-banner .js-cookie-banner-close" }],
9
+ "detectPopup": [{ "visible": ".cookie-menu.js-cookie-banner" }],
10
+ "optIn": [{ "click": ".cookie-menu.js-cookie-banner .js-cookie-banner-close" }],
11
+ "optOut": [{ "click": ".cookie-menu.js-cookie-banner .js-cookie-banner-close" }],
12
+ "test": [{ "cookieContains": "apaycnst-mktg=true" }]
13
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "bbva",
3
+ "prehideSelectors": [".cookiesgdpr__base:has(.cookiesgdpr__rejectbtn)"],
4
+ "detectCmp": [{ "exists": ".cookiesgdpr__base .cookiesgdpr__rejectbtn" }],
5
+ "detectPopup": [{ "visible": ".cookiesgdpr__base .cookiesgdpr__rejectbtn" }],
6
+ "optIn": [{ "waitForThenClick": ".cookiesgdpr__base .cookiesgdpr__acceptbtn" }],
7
+ "optOut": [
8
+ { "waitForThenClick": ".cookiesgdpr__base .cookiesgdpr__rejectbtn" },
9
+ { "waitForVisible": ".cookiesgdpr__base .cookiesgdpr__wrapper", "check": "none" }
10
+ ],
11
+ "test": [{ "cookieContains": "\"analitica\":false" }]
12
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "deliveroo",
3
+ "runContext": {
4
+ "urlPattern": "^https?://(\\w+\\.)?deliveroo\\.(co\\.uk|\\w+)/"
5
+ },
6
+ "prehideSelectors": ["div[role=\"dialog\"][class*=\"CustomCookieBanner\"]"],
7
+ "detectCmp": [{ "exists": "div[role=\"dialog\"][class*=\"CustomCookieBanner\"]" }],
8
+ "detectPopup": [{ "visible": "div[role=\"dialog\"][class*=\"CustomCookieBanner\"]" }],
9
+ "optIn": [{ "waitForThenClick": "#onetrust-consent-sdk #accept-recommended-btn-handler" }],
10
+ "optOut": [
11
+ { "waitForThenClick": "#onetrust-consent-sdk .ot-pc-refuse-all-handler" },
12
+ { "waitForVisible": "div[role=\"dialog\"][class*=\"CustomCookieBanner\"]", "check": "none" }
13
+ ],
14
+ "test": [{ "cookieContains": "OptanonAlertBoxClosed" }]
15
+ }
@@ -21,11 +21,45 @@
21
21
  "optIn": [
22
22
  {
23
23
  "waitForThenClick": "#gdpr-banner-accept"
24
+ },
25
+ {
26
+ "waitForVisible": "#gdpr-banner",
27
+ "check": "none",
28
+ "timeout": 2000,
29
+ "optional": true
30
+ },
31
+ {
32
+ "if": {
33
+ "visible": "#gdpr-banner"
34
+ },
35
+ "then": [
36
+ {
37
+ "waitForThenClick": "#gdpr-banner-accept",
38
+ "timeout": 2000
39
+ }
40
+ ]
24
41
  }
25
42
  ],
26
43
  "optOut": [
27
44
  {
28
45
  "waitForThenClick": "#gdpr-banner-decline"
46
+ },
47
+ {
48
+ "waitForVisible": "#gdpr-banner",
49
+ "check": "none",
50
+ "timeout": 2000,
51
+ "optional": true
52
+ },
53
+ {
54
+ "if": {
55
+ "visible": "#gdpr-banner"
56
+ },
57
+ "then": [
58
+ {
59
+ "waitForThenClick": "#gdpr-banner-decline",
60
+ "timeout": 2000
61
+ }
62
+ ]
29
63
  }
30
64
  ]
31
65
  }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "ecbeing",
3
+ "vendorUrl": "https://www.ecbeing.net/",
4
+ "cosmetic": true,
5
+ "prehideSelectors": ["#cookieBox .block-cookie-consent"],
6
+ "detectCmp": [{ "exists": "#cookieBox #consentButton.block-cookie-consent--btn" }],
7
+ "detectPopup": [{ "visible": "#cookieBox" }],
8
+ "optIn": [{ "waitForThenClick": "#cookieBox #consentButton" }],
9
+ "optOut": [{ "hide": "#cookieBox" }]
10
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "stright",
3
+ "prehideSelectors": ["#controlled_banner-cookie_consent-consent_banner", "#controlled_banner-cookie_consent-privacy_setting_banner"],
4
+ "detectCmp": [{ "exists": "#controlled_banner-cookie_consent-consent_banner" }],
5
+ "detectPopup": [{ "visible": "#controlled_banner-cookie_consent-consent_banner" }],
6
+ "optIn": [{ "waitForThenClick": "#controlled_submit-cookie_consent-consent_banner-accept_all" }],
7
+ "optOut": [
8
+ {
9
+ "if": {
10
+ "visible": "#controlled_banner-cookie_consent-consent_banner [data-search-key-button=reject_all_text]"
11
+ },
12
+ "then": [
13
+ {
14
+ "waitForThenClick": "#controlled_banner-cookie_consent-consent_banner [data-search-key-button=reject_all_text]"
15
+ }
16
+ ],
17
+ "else": [
18
+ {
19
+ "waitForThenClick": "#controlled_submit-cookie_consent-consent_banner-privacy_setting_banner_link"
20
+ },
21
+ {
22
+ "waitForThenClick": "#controlled_banner-cookie_consent-privacy_setting_banner [data-search-key-button=reject_all_text]"
23
+ }
24
+ ]
25
+ },
26
+ { "waitForVisible": "#controlled_banner-cookie_consent-consent_banner", "check": "none" }
27
+ ],
28
+ "test": [{ "visible": "#controlled_banner-cookie_consent-consent_banner", "check": "none" }]
29
+ }
@@ -3,7 +3,7 @@
3
3
  "vendorUrl": "https://www.theguardian.com/",
4
4
  "comment": "Sourcepoint 'consent or pay' wall shown in the EU/EEA (message 1482251) and UK (message 1482252), where the only reject option requires a paid subscription. The container id embeds the Sourcepoint message id, so we target the pay-wall variants specifically to avoid clashing with the free-reject Sourcepoint popup used in other regions (handled by Sourcepoint-frame). Brittle: message ids may change if the campaign is reconfigured.",
5
5
  "cosmetic": true,
6
- "minimumRuleStepVersion": 2,
6
+ "minimumRuleStepVersion": 3,
7
7
  "runContext": {
8
8
  "urlPattern": "^https?://(\\w+\\.)?theguardian\\.com/",
9
9
  "main": true,
@@ -11,11 +11,21 @@
11
11
  },
12
12
  "prehideSelectors": ["div[id='sp_message_container_1482251'],div[id='sp_message_container_1482252']"],
13
13
  "detectCmp": [{ "exists": "div[id='sp_message_container_1482251'],div[id='sp_message_container_1482252']" }],
14
- "detectPopup": [{ "visible": "div[id='sp_message_container_1482251'],div[id='sp_message_container_1482252']" }],
14
+ "detectPopup": [
15
+ {
16
+ "any": [
17
+ { "visible": "div[id='sp_message_container_1482251'],div[id='sp_message_container_1482252']" },
18
+ { "exists": "html.sp-message-open" }
19
+ ]
20
+ }
21
+ ],
15
22
  "optIn": [],
16
23
  "optOut": [
17
24
  { "hide": "div[id='sp_message_container_1482251'],div[id='sp_message_container_1482252']" },
18
- { "removeClass": "sp-message-open", "selector": "html", "optional": true }
25
+ {
26
+ "stylesheet": "html.sp-message-open body { position: static !important; top: auto !important; width: auto !important; overflow: auto !important; }",
27
+ "stylesheetId": "theguardian-scroll-unlock"
28
+ }
19
29
  ],
20
30
  "test": [{ "visible": "div[id='sp_message_container_1482251'],div[id='sp_message_container_1482252']", "check": "none" }]
21
31
  }
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "WPConsent",
3
+ "vendorUrl": "https://wordpress.org/plugins/wpconsent-cookies-banner-privacy-suite/",
4
+ "prehideSelectors": ["#wpconsent-root"],
5
+ "detectCmp": [
6
+ {
7
+ "exists": "#wpconsent-container"
8
+ }
9
+ ],
10
+ "detectPopup": [
11
+ {
12
+ "visible": ["#wpconsent-container", "#wpconsent-banner-holder.wpconsent-banner-visible"]
13
+ }
14
+ ],
15
+ "optIn": [
16
+ {
17
+ "waitForThenClick": ["#wpconsent-container", "#wpconsent-accept-all"]
18
+ }
19
+ ],
20
+ "optOut": [
21
+ {
22
+ "waitForThenClick": ["#wpconsent-container", "#wpconsent-cancel-all"]
23
+ }
24
+ ],
25
+ "test": [
26
+ {
27
+ "cookieContains": "wpconsent_preferences="
28
+ }
29
+ ]
30
+ }