@duckduckgo/autoconsent 8.2.0 → 9.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 +17 -0
- package/build.sh +1 -0
- package/dist/addon-firefox/background.bundle.js +45 -42
- package/dist/addon-firefox/content.bundle.js +468 -380
- package/dist/addon-firefox/manifest.json +1 -1
- package/dist/addon-mv3/background.bundle.js +45 -42
- package/dist/addon-mv3/content.bundle.js +468 -380
- package/dist/addon-mv3/manifest.json +1 -1
- package/dist/addon-mv3/popup.bundle.js +71 -33
- package/dist/addon-mv3/popup.html +28 -0
- package/dist/autoconsent.cjs.js +468 -380
- package/dist/autoconsent.esm.js +468 -380
- package/dist/autoconsent.playwright.js +1 -1
- package/dist/autoconsent.unit.js +10370 -0
- package/lib/cmps/airbnb.ts +5 -6
- package/lib/cmps/base.ts +97 -41
- package/lib/cmps/consentmanager.ts +13 -14
- package/lib/cmps/conversant.ts +8 -9
- package/lib/cmps/cookiebot.ts +8 -9
- package/lib/cmps/evidon.ts +7 -8
- package/lib/cmps/klaro.ts +13 -14
- package/lib/cmps/onetrust.ts +15 -16
- package/lib/cmps/sourcepoint-frame.ts +25 -26
- package/lib/cmps/tiktok.ts +7 -7
- package/lib/cmps/trustarc-frame.ts +27 -28
- package/lib/cmps/trustarc-top.ts +5 -6
- package/lib/cmps/uniconsent.ts +9 -10
- package/lib/dom-actions.ts +145 -0
- package/lib/types.ts +24 -1
- package/lib/utils.ts +32 -1
- package/lib/web.ts +46 -34
- package/package.json +4 -4
- package/playwright/runner.ts +11 -3
- package/playwright/unit.ts +15 -0
- package/readme.md +1 -1
- package/tests/{rule-executors.spec.ts → dom-actions.spec.ts} +20 -21
- package/lib/config.ts +0 -2
- package/lib/rule-executors.ts +0 -147
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { enableLogs } from "../config";
|
|
2
|
-
import { click, elementExists, wait, waitForElement, waitForThenClick } from "../rule-executors";
|
|
3
1
|
import { RunContext } from "../rules";
|
|
4
2
|
import { waitFor } from "../utils";
|
|
5
3
|
import AutoConsentCMPBase from "./base";
|
|
@@ -47,20 +45,20 @@ export default class SourcePoint extends AutoConsentCMPBase {
|
|
|
47
45
|
return true;
|
|
48
46
|
}
|
|
49
47
|
if (this.ccpaPopup) {
|
|
50
|
-
return await waitForElement('.priv-save-btn', 2000);
|
|
48
|
+
return await this.waitForElement('.priv-save-btn', 2000);
|
|
51
49
|
}
|
|
52
50
|
// check for the paywall button, and bail if it exists to prevent broken opt out
|
|
53
|
-
await waitForElement(".sp_choice_type_11,.sp_choice_type_12,.sp_choice_type_13,.sp_choice_type_ACCEPT_ALL,.sp_choice_type_SAVE_AND_EXIT", 2000);
|
|
54
|
-
return !elementExists('.sp_choice_type_9');
|
|
51
|
+
await this.waitForElement(".sp_choice_type_11,.sp_choice_type_12,.sp_choice_type_13,.sp_choice_type_ACCEPT_ALL,.sp_choice_type_SAVE_AND_EXIT", 2000);
|
|
52
|
+
return !this.elementExists('.sp_choice_type_9');
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
async optIn() {
|
|
58
|
-
await waitForElement(".sp_choice_type_11,.sp_choice_type_ACCEPT_ALL", 2000);
|
|
59
|
-
if (click(".sp_choice_type_11")) {
|
|
56
|
+
await this.waitForElement(".sp_choice_type_11,.sp_choice_type_ACCEPT_ALL", 2000);
|
|
57
|
+
if (this.click(".sp_choice_type_11")) {
|
|
60
58
|
return true;
|
|
61
59
|
}
|
|
62
60
|
|
|
63
|
-
if (click('.sp_choice_type_ACCEPT_ALL')) {
|
|
61
|
+
if (this.click('.sp_choice_type_ACCEPT_ALL')) {
|
|
64
62
|
return true;
|
|
65
63
|
}
|
|
66
64
|
return false;
|
|
@@ -71,6 +69,7 @@ export default class SourcePoint extends AutoConsentCMPBase {
|
|
|
71
69
|
}
|
|
72
70
|
|
|
73
71
|
async optOut() {
|
|
72
|
+
const logsConfig = this.autoconsent.config.logs;
|
|
74
73
|
if (this.ccpaPopup) {
|
|
75
74
|
// toggles with 2 buttons
|
|
76
75
|
const toggles = document.querySelectorAll('.priv-purpose-container .sp-switch-arrow-block a.neutral.on .right') as NodeListOf<HTMLElement>;
|
|
@@ -82,19 +81,19 @@ export default class SourcePoint extends AutoConsentCMPBase {
|
|
|
82
81
|
for (const t of switches) {
|
|
83
82
|
t.click()
|
|
84
83
|
}
|
|
85
|
-
return click('.priv-save-btn');
|
|
84
|
+
return this.click('.priv-save-btn');
|
|
86
85
|
}
|
|
87
86
|
if (!this.isManagerOpen()) {
|
|
88
|
-
const actionable = await waitForElement('.sp_choice_type_12,.sp_choice_type_13');
|
|
87
|
+
const actionable = await this.waitForElement('.sp_choice_type_12,.sp_choice_type_13');
|
|
89
88
|
if (!actionable) {
|
|
90
89
|
return false;
|
|
91
90
|
}
|
|
92
|
-
if (!elementExists(".sp_choice_type_12")) {
|
|
91
|
+
if (!this.elementExists(".sp_choice_type_12")) {
|
|
93
92
|
// do not sell button
|
|
94
|
-
return click(".sp_choice_type_13");
|
|
93
|
+
return this.click(".sp_choice_type_13");
|
|
95
94
|
}
|
|
96
95
|
|
|
97
|
-
click(".sp_choice_type_12");
|
|
96
|
+
this.click(".sp_choice_type_12");
|
|
98
97
|
// the page may navigate at this point but that's okay
|
|
99
98
|
await waitFor(
|
|
100
99
|
() => this.isManagerOpen(),
|
|
@@ -103,35 +102,35 @@ export default class SourcePoint extends AutoConsentCMPBase {
|
|
|
103
102
|
);
|
|
104
103
|
}
|
|
105
104
|
|
|
106
|
-
await waitForElement('.type-modal', 20000);
|
|
105
|
+
await this.waitForElement('.type-modal', 20000);
|
|
107
106
|
|
|
108
107
|
// check "Do Not Sell" (CCPA) toggle if it exists
|
|
109
|
-
waitForThenClick('.ccpa-stack .pm-switch[aria-checked=true] .slider', 500, true); // the UI is reversed: "unchecked" switch displays as an enabled toggle
|
|
108
|
+
this.waitForThenClick('.ccpa-stack .pm-switch[aria-checked=true] .slider', 500, true); // the UI is reversed: "unchecked" switch displays as an enabled toggle
|
|
110
109
|
|
|
111
110
|
// reject all button is offered by some sites
|
|
112
111
|
try {
|
|
113
112
|
const rejectSelector1 = '.sp_choice_type_REJECT_ALL';
|
|
114
113
|
const rejectSelector2 = '.reject-toggle';
|
|
115
114
|
const path = await Promise.race([
|
|
116
|
-
waitForElement(rejectSelector1, 2000).then(success => success ? 0: -1),
|
|
117
|
-
waitForElement(rejectSelector2, 2000).then(success => success ? 1: -1),
|
|
118
|
-
waitForElement('.pm-features', 2000).then(success => success ? 2: -1),
|
|
115
|
+
this.waitForElement(rejectSelector1, 2000).then(success => success ? 0: -1),
|
|
116
|
+
this.waitForElement(rejectSelector2, 2000).then(success => success ? 1: -1),
|
|
117
|
+
this.waitForElement('.pm-features', 2000).then(success => success ? 2: -1),
|
|
119
118
|
]);
|
|
120
119
|
if (path === 0) {
|
|
121
|
-
await wait(1000);
|
|
122
|
-
return click(rejectSelector1);
|
|
120
|
+
await this.wait(1000);
|
|
121
|
+
return this.click(rejectSelector1);
|
|
123
122
|
} else if (path === 1) {
|
|
124
|
-
click(rejectSelector2);
|
|
123
|
+
this.click(rejectSelector2);
|
|
125
124
|
} else if (path === 2) {
|
|
126
|
-
await waitForElement('.pm-features', 10000);
|
|
127
|
-
click('.checked > span', true);
|
|
125
|
+
await this.waitForElement('.pm-features', 10000);
|
|
126
|
+
this.click('.checked > span', true);
|
|
128
127
|
|
|
129
|
-
click('.chevron');
|
|
128
|
+
this.click('.chevron');
|
|
130
129
|
}
|
|
131
130
|
} catch (e) {
|
|
132
|
-
|
|
131
|
+
logsConfig.errors && console.warn(e);
|
|
133
132
|
}
|
|
134
133
|
// TODO: race condition: if the reject button was clicked, the popup disappears very quickly, so the background script may not receive a success report.
|
|
135
|
-
return click('.sp_choice_type_SAVE_AND_EXIT');
|
|
134
|
+
return this.click('.sp_choice_type_SAVE_AND_EXIT');
|
|
136
135
|
}
|
|
137
136
|
}
|
package/lib/cmps/tiktok.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { enableLogs } from "../config";
|
|
2
|
-
import { elementExists } from "../rule-executors";
|
|
3
1
|
import { RunContext } from "../rules";
|
|
4
2
|
import { isElementVisible } from "../utils";
|
|
5
3
|
import AutoConsentCMPBase from "./base";
|
|
@@ -32,7 +30,7 @@ export default class Tiktok extends AutoConsentCMPBase {
|
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
async detectCmp() {
|
|
35
|
-
return elementExists("tiktok-cookie-banner");
|
|
33
|
+
return this.elementExists("tiktok-cookie-banner");
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
async detectPopup() {
|
|
@@ -41,25 +39,27 @@ export default class Tiktok extends AutoConsentCMPBase {
|
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
async optOut() {
|
|
42
|
+
const logsConfig = this.autoconsent.config.logs;
|
|
44
43
|
const declineButton = this.getShadowRoot().querySelector('.button-wrapper button:first-child') as HTMLElement;
|
|
45
44
|
if (declineButton) {
|
|
46
|
-
|
|
45
|
+
logsConfig.rulesteps && console.log("[clicking]", declineButton);
|
|
47
46
|
declineButton.click();
|
|
48
47
|
return true;
|
|
49
48
|
} else {
|
|
50
|
-
|
|
49
|
+
logsConfig.errors && console.log("no decline button found");
|
|
51
50
|
return false;
|
|
52
51
|
}
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
async optIn() {
|
|
55
|
+
const logsConfig = this.autoconsent.config.logs;
|
|
56
56
|
const acceptButton = this.getShadowRoot().querySelector('.button-wrapper button:last-child') as HTMLElement;
|
|
57
57
|
if (acceptButton) {
|
|
58
|
-
|
|
58
|
+
logsConfig.rulesteps && console.log("[clicking]", acceptButton);
|
|
59
59
|
acceptButton.click();
|
|
60
60
|
return true;
|
|
61
61
|
} else {
|
|
62
|
-
|
|
62
|
+
logsConfig.errors && console.log("no accept button found");
|
|
63
63
|
return false;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { click, elementExists, elementVisible, waitForElement } from "../rule-executors";
|
|
2
1
|
import { RunContext } from "../rules";
|
|
3
2
|
import { waitFor } from "../utils";
|
|
4
3
|
import AutoConsentCMPBase from "./base";
|
|
@@ -30,7 +29,7 @@ export default class TrustArcFrame extends AutoConsentCMPBase {
|
|
|
30
29
|
|
|
31
30
|
async detectPopup() {
|
|
32
31
|
// we're already inside the popup
|
|
33
|
-
return elementVisible("#defaultpreferencemanager", 'any') && elementVisible(".mainContent", 'any');
|
|
32
|
+
return this.elementVisible("#defaultpreferencemanager", 'any') && this.elementVisible(".mainContent", 'any');
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
async navigateToSettings() {
|
|
@@ -38,29 +37,29 @@ export default class TrustArcFrame extends AutoConsentCMPBase {
|
|
|
38
37
|
await waitFor(
|
|
39
38
|
async () => {
|
|
40
39
|
return (
|
|
41
|
-
elementExists(".shp") ||
|
|
42
|
-
elementVisible(".advance", 'any') ||
|
|
43
|
-
elementExists(".switch span:first-child")
|
|
40
|
+
this.elementExists(".shp") ||
|
|
41
|
+
this.elementVisible(".advance", 'any') ||
|
|
42
|
+
this.elementExists(".switch span:first-child")
|
|
44
43
|
);
|
|
45
44
|
},
|
|
46
45
|
10,
|
|
47
46
|
500
|
|
48
47
|
);
|
|
49
48
|
// splash screen -> hit more information
|
|
50
|
-
if (elementExists(".shp")) {
|
|
51
|
-
click(".shp");
|
|
49
|
+
if (this.elementExists(".shp")) {
|
|
50
|
+
this.click(".shp");
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
await waitForElement(".prefPanel", 5000);
|
|
53
|
+
await this.waitForElement(".prefPanel", 5000);
|
|
55
54
|
|
|
56
55
|
// go to advanced settings if not yet shown
|
|
57
|
-
if (elementVisible(".advance", 'any')) {
|
|
58
|
-
click(".advance");
|
|
56
|
+
if (this.elementVisible(".advance", 'any')) {
|
|
57
|
+
this.click(".advance");
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
// takes a while to load the opt-in/opt-out buttons
|
|
62
61
|
return await waitFor(
|
|
63
|
-
() => elementVisible(".switch span:first-child", 'any'),
|
|
62
|
+
() => this.elementVisible(".switch span:first-child", 'any'),
|
|
64
63
|
5,
|
|
65
64
|
1000
|
|
66
65
|
);
|
|
@@ -68,51 +67,51 @@ export default class TrustArcFrame extends AutoConsentCMPBase {
|
|
|
68
67
|
|
|
69
68
|
async optOut() {
|
|
70
69
|
await waitFor(() => document.readyState === 'complete', 20, 100);
|
|
71
|
-
await waitForElement(".mainContent[aria-hidden=false]", 5000);
|
|
70
|
+
await this.waitForElement(".mainContent[aria-hidden=false]", 5000);
|
|
72
71
|
|
|
73
|
-
if (click(".rejectAll")) {
|
|
72
|
+
if (this.click(".rejectAll")) {
|
|
74
73
|
return true;
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
if (elementExists('.prefPanel')) {
|
|
78
|
-
await waitForElement('.prefPanel[style="visibility: visible;"]', 3000);
|
|
76
|
+
if (this.elementExists('.prefPanel')) {
|
|
77
|
+
await this.waitForElement('.prefPanel[style="visibility: visible;"]', 3000);
|
|
79
78
|
}
|
|
80
79
|
|
|
81
|
-
if (click("#catDetails0")) {
|
|
82
|
-
click(".submit");
|
|
83
|
-
waitForThenClick("#gwt-debug-close_id", 5000);
|
|
80
|
+
if (this.click("#catDetails0")) {
|
|
81
|
+
this.click(".submit");
|
|
82
|
+
this.waitForThenClick("#gwt-debug-close_id", 5000);
|
|
84
83
|
return true;
|
|
85
84
|
}
|
|
86
85
|
|
|
87
|
-
if (click(".required")) {
|
|
88
|
-
waitForThenClick("#gwt-debug-close_id", 5000);
|
|
86
|
+
if (this.click(".required")) {
|
|
87
|
+
this.waitForThenClick("#gwt-debug-close_id", 5000);
|
|
89
88
|
return true;
|
|
90
89
|
}
|
|
91
90
|
|
|
92
91
|
await this.navigateToSettings();
|
|
93
92
|
|
|
94
|
-
click(".switch span:nth-child(1):not(.active)", true);
|
|
93
|
+
this.click(".switch span:nth-child(1):not(.active)", true);
|
|
95
94
|
|
|
96
|
-
click(".submit");
|
|
95
|
+
this.click(".submit");
|
|
97
96
|
|
|
98
97
|
// at this point, iframe usually closes. Sometimes we need to close manually, but we don't wait for it to report success
|
|
99
|
-
waitForThenClick("#gwt-debug-close_id", 300000);
|
|
98
|
+
this.waitForThenClick("#gwt-debug-close_id", 300000);
|
|
100
99
|
|
|
101
100
|
return true;
|
|
102
101
|
}
|
|
103
102
|
|
|
104
103
|
async optIn() {
|
|
105
|
-
if (click('.call')) {
|
|
104
|
+
if (this.click('.call')) {
|
|
106
105
|
return true;
|
|
107
106
|
}
|
|
108
107
|
await this.navigateToSettings();
|
|
109
|
-
click(".switch span:nth-child(2)", true);
|
|
108
|
+
this.click(".switch span:nth-child(2)", true);
|
|
110
109
|
|
|
111
|
-
click(".submit");
|
|
110
|
+
this.click(".submit");
|
|
112
111
|
|
|
113
112
|
// at this point, iframe usually closes. Sometimes we need to close manually, but we don't wait for it to report success
|
|
114
|
-
waitForElement("#gwt-debug-close_id", 300000).then(() => {
|
|
115
|
-
click("#gwt-debug-close_id");
|
|
113
|
+
this.waitForElement("#gwt-debug-close_id", 300000).then(() => {
|
|
114
|
+
this.click("#gwt-debug-close_id");
|
|
116
115
|
});
|
|
117
116
|
|
|
118
117
|
return true;
|
package/lib/cmps/trustarc-top.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { click, elementExists, elementVisible } from "../rule-executors";
|
|
2
1
|
import { RunContext } from "../rules";
|
|
3
2
|
import { getStyleElement, hideElements } from "../utils";
|
|
4
3
|
import AutoConsent from "../web";
|
|
@@ -47,7 +46,7 @@ export default class TrustArcTop extends AutoConsentCMPBase {
|
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
async detectCmp() {
|
|
50
|
-
const result = elementExists(`${cookieSettingsButton},${bannerContainer}`);
|
|
49
|
+
const result = this.elementExists(`${cookieSettingsButton},${bannerContainer}`);
|
|
51
50
|
if (result) {
|
|
52
51
|
// additionally detect the opt-out button
|
|
53
52
|
this._shortcutButton = document.querySelector(shortcutOptOut);
|
|
@@ -57,11 +56,11 @@ export default class TrustArcTop extends AutoConsentCMPBase {
|
|
|
57
56
|
|
|
58
57
|
async detectPopup() {
|
|
59
58
|
// not every element should exist, but if it does, it's a popup
|
|
60
|
-
return elementVisible(`${popupContent},${bannerOverlay},${bannerContainer}`, 'all');
|
|
59
|
+
return this.elementVisible(`${popupContent},${bannerOverlay},${bannerContainer}`, 'all');
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
openFrame() {
|
|
64
|
-
click(cookieSettingsButton);
|
|
63
|
+
this.click(cookieSettingsButton);
|
|
65
64
|
}
|
|
66
65
|
|
|
67
66
|
async optOut() {
|
|
@@ -75,7 +74,7 @@ export default class TrustArcTop extends AutoConsentCMPBase {
|
|
|
75
74
|
getStyleElement(),
|
|
76
75
|
`.truste_popframe, .truste_overlay, .truste_box_overlay, ${bannerContainer}`,
|
|
77
76
|
);
|
|
78
|
-
click(cookieSettingsButton);
|
|
77
|
+
this.click(cookieSettingsButton);
|
|
79
78
|
|
|
80
79
|
// schedule cleanup
|
|
81
80
|
setTimeout(() => {
|
|
@@ -87,7 +86,7 @@ export default class TrustArcTop extends AutoConsentCMPBase {
|
|
|
87
86
|
|
|
88
87
|
async optIn() {
|
|
89
88
|
this._optInDone = true; // just a hack to force autoconsentDone
|
|
90
|
-
return click(shortcutOptIn);
|
|
89
|
+
return this.click(shortcutOptIn);
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
async openCmp() {
|
package/lib/cmps/uniconsent.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { elementExists, elementVisible, wait, waitForElement, waitForThenClick } from "../rule-executors";
|
|
2
1
|
import AutoConsentCMPBase from "./base";
|
|
3
2
|
|
|
4
3
|
export default class Uniconsent extends AutoConsentCMPBase {
|
|
@@ -21,15 +20,15 @@ export default class Uniconsent extends AutoConsentCMPBase {
|
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
async detectCmp() {
|
|
24
|
-
return elementExists(".unic .unic-box,.unic .unic-bar");
|
|
23
|
+
return this.elementExists(".unic .unic-box,.unic .unic-bar");
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
async detectPopup() {
|
|
28
|
-
return elementVisible(".unic .unic-box,.unic .unic-bar", 'any');
|
|
27
|
+
return this.elementVisible(".unic .unic-box,.unic .unic-bar", 'any');
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
async optOut() {
|
|
32
|
-
await waitForElement(".unic button", 1000);
|
|
31
|
+
await this.waitForElement(".unic button", 1000);
|
|
33
32
|
document.querySelectorAll(".unic button").forEach((button: HTMLButtonElement) => {
|
|
34
33
|
const text = button.textContent;
|
|
35
34
|
if (text.includes('Manage Options') || text.includes('Optionen verwalten')) {
|
|
@@ -37,8 +36,8 @@ export default class Uniconsent extends AutoConsentCMPBase {
|
|
|
37
36
|
}
|
|
38
37
|
});
|
|
39
38
|
|
|
40
|
-
if (await waitForElement('.unic input[type=checkbox]', 1000)) {
|
|
41
|
-
await waitForElement('.unic button', 1000);
|
|
39
|
+
if (await this.waitForElement('.unic input[type=checkbox]', 1000)) {
|
|
40
|
+
await this.waitForElement('.unic button', 1000);
|
|
42
41
|
|
|
43
42
|
document.querySelectorAll('.unic input[type=checkbox]').forEach((c: HTMLInputElement) => {
|
|
44
43
|
if (c.checked) {
|
|
@@ -51,7 +50,7 @@ export default class Uniconsent extends AutoConsentCMPBase {
|
|
|
51
50
|
for (const pattern of ['Confirm Choices', 'Save Choices', 'Auswahl speichern']) {
|
|
52
51
|
if (text.includes(pattern)) {
|
|
53
52
|
b.click();
|
|
54
|
-
await wait(500); // give it some time to close the popup
|
|
53
|
+
await this.wait(500); // give it some time to close the popup
|
|
55
54
|
return true;
|
|
56
55
|
}
|
|
57
56
|
}
|
|
@@ -62,12 +61,12 @@ export default class Uniconsent extends AutoConsentCMPBase {
|
|
|
62
61
|
}
|
|
63
62
|
|
|
64
63
|
async optIn() {
|
|
65
|
-
return waitForThenClick(".unic #unic-agree");
|
|
64
|
+
return this.waitForThenClick(".unic #unic-agree");
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
async test() {
|
|
69
|
-
await wait(1000);
|
|
70
|
-
const res = elementExists(".unic .unic-box,.unic .unic-bar");
|
|
68
|
+
await this.wait(1000);
|
|
69
|
+
const res = this.elementExists(".unic .unic-box,.unic .unic-bar");
|
|
71
70
|
return !res;
|
|
72
71
|
}
|
|
73
72
|
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { ElementSelector, HideMethod, VisibilityCheck } from "./rules";
|
|
2
|
+
import { DomActionsProvider } from "./types";
|
|
3
|
+
import { getStyleElement, hideElements, isElementVisible, waitFor } from "./utils";
|
|
4
|
+
import AutoConsent from "./web";
|
|
5
|
+
|
|
6
|
+
export class DomActions implements DomActionsProvider {
|
|
7
|
+
constructor(public autoconsentInstance: AutoConsent) { }
|
|
8
|
+
|
|
9
|
+
click(selector: ElementSelector, all = false): boolean {
|
|
10
|
+
const elem = this.elementSelector(selector)
|
|
11
|
+
this.autoconsentInstance.config.logs.rulesteps && console.log("[click]", selector, all, elem);
|
|
12
|
+
if (elem.length > 0) {
|
|
13
|
+
if (all) {
|
|
14
|
+
elem.forEach((e) => e.click());
|
|
15
|
+
} else {
|
|
16
|
+
elem[0].click();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return elem.length > 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
elementExists(selector: ElementSelector): boolean {
|
|
23
|
+
const exists = this.elementSelector(selector).length > 0;
|
|
24
|
+
return exists;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
elementVisible(selector: ElementSelector, check: VisibilityCheck): boolean {
|
|
28
|
+
const elem = this.elementSelector(selector);
|
|
29
|
+
const results = new Array(elem.length);
|
|
30
|
+
elem.forEach((e, i) => {
|
|
31
|
+
// check for display: none
|
|
32
|
+
results[i] = isElementVisible(e);
|
|
33
|
+
});
|
|
34
|
+
if (check === "none") {
|
|
35
|
+
return results.every(r => !r);
|
|
36
|
+
} else if (results.length === 0) {
|
|
37
|
+
return false;
|
|
38
|
+
} else if (check === "any") {
|
|
39
|
+
return results.some(r => r);
|
|
40
|
+
}
|
|
41
|
+
// all
|
|
42
|
+
return results.every(r => r);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
waitForElement(selector: ElementSelector, timeout = 10000): Promise<boolean> {
|
|
46
|
+
const interval = 200;
|
|
47
|
+
const times = Math.ceil((timeout) / interval);
|
|
48
|
+
this.autoconsentInstance.config.logs.rulesteps && console.log("[waitForElement]", selector);
|
|
49
|
+
return waitFor(
|
|
50
|
+
() => this.elementSelector(selector).length > 0,
|
|
51
|
+
times,
|
|
52
|
+
interval
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
waitForVisible(selector: ElementSelector, timeout = 10000, check: VisibilityCheck = 'any'): Promise<boolean> {
|
|
57
|
+
const interval = 200;
|
|
58
|
+
const times = Math.ceil((timeout) / interval);
|
|
59
|
+
return waitFor(
|
|
60
|
+
() => this.elementVisible(selector, check),
|
|
61
|
+
times,
|
|
62
|
+
interval
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async waitForThenClick(selector: ElementSelector, timeout = 10000, all = false): Promise<boolean> {
|
|
67
|
+
await this.waitForElement(selector, timeout);
|
|
68
|
+
return this.click(selector, all);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
wait(ms: number): Promise<true> {
|
|
72
|
+
return new Promise(resolve => {
|
|
73
|
+
setTimeout(() => {
|
|
74
|
+
resolve(true);
|
|
75
|
+
}, ms);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
hide(selector: string, method: HideMethod): boolean {
|
|
80
|
+
const styleEl = getStyleElement();
|
|
81
|
+
return hideElements(styleEl, selector, method);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
prehide(selector: string): boolean {
|
|
85
|
+
const styleEl = getStyleElement('autoconsent-prehide');
|
|
86
|
+
this.autoconsentInstance.config.logs.lifecycle && console.log("[prehide]", styleEl, location.href);
|
|
87
|
+
return hideElements(styleEl, selector, "opacity");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
undoPrehide(): boolean {
|
|
91
|
+
const existingElement = getStyleElement('autoconsent-prehide');
|
|
92
|
+
this.autoconsentInstance.config.logs.lifecycle && console.log("[undoprehide]", existingElement, location.href);
|
|
93
|
+
if (existingElement) {
|
|
94
|
+
existingElement.remove();
|
|
95
|
+
}
|
|
96
|
+
return !!existingElement;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
querySingleReplySelector(selector: string, parent: any = document): HTMLElement[] {
|
|
100
|
+
if (selector.startsWith('aria/')) {
|
|
101
|
+
return []
|
|
102
|
+
}
|
|
103
|
+
if (selector.startsWith('xpath/')) {
|
|
104
|
+
const xpath = selector.slice(6)
|
|
105
|
+
const result = document.evaluate(xpath, parent, null, XPathResult.ANY_TYPE, null)
|
|
106
|
+
let node: Node = null
|
|
107
|
+
const elements: HTMLElement[] = []
|
|
108
|
+
// eslint-disable-next-line no-cond-assign
|
|
109
|
+
while (node = result.iterateNext()) {
|
|
110
|
+
elements.push(node as HTMLElement)
|
|
111
|
+
}
|
|
112
|
+
return elements
|
|
113
|
+
}
|
|
114
|
+
if (selector.startsWith('text/')) {
|
|
115
|
+
return []
|
|
116
|
+
}
|
|
117
|
+
if (selector.startsWith('pierce/')) {
|
|
118
|
+
return []
|
|
119
|
+
}
|
|
120
|
+
if (parent.shadowRoot) {
|
|
121
|
+
return Array.from(parent.shadowRoot.querySelectorAll(selector))
|
|
122
|
+
}
|
|
123
|
+
return Array.from(parent.querySelectorAll(selector))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
querySelectorChain(selectors: string[]): HTMLElement[] {
|
|
127
|
+
let parent: ParentNode = document
|
|
128
|
+
let matches: HTMLElement[]
|
|
129
|
+
for (const selector of selectors) {
|
|
130
|
+
matches = this.querySingleReplySelector(selector, parent)
|
|
131
|
+
if (matches.length === 0) {
|
|
132
|
+
return []
|
|
133
|
+
}
|
|
134
|
+
parent = matches[0]
|
|
135
|
+
}
|
|
136
|
+
return matches;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
elementSelector(selector: ElementSelector): HTMLElement[] {
|
|
140
|
+
if (typeof selector === 'string') {
|
|
141
|
+
return this.querySingleReplySelector(selector)
|
|
142
|
+
}
|
|
143
|
+
return this.querySelectorChain(selector)
|
|
144
|
+
}
|
|
145
|
+
}
|
package/lib/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ContentScriptMessage } from "./messages";
|
|
2
2
|
import { ConsentOMaticConfig } from "./cmps/consentomatic";
|
|
3
|
-
import { AutoConsentCMPRule, RunContext } from "./rules";
|
|
3
|
+
import { AutoConsentCMPRule, ElementSelector, HideMethod, RunContext, VisibilityCheck } from "./rules";
|
|
4
4
|
|
|
5
5
|
export type MessageSender = (message: ContentScriptMessage) => Promise<void>;
|
|
6
6
|
|
|
@@ -20,6 +20,22 @@ export interface AutoCMP {
|
|
|
20
20
|
test(): Promise<boolean>
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
export interface DomActionsProvider {
|
|
24
|
+
click(selector: ElementSelector, all: boolean): boolean;
|
|
25
|
+
elementExists(selector: ElementSelector): boolean;
|
|
26
|
+
elementVisible(selector: ElementSelector, check: VisibilityCheck): boolean;
|
|
27
|
+
waitForElement(selector: ElementSelector, timeout?: number): Promise<boolean>;
|
|
28
|
+
waitForVisible(selector: ElementSelector, timeout?: number, check?: VisibilityCheck): Promise<boolean>;
|
|
29
|
+
waitForThenClick(selector: ElementSelector, timeout?: number, all?: boolean): Promise<boolean>;
|
|
30
|
+
wait(ms: number): Promise<true>;
|
|
31
|
+
hide(selector: string, method: HideMethod): boolean;
|
|
32
|
+
prehide(selector: string): boolean;
|
|
33
|
+
undoPrehide(): boolean;
|
|
34
|
+
querySingleReplySelector(selector: string, parent?: any): HTMLElement[];
|
|
35
|
+
querySelectorChain(selectors: string[]): HTMLElement[];
|
|
36
|
+
elementSelector(selector: ElementSelector): HTMLElement[];
|
|
37
|
+
}
|
|
38
|
+
|
|
23
39
|
export type RuleBundle = {
|
|
24
40
|
autoconsent: AutoConsentCMPRule[];
|
|
25
41
|
consentomatic: { [name: string]: ConsentOMaticConfig };
|
|
@@ -36,6 +52,13 @@ export type Config = {
|
|
|
36
52
|
detectRetries: number;
|
|
37
53
|
isMainWorld: boolean;
|
|
38
54
|
prehideTimeout: number;
|
|
55
|
+
logs: {
|
|
56
|
+
lifecycle: boolean;
|
|
57
|
+
rulesteps: boolean;
|
|
58
|
+
evals: boolean;
|
|
59
|
+
errors: boolean;
|
|
60
|
+
messages: boolean;
|
|
61
|
+
};
|
|
39
62
|
}
|
|
40
63
|
|
|
41
64
|
export type LifecycleState = 'loading' |
|
package/lib/utils.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HideMethod } from "./rules";
|
|
2
|
+
import { Config } from "./types";
|
|
2
3
|
|
|
3
4
|
// get or create a style container for CSS overrides
|
|
4
5
|
export function getStyleElement(styleOverrideElementId = "autoconsent-css-rules"): HTMLStyleElement {
|
|
@@ -59,4 +60,34 @@ export function isElementVisible(elem: HTMLElement): boolean {
|
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
return false;
|
|
62
|
-
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function normalizeConfig(providedConfig: any): Config {
|
|
66
|
+
const defaultConfig: Config = {
|
|
67
|
+
enabled: true,
|
|
68
|
+
autoAction: 'optOut', // if falsy, the extension will wait for an explicit user signal before opting in/out
|
|
69
|
+
disabledCmps: [],
|
|
70
|
+
enablePrehide: true,
|
|
71
|
+
enableCosmeticRules: true,
|
|
72
|
+
detectRetries: 20,
|
|
73
|
+
isMainWorld: false,
|
|
74
|
+
prehideTimeout: 2000,
|
|
75
|
+
logs: {
|
|
76
|
+
lifecycle: false,
|
|
77
|
+
rulesteps: false,
|
|
78
|
+
evals: false,
|
|
79
|
+
errors: true,
|
|
80
|
+
messages: false,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
const updatedConfig: Config = structuredClone(defaultConfig);
|
|
84
|
+
// filter out any unknown entries
|
|
85
|
+
for (const key of Object.keys(defaultConfig)) {
|
|
86
|
+
if (typeof providedConfig[key] !== 'undefined') {
|
|
87
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
88
|
+
// @ts-expect-error - TS doesn't know that we've checked for undefined
|
|
89
|
+
updatedConfig[key] = providedConfig[key];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return updatedConfig;
|
|
93
|
+
}
|