@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.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +40 -0
- package/dist/addon-firefox/compact-rules.json +1 -1
- package/dist/addon-firefox/content.bundle.js +43 -6
- package/dist/addon-firefox/manifest.json +1 -1
- package/dist/addon-firefox/rule-index.json +1 -1
- package/dist/addon-firefox/rules.json +1 -1
- package/dist/addon-mv3/compact-rules.json +1 -1
- package/dist/addon-mv3/content.bundle.js +43 -6
- package/dist/addon-mv3/manifest.json +1 -1
- package/dist/addon-mv3/rule-index.json +1 -1
- package/dist/addon-mv3/rules.json +1 -1
- package/dist/autoconsent.cjs.js +43 -6
- package/dist/autoconsent.esm.js +43 -6
- package/dist/autoconsent.playwright.js +43 -6
- package/dist/autoconsent.standalone.js +44 -7
- package/dist/types/cmps/base.d.ts +2 -1
- package/dist/types/dom-actions.d.ts +3 -1
- package/dist/types/rules.d.ts +16 -2
- package/dist/types/types.d.ts +1 -1
- package/dist/types/utils.d.ts +1 -0
- package/docs/rule-syntax.md +26 -1
- package/lib/cmps/base.ts +10 -3
- package/lib/dom-actions.ts +33 -3
- package/lib/rules.ts +19 -2
- package/lib/types.ts +7 -1
- package/lib/utils.ts +16 -0
- package/package.json +1 -1
- package/rules/autoconsent/amazon-pay.json +13 -0
- package/rules/autoconsent/bbva.json +12 -0
- package/rules/autoconsent/deliveroo.json +15 -0
- package/rules/autoconsent/ebay.json +34 -0
- package/rules/autoconsent/ecbeing.json +10 -0
- package/rules/autoconsent/stright.json +29 -0
- package/rules/autoconsent/theguardian.com.json +13 -3
- package/rules/autoconsent/wpconsent.json +30 -0
- package/rules/compact-rules.json +1 -1
- package/rules/rule-index.json +1 -1
- package/rules/rules.json +1 -1
- package/tests/amazon-pay.spec.ts +3 -0
- package/tests/bbva.spec.ts +4 -0
- package/tests/deliveroo.spec.ts +3 -0
- package/tests/ecbeing.spec.ts +3 -0
- package/tests/stright.spec.ts +8 -0
- package/tests/wpconsent.spec.ts +5 -0
- package/tests-wtr/dom-actions/dom-actions.stylesheet.html +13 -0
- package/tests-wtr/dom-actions/dom-actions.stylesheet.ts +26 -0
- package/tests-wtr/dom-actions/dom-actions.wait-for-then-click.html +14 -0
- package/tests-wtr/dom-actions/dom-actions.wait-for-then-click.ts +106 -0
- package/tests-wtr/utils/append-stylesheet-rule.html +11 -0
- 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;
|
package/dist/types/rules.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { snippets } from './eval-snippets';
|
|
2
|
-
export declare const SUPPORTED_RULE_STEP_VERSION =
|
|
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
|
+
};
|
package/dist/types/types.d.ts
CHANGED
|
@@ -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;
|
package/dist/types/utils.d.ts
CHANGED
|
@@ -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;
|
package/docs/rule-syntax.md
CHANGED
|
@@ -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
|
}
|
package/lib/dom-actions.ts
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
@@ -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":
|
|
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": [
|
|
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
|
-
{
|
|
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
|
+
}
|