@duckduckgo/autoconsent 14.97.0 → 15.0.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/CHANGELOG.md +18 -0
- package/dist/addon-firefox/background.bundle.js +10 -1
- package/dist/addon-firefox/compact-rules.json +1 -1
- package/dist/addon-firefox/content.bundle.js +142 -33
- package/dist/addon-firefox/manifest.json +1 -1
- package/dist/addon-firefox/popup.bundle.js +10 -1
- package/dist/addon-firefox/rules.json +1 -1
- package/dist/addon-mv3/background.bundle.js +10 -1
- package/dist/addon-mv3/compact-rules.json +1 -1
- package/dist/addon-mv3/content.bundle.js +142 -33
- package/dist/addon-mv3/manifest.json +1 -1
- package/dist/addon-mv3/popup.bundle.js +10 -1
- package/dist/addon-mv3/rules.json +1 -1
- package/dist/autoconsent.cjs.js +142 -33
- package/dist/autoconsent.esm.js +142 -33
- package/dist/autoconsent.extra.cjs.js +142 -33
- package/dist/autoconsent.extra.esm.js +142 -33
- package/dist/autoconsent.playwright.js +142 -33
- package/dist/types/cmps/base.d.ts +4 -2
- package/dist/types/heuristic-patterns.d.ts +3 -0
- package/dist/types/heuristics.d.ts +4 -7
- package/dist/types/types.d.ts +18 -2
- package/lib/cmps/base.ts +20 -7
- package/lib/heuristic-patterns.ts +68 -1
- package/lib/heuristics.ts +76 -27
- package/lib/types.ts +20 -2
- package/lib/utils.ts +2 -1
- package/lib/web.ts +3 -1
- package/package.json +1 -1
- package/rules/autoconsent/twitch.json +1 -1
- package/rules/compact-rules.json +1 -1
- package/rules/rules.json +1 -1
- package/tests-wtr/heuristics/get-actionable-popups.html +16 -0
- package/tests-wtr/heuristics/get-actionable-popups.ts +85 -6
- package/tests-wtr/heuristics/heuristic-cmp.html +69 -0
- package/tests-wtr/heuristics/heuristic-cmp.ts +178 -0
- package/tests-wtr/heuristics/heuristics-utils.test.ts +96 -28
- package/tests-wtr/lifecycle/find-cmp.html +14 -0
- package/tests-wtr/lifecycle/find-cmp.ts +44 -2
package/lib/heuristics.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
ACCEPT_PATTERNS,
|
|
3
|
+
ACKNOWLEDGE_PATTERNS,
|
|
4
|
+
DETECT_PATTERNS,
|
|
5
|
+
NEVER_MATCH_PATTERNS,
|
|
6
|
+
REJECT_PATTERNS,
|
|
7
|
+
SETTINGS_PATTERNS,
|
|
8
|
+
} from './heuristic-patterns';
|
|
9
|
+
import { ButtonData, ButtonRegexClassification, PopupData, PopupHandlingMode, PopupHandlingModes } from './types';
|
|
3
10
|
import { isElementVisible, isTopFrame } from './utils';
|
|
4
11
|
|
|
5
12
|
const BUTTON_LIKE_ELEMENT_SELECTOR = 'button, input[type="button"], input[type="submit"], a, [role="button"], [class*="button"]';
|
|
@@ -21,52 +28,78 @@ export function checkHeuristicPatterns(allText: string, detectPatterns = DETECT_
|
|
|
21
28
|
return { patterns, snippets };
|
|
22
29
|
}
|
|
23
30
|
|
|
24
|
-
export function getActionablePopups(timeout = POPUP_SEARCH_MAX_TIME): PopupData[] {
|
|
31
|
+
export function getActionablePopups(mode: PopupHandlingMode = PopupHandlingModes.Reject, timeout = POPUP_SEARCH_MAX_TIME): PopupData[] {
|
|
25
32
|
const popups = getPotentialPopups(timeout);
|
|
26
33
|
const result = popups.reduce((acc, popup) => {
|
|
27
34
|
const popupText = popup.text?.trim();
|
|
28
35
|
if (popupText) {
|
|
29
36
|
const { patterns } = checkHeuristicPatterns(popupText);
|
|
30
37
|
if (patterns.length > 0) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
otherButtons,
|
|
37
|
-
});
|
|
38
|
-
}
|
|
38
|
+
classifyButtons(popup.buttons);
|
|
39
|
+
popup.regexClassification = classifyPopup(popup.buttons);
|
|
40
|
+
acc.push({
|
|
41
|
+
...popup,
|
|
42
|
+
});
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
return acc;
|
|
42
46
|
}, [] as PopupData[]);
|
|
43
|
-
|
|
47
|
+
// popups filtered by mode and sorted so a popup with reject will always win.
|
|
48
|
+
return result
|
|
49
|
+
.filter(
|
|
50
|
+
(popup) =>
|
|
51
|
+
popup.regexClassification !== undefined &&
|
|
52
|
+
popup.regexClassification !== PopupHandlingModes.None &&
|
|
53
|
+
popup.regexClassification <= mode,
|
|
54
|
+
)
|
|
55
|
+
.sort((a, b) => (a.regexClassification ?? 0) - (b.regexClassification ?? 0));
|
|
44
56
|
}
|
|
45
57
|
|
|
46
|
-
export function classifyButtons(buttons: ButtonData[])
|
|
47
|
-
const rejectButtons = [];
|
|
48
|
-
const otherButtons = [];
|
|
58
|
+
export function classifyButtons(buttons: ButtonData[]) {
|
|
49
59
|
for (const button of buttons) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
button.regexClassification = classifyButtonTextRegex(button.text);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function classifyPopup(buttons: ButtonData[]): PopupHandlingMode {
|
|
65
|
+
const { reject, settings, accept, acknowledge } = buttons.reduce(
|
|
66
|
+
(acc, button) => {
|
|
67
|
+
if (button.regexClassification && button.regexClassification !== 'other') {
|
|
68
|
+
acc[button.regexClassification]++;
|
|
69
|
+
}
|
|
70
|
+
return acc;
|
|
71
|
+
},
|
|
72
|
+
{ reject: 0, settings: 0, accept: 0, acknowledge: 0 },
|
|
73
|
+
);
|
|
74
|
+
if (reject > 0) {
|
|
75
|
+
return PopupHandlingModes.Reject;
|
|
76
|
+
}
|
|
77
|
+
if (settings > 0) {
|
|
78
|
+
return PopupHandlingModes.None;
|
|
55
79
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
80
|
+
if (acknowledge > 0) {
|
|
81
|
+
return PopupHandlingModes.Tier1;
|
|
82
|
+
}
|
|
83
|
+
if (accept > 0) {
|
|
84
|
+
return accept === 1 ? PopupHandlingModes.Tier2 : PopupHandlingModes.None;
|
|
85
|
+
}
|
|
86
|
+
return PopupHandlingModes.None;
|
|
60
87
|
}
|
|
61
88
|
|
|
62
|
-
|
|
89
|
+
/**
|
|
90
|
+
* @param {string} buttonText
|
|
91
|
+
* @param {Array<string|RegExp>} matchPatterns
|
|
92
|
+
* @param {Array<string|RegExp>} neverMatchPatterns
|
|
93
|
+
* @returns {boolean}
|
|
94
|
+
*/
|
|
95
|
+
function testButtonMatches(buttonText: string, matchPatterns: (string | RegExp)[], neverMatchPatterns: (string | RegExp)[]): boolean {
|
|
63
96
|
if (!buttonText) {
|
|
64
97
|
return false;
|
|
65
98
|
}
|
|
66
99
|
const cleanedButtonText = cleanButtonText(buttonText);
|
|
67
100
|
return (
|
|
68
|
-
!neverMatchPatterns.some((p) => p.test(cleanedButtonText)) &&
|
|
69
|
-
|
|
101
|
+
!neverMatchPatterns.some((p) => (p instanceof RegExp && p.test(cleanedButtonText)) || p === cleanedButtonText) &&
|
|
102
|
+
matchPatterns.some((p) => (p instanceof RegExp && p.test(cleanedButtonText)) || p === cleanedButtonText)
|
|
70
103
|
);
|
|
71
104
|
}
|
|
72
105
|
|
|
@@ -89,6 +122,22 @@ export function cleanButtonText(buttonText: string): string {
|
|
|
89
122
|
return result;
|
|
90
123
|
}
|
|
91
124
|
|
|
125
|
+
export function classifyButtonTextRegex(buttonText: string): ButtonRegexClassification {
|
|
126
|
+
if (testButtonMatches(buttonText, REJECT_PATTERNS, NEVER_MATCH_PATTERNS)) {
|
|
127
|
+
return 'reject';
|
|
128
|
+
}
|
|
129
|
+
if (testButtonMatches(buttonText, SETTINGS_PATTERNS, NEVER_MATCH_PATTERNS)) {
|
|
130
|
+
return 'settings';
|
|
131
|
+
}
|
|
132
|
+
if (testButtonMatches(buttonText, ACCEPT_PATTERNS, NEVER_MATCH_PATTERNS)) {
|
|
133
|
+
return 'accept';
|
|
134
|
+
}
|
|
135
|
+
if (testButtonMatches(buttonText, ACKNOWLEDGE_PATTERNS, NEVER_MATCH_PATTERNS)) {
|
|
136
|
+
return 'acknowledge';
|
|
137
|
+
}
|
|
138
|
+
return 'other';
|
|
139
|
+
}
|
|
140
|
+
|
|
92
141
|
function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME) {
|
|
93
142
|
const isFramed = !isTopFrame();
|
|
94
143
|
// do not inspect frames that are more than one level deep
|
package/lib/types.ts
CHANGED
|
@@ -78,6 +78,7 @@ export type Config = {
|
|
|
78
78
|
};
|
|
79
79
|
performanceLoggingEnabled: boolean;
|
|
80
80
|
heuristicPopupSearchTimeout: number;
|
|
81
|
+
heuristicMode: PopupHandlingMode; // controls the behavior of the heuristic popup detection. Has no effect if enableHeuristicDetection is false.
|
|
81
82
|
};
|
|
82
83
|
|
|
83
84
|
export type LifecycleState =
|
|
@@ -114,15 +115,32 @@ export type ConsentState = {
|
|
|
114
115
|
performance?: Record<string, number[]>;
|
|
115
116
|
};
|
|
116
117
|
|
|
118
|
+
export type ButtonRegexClassification = 'reject' | 'settings' | 'accept' | 'acknowledge' | 'other';
|
|
119
|
+
|
|
117
120
|
export interface ButtonData {
|
|
118
121
|
text: string;
|
|
119
122
|
element: HTMLElement;
|
|
123
|
+
regexClassification?: ButtonRegexClassification;
|
|
120
124
|
}
|
|
121
125
|
|
|
122
126
|
export interface PopupData {
|
|
123
127
|
text: string;
|
|
124
128
|
element: HTMLElement;
|
|
125
129
|
buttons: ButtonData[];
|
|
126
|
-
|
|
127
|
-
otherButtons?: ButtonData[];
|
|
130
|
+
regexClassification?: PopupHandlingMode;
|
|
128
131
|
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Controls the behavior of the heuristic popup detection.
|
|
135
|
+
* - Reject: Will click the reject button on a popup if it exists.
|
|
136
|
+
* - Tier1: Will also click the acknowledge button on a popup if it exists, and no Reject button exists.
|
|
137
|
+
* - Tier2: Will also click the accept button on a popup if it exists, and no Reject or Acknowledge button exists.
|
|
138
|
+
* - None: Disabled
|
|
139
|
+
*/
|
|
140
|
+
export const PopupHandlingModes = {
|
|
141
|
+
None: -1,
|
|
142
|
+
Reject: 0,
|
|
143
|
+
Tier1: 1,
|
|
144
|
+
Tier2: 2,
|
|
145
|
+
} as const;
|
|
146
|
+
export type PopupHandlingMode = (typeof PopupHandlingModes)[keyof typeof PopupHandlingModes];
|
package/lib/utils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HideMethod } from './rules';
|
|
2
|
-
import { Config } from './types';
|
|
2
|
+
import { Config, PopupHandlingModes } from './types';
|
|
3
3
|
|
|
4
4
|
// get or create a style container for CSS overrides
|
|
5
5
|
export function getStyleElement(styleOverrideElementId = 'autoconsent-css-rules'): HTMLStyleElement {
|
|
@@ -95,6 +95,7 @@ export function normalizeConfig(providedConfig: any): Config {
|
|
|
95
95
|
},
|
|
96
96
|
performanceLoggingEnabled: false,
|
|
97
97
|
heuristicPopupSearchTimeout: 100,
|
|
98
|
+
heuristicMode: PopupHandlingModes.Reject,
|
|
98
99
|
};
|
|
99
100
|
const updatedConfig: Config = copyObject(defaultConfig);
|
|
100
101
|
// filter out any unknown entries
|
package/lib/web.ts
CHANGED
|
@@ -296,7 +296,9 @@ export default class AutoConsent {
|
|
|
296
296
|
});
|
|
297
297
|
// heuristic CMP is only run in the top frame and only if heuristic action is enabled and retries is odd
|
|
298
298
|
const heuristicRules =
|
|
299
|
-
isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0
|
|
299
|
+
isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0
|
|
300
|
+
? [new AutoConsentHeuristicCMP(this, this.config.heuristicMode)]
|
|
301
|
+
: [];
|
|
300
302
|
|
|
301
303
|
const rulesPriorityStages: [string, AutoCMP[]][] = [
|
|
302
304
|
['site-specific', siteSpecificRules],
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "twitch.tv",
|
|
3
3
|
"runContext": {
|
|
4
|
-
"urlPattern": "^https?://(
|
|
4
|
+
"urlPattern": "^https?://([\\w-]+\\.)?twitch\\.tv"
|
|
5
5
|
},
|
|
6
6
|
"prehideSelectors": [
|
|
7
7
|
"div:has(> .consent-banner .consent-banner__content--gdpr-v2),.ReactModalPortal:has([data-a-target=consent-modal-save])"
|