@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
|
@@ -407,6 +407,14 @@
|
|
|
407
407
|
});
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
+
// lib/types.ts
|
|
411
|
+
var PopupHandlingModes = {
|
|
412
|
+
None: -1,
|
|
413
|
+
Reject: 0,
|
|
414
|
+
Tier1: 1,
|
|
415
|
+
Tier2: 2
|
|
416
|
+
};
|
|
417
|
+
|
|
410
418
|
// lib/random.ts
|
|
411
419
|
function getRandomID() {
|
|
412
420
|
if (crypto && typeof crypto.randomUUID !== "undefined") {
|
|
@@ -716,7 +724,8 @@
|
|
|
716
724
|
waits: false
|
|
717
725
|
},
|
|
718
726
|
performanceLoggingEnabled: false,
|
|
719
|
-
heuristicPopupSearchTimeout: 100
|
|
727
|
+
heuristicPopupSearchTimeout: 100,
|
|
728
|
+
heuristicMode: PopupHandlingModes.Reject
|
|
720
729
|
};
|
|
721
730
|
const updatedConfig = copyObject(defaultConfig);
|
|
722
731
|
for (const key of Object.keys(defaultConfig)) {
|
|
@@ -883,12 +892,17 @@
|
|
|
883
892
|
/^\s*(use|accept|allow|continue\s+with)?\s*only\s*(strictly)?\s*(necessary|essentials?|required)?\s*(cookies)?\s*$/is,
|
|
884
893
|
// e.g. "do not sell or share my personal information", "do not sell my personal information"
|
|
885
894
|
// often used in CCPA
|
|
886
|
-
/^\s*do\s+not\s+sell(\s+or\s+share)?\s*my\s*personal\s*
|
|
895
|
+
/^\s*do\s+not\s+sell(\s+or\s+share)?\s*my\s*personal\s*info(rmation)?\s*$/is,
|
|
887
896
|
"allow selection",
|
|
888
|
-
"disagree and close"
|
|
897
|
+
"disagree and close",
|
|
889
898
|
// These are impactful, but look error-prone
|
|
899
|
+
// // e.g. "disagree"
|
|
900
|
+
/^(i)?\s*disagree\s*(and\s+close)?$/i,
|
|
890
901
|
// // e.g. "i do not agree"
|
|
891
902
|
// /^\s*(i\s+)?do\s+not\s+agree\s*$/i,
|
|
903
|
+
"no",
|
|
904
|
+
/^no,? thanks$/is,
|
|
905
|
+
/^opt[ -]out$/is
|
|
892
906
|
];
|
|
893
907
|
var REJECT_PATTERNS_DUTCH = [
|
|
894
908
|
"weigeren",
|
|
@@ -1464,6 +1478,64 @@
|
|
|
1464
1478
|
/sostienici/is,
|
|
1465
1479
|
/suscribir/is
|
|
1466
1480
|
];
|
|
1481
|
+
var SETTINGS_PATTERNS = [
|
|
1482
|
+
"settings",
|
|
1483
|
+
"preferences",
|
|
1484
|
+
/customi(s|z)e/is,
|
|
1485
|
+
"show details",
|
|
1486
|
+
"more options",
|
|
1487
|
+
/(manage|configure) (my|your) (preferences|choices|cookies)/is,
|
|
1488
|
+
"manage choices",
|
|
1489
|
+
/(cookie )?preference center/is,
|
|
1490
|
+
"change settings",
|
|
1491
|
+
"configure",
|
|
1492
|
+
"change my preferences",
|
|
1493
|
+
"cookie manager",
|
|
1494
|
+
"cookie preference",
|
|
1495
|
+
"let me choose",
|
|
1496
|
+
"cookieconsent preferences",
|
|
1497
|
+
/privacy choices/is,
|
|
1498
|
+
/(privacy|cookie|custom) settings/is,
|
|
1499
|
+
/cookies? (settings|preferences|setting)/is,
|
|
1500
|
+
/(manage|customize|customise|opt-out|edit).*(cookies|preferences|settings|options)/is,
|
|
1501
|
+
"cookie consent options",
|
|
1502
|
+
"privacy controls",
|
|
1503
|
+
"show purposes",
|
|
1504
|
+
// German
|
|
1505
|
+
"einstellungen"
|
|
1506
|
+
];
|
|
1507
|
+
var ACCEPT_PATTERNS = [
|
|
1508
|
+
/^(accept|allow)( all)?( cookies)?$/is,
|
|
1509
|
+
/i (accept|allow)( all)?/is,
|
|
1510
|
+
"yes",
|
|
1511
|
+
/^(i )?agree$/is,
|
|
1512
|
+
"continue with all",
|
|
1513
|
+
"accept and continue",
|
|
1514
|
+
/accept all above/is,
|
|
1515
|
+
/^(accept|agree) and close/is,
|
|
1516
|
+
"accept continue",
|
|
1517
|
+
"agree proceed",
|
|
1518
|
+
"allow and continue",
|
|
1519
|
+
"close and accept",
|
|
1520
|
+
/accept all$/is,
|
|
1521
|
+
"im ok with that",
|
|
1522
|
+
"accept optional cookies",
|
|
1523
|
+
/^alle (cookies )?akzeptieren$/is
|
|
1524
|
+
];
|
|
1525
|
+
var ACKNOWLEDGE_PATTERNS = [
|
|
1526
|
+
"ok",
|
|
1527
|
+
"close",
|
|
1528
|
+
"continue",
|
|
1529
|
+
"x",
|
|
1530
|
+
/^got it!?$/,
|
|
1531
|
+
"i understand",
|
|
1532
|
+
"dismiss",
|
|
1533
|
+
"okay",
|
|
1534
|
+
"acknowledge",
|
|
1535
|
+
/^close (banner|cookie notification)$/is,
|
|
1536
|
+
/understood$/is,
|
|
1537
|
+
"confirm my choices"
|
|
1538
|
+
];
|
|
1467
1539
|
|
|
1468
1540
|
// lib/heuristics.ts
|
|
1469
1541
|
var BUTTON_LIKE_ELEMENT_SELECTOR = 'button, input[type="button"], input[type="submit"], a, [role="button"], [class*="button"]';
|
|
@@ -1482,48 +1554,61 @@
|
|
|
1482
1554
|
}
|
|
1483
1555
|
return { patterns, snippets: snippets2 };
|
|
1484
1556
|
}
|
|
1485
|
-
function getActionablePopups(timeout = POPUP_SEARCH_MAX_TIME) {
|
|
1557
|
+
function getActionablePopups(mode = PopupHandlingModes.Reject, timeout = POPUP_SEARCH_MAX_TIME) {
|
|
1486
1558
|
const popups = getPotentialPopups(timeout);
|
|
1487
1559
|
const result = popups.reduce((acc, popup) => {
|
|
1488
1560
|
const popupText = popup.text?.trim();
|
|
1489
1561
|
if (popupText) {
|
|
1490
1562
|
const { patterns } = checkHeuristicPatterns(popupText);
|
|
1491
1563
|
if (patterns.length > 0) {
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
otherButtons
|
|
1498
|
-
});
|
|
1499
|
-
}
|
|
1564
|
+
classifyButtons(popup.buttons);
|
|
1565
|
+
popup.regexClassification = classifyPopup(popup.buttons);
|
|
1566
|
+
acc.push({
|
|
1567
|
+
...popup
|
|
1568
|
+
});
|
|
1500
1569
|
}
|
|
1501
1570
|
}
|
|
1502
1571
|
return acc;
|
|
1503
1572
|
}, []);
|
|
1504
|
-
return result
|
|
1573
|
+
return result.filter(
|
|
1574
|
+
(popup) => popup.regexClassification !== void 0 && popup.regexClassification !== PopupHandlingModes.None && popup.regexClassification <= mode
|
|
1575
|
+
).sort((a, b) => (a.regexClassification ?? 0) - (b.regexClassification ?? 0));
|
|
1505
1576
|
}
|
|
1506
1577
|
function classifyButtons(buttons) {
|
|
1507
|
-
const rejectButtons = [];
|
|
1508
|
-
const otherButtons = [];
|
|
1509
1578
|
for (const button of buttons) {
|
|
1510
|
-
|
|
1511
|
-
rejectButtons.push(button);
|
|
1512
|
-
} else {
|
|
1513
|
-
otherButtons.push(button);
|
|
1514
|
-
}
|
|
1579
|
+
button.regexClassification = classifyButtonTextRegex(button.text);
|
|
1515
1580
|
}
|
|
1516
|
-
return {
|
|
1517
|
-
rejectButtons,
|
|
1518
|
-
otherButtons
|
|
1519
|
-
};
|
|
1520
1581
|
}
|
|
1521
|
-
function
|
|
1582
|
+
function classifyPopup(buttons) {
|
|
1583
|
+
const { reject, settings, accept, acknowledge } = buttons.reduce(
|
|
1584
|
+
(acc, button) => {
|
|
1585
|
+
if (button.regexClassification && button.regexClassification !== "other") {
|
|
1586
|
+
acc[button.regexClassification]++;
|
|
1587
|
+
}
|
|
1588
|
+
return acc;
|
|
1589
|
+
},
|
|
1590
|
+
{ reject: 0, settings: 0, accept: 0, acknowledge: 0 }
|
|
1591
|
+
);
|
|
1592
|
+
if (reject > 0) {
|
|
1593
|
+
return PopupHandlingModes.Reject;
|
|
1594
|
+
}
|
|
1595
|
+
if (settings > 0) {
|
|
1596
|
+
return PopupHandlingModes.None;
|
|
1597
|
+
}
|
|
1598
|
+
if (acknowledge > 0) {
|
|
1599
|
+
return PopupHandlingModes.Tier1;
|
|
1600
|
+
}
|
|
1601
|
+
if (accept > 0) {
|
|
1602
|
+
return accept === 1 ? PopupHandlingModes.Tier2 : PopupHandlingModes.None;
|
|
1603
|
+
}
|
|
1604
|
+
return PopupHandlingModes.None;
|
|
1605
|
+
}
|
|
1606
|
+
function testButtonMatches(buttonText, matchPatterns, neverMatchPatterns) {
|
|
1522
1607
|
if (!buttonText) {
|
|
1523
1608
|
return false;
|
|
1524
1609
|
}
|
|
1525
1610
|
const cleanedButtonText = cleanButtonText(buttonText);
|
|
1526
|
-
return !neverMatchPatterns.some((p) => p.test(cleanedButtonText)) &&
|
|
1611
|
+
return !neverMatchPatterns.some((p) => p instanceof RegExp && p.test(cleanedButtonText) || p === cleanedButtonText) && matchPatterns.some((p) => p instanceof RegExp && p.test(cleanedButtonText) || p === cleanedButtonText);
|
|
1527
1612
|
}
|
|
1528
1613
|
function cleanButtonText(buttonText) {
|
|
1529
1614
|
let result = buttonText.toLowerCase();
|
|
@@ -1537,6 +1622,21 @@
|
|
|
1537
1622
|
result = result.trim();
|
|
1538
1623
|
return result;
|
|
1539
1624
|
}
|
|
1625
|
+
function classifyButtonTextRegex(buttonText) {
|
|
1626
|
+
if (testButtonMatches(buttonText, REJECT_PATTERNS, NEVER_MATCH_PATTERNS)) {
|
|
1627
|
+
return "reject";
|
|
1628
|
+
}
|
|
1629
|
+
if (testButtonMatches(buttonText, SETTINGS_PATTERNS, NEVER_MATCH_PATTERNS)) {
|
|
1630
|
+
return "settings";
|
|
1631
|
+
}
|
|
1632
|
+
if (testButtonMatches(buttonText, ACCEPT_PATTERNS, NEVER_MATCH_PATTERNS)) {
|
|
1633
|
+
return "accept";
|
|
1634
|
+
}
|
|
1635
|
+
if (testButtonMatches(buttonText, ACKNOWLEDGE_PATTERNS, NEVER_MATCH_PATTERNS)) {
|
|
1636
|
+
return "acknowledge";
|
|
1637
|
+
}
|
|
1638
|
+
return "other";
|
|
1639
|
+
}
|
|
1540
1640
|
function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME) {
|
|
1541
1641
|
const isFramed = !isTopFrame();
|
|
1542
1642
|
if (isFramed && window.parent && window.parent !== window.top) {
|
|
@@ -1982,7 +2082,7 @@
|
|
|
1982
2082
|
}
|
|
1983
2083
|
};
|
|
1984
2084
|
var AutoConsentHeuristicCMP = class extends AutoConsentCMPBase {
|
|
1985
|
-
constructor(autoconsentInstance) {
|
|
2085
|
+
constructor(autoconsentInstance, mode = PopupHandlingModes.Reject) {
|
|
1986
2086
|
super(autoconsentInstance);
|
|
1987
2087
|
this.popups = [];
|
|
1988
2088
|
this.name = "HEURISTIC";
|
|
@@ -1991,6 +2091,7 @@
|
|
|
1991
2091
|
frame: false
|
|
1992
2092
|
// do not run in iframes for security reasons
|
|
1993
2093
|
};
|
|
2094
|
+
this.mode = mode;
|
|
1994
2095
|
}
|
|
1995
2096
|
get hasSelfTest() {
|
|
1996
2097
|
return true;
|
|
@@ -2004,25 +2105,33 @@
|
|
|
2004
2105
|
async detectCmp() {
|
|
2005
2106
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
2006
2107
|
this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorStart");
|
|
2007
|
-
this.popups = getActionablePopups(this.autoconsent.config.heuristicPopupSearchTimeout);
|
|
2108
|
+
this.popups = getActionablePopups(this.mode, this.autoconsent.config.heuristicPopupSearchTimeout);
|
|
2008
2109
|
this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorEnd");
|
|
2009
2110
|
this.autoconsent.config.performanceLoggingEnabled && performance.measure("heuristicDetector", "heuristicDetectorStart", "heuristicDetectorEnd");
|
|
2010
2111
|
if (this.popups.length > 0) {
|
|
2112
|
+
this.name = `HEURISTIC-TIER${this.popups[0].regexClassification}`;
|
|
2011
2113
|
return Promise.resolve(true);
|
|
2012
2114
|
}
|
|
2013
2115
|
return Promise.resolve(false);
|
|
2014
2116
|
}
|
|
2015
2117
|
async detectPopup() {
|
|
2016
2118
|
if (this.popups.length > 0) {
|
|
2017
|
-
if (this.popups.length > 1
|
|
2018
|
-
this.autoconsent.config.logs.errors && console.warn("Heuristic found multiple
|
|
2119
|
+
if (this.popups.length > 1) {
|
|
2120
|
+
this.autoconsent.config.logs.errors && console.warn("Heuristic found multiple popups");
|
|
2019
2121
|
}
|
|
2020
2122
|
return true;
|
|
2021
2123
|
}
|
|
2022
2124
|
return false;
|
|
2023
2125
|
}
|
|
2126
|
+
getTargetButton() {
|
|
2127
|
+
const popup = this.popups[0];
|
|
2128
|
+
const level = popup.regexClassification;
|
|
2129
|
+
const buttons = popup.buttons;
|
|
2130
|
+
const targetButtonType = level === PopupHandlingModes.Reject ? "reject" : level === PopupHandlingModes.Tier1 ? "acknowledge" : "accept";
|
|
2131
|
+
return buttons.find((button) => button.regexClassification === targetButtonType);
|
|
2132
|
+
}
|
|
2024
2133
|
optOut() {
|
|
2025
|
-
const button = this.
|
|
2134
|
+
const button = this.getTargetButton();
|
|
2026
2135
|
if (button) {
|
|
2027
2136
|
return this.clickElement(button.element);
|
|
2028
2137
|
}
|
|
@@ -2035,7 +2144,7 @@
|
|
|
2035
2144
|
throw new Error("Not Implemented");
|
|
2036
2145
|
}
|
|
2037
2146
|
async test() {
|
|
2038
|
-
const button = this.
|
|
2147
|
+
const button = this.getTargetButton();
|
|
2039
2148
|
if (button) {
|
|
2040
2149
|
await this.wait(500);
|
|
2041
2150
|
return !isElementVisible(button.element);
|
|
@@ -3505,7 +3614,7 @@
|
|
|
3505
3614
|
}
|
|
3506
3615
|
}
|
|
3507
3616
|
});
|
|
3508
|
-
const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this)] : [];
|
|
3617
|
+
const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this, this.config.heuristicMode)] : [];
|
|
3509
3618
|
const rulesPriorityStages = [
|
|
3510
3619
|
["site-specific", siteSpecificRules],
|
|
3511
3620
|
["generic", genericRules],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AutoCMP, DomActionsProvider, PopupData } from '../types';
|
|
1
|
+
import { AutoCMP, DomActionsProvider, PopupData, PopupHandlingMode } from '../types';
|
|
2
2
|
import { AutoConsentCMPRule, AutoConsentRuleStep, ElementSelector, HideMethod, RunContext, VisibilityCheck } from '../rules';
|
|
3
3
|
import AutoConsent from '../web';
|
|
4
4
|
import { snippets } from '../eval-snippets';
|
|
@@ -62,12 +62,14 @@ export declare class AutoConsentCMP extends AutoConsentCMPBase {
|
|
|
62
62
|
}
|
|
63
63
|
export declare class AutoConsentHeuristicCMP extends AutoConsentCMPBase {
|
|
64
64
|
popups: PopupData[];
|
|
65
|
-
|
|
65
|
+
mode: PopupHandlingMode;
|
|
66
|
+
constructor(autoconsentInstance: AutoConsent, mode?: PopupHandlingMode);
|
|
66
67
|
get hasSelfTest(): boolean;
|
|
67
68
|
get isIntermediate(): boolean;
|
|
68
69
|
get isCosmetic(): boolean;
|
|
69
70
|
detectCmp(): Promise<boolean>;
|
|
70
71
|
detectPopup(): Promise<boolean>;
|
|
72
|
+
getTargetButton(): import("../types").ButtonData | undefined;
|
|
71
73
|
optOut(): Promise<boolean>;
|
|
72
74
|
optIn(): Promise<boolean>;
|
|
73
75
|
openCmp(): Promise<boolean>;
|
|
@@ -4,3 +4,6 @@ export declare const DETECT_PATTERNS: RegExp[];
|
|
|
4
4
|
*/
|
|
5
5
|
export declare const REJECT_PATTERNS: (string | RegExp)[];
|
|
6
6
|
export declare const NEVER_MATCH_PATTERNS: RegExp[];
|
|
7
|
+
export declare const SETTINGS_PATTERNS: (string | RegExp)[];
|
|
8
|
+
export declare const ACCEPT_PATTERNS: (string | RegExp)[];
|
|
9
|
+
export declare const ACKNOWLEDGE_PATTERNS: (string | RegExp)[];
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
import { ButtonData, PopupData } from './types';
|
|
1
|
+
import { ButtonData, ButtonRegexClassification, PopupData, PopupHandlingMode } from './types';
|
|
2
2
|
export declare function checkHeuristicPatterns(allText: string, detectPatterns?: RegExp[]): {
|
|
3
3
|
patterns: string[];
|
|
4
4
|
snippets: string[];
|
|
5
5
|
};
|
|
6
|
-
export declare function getActionablePopups(timeout?: number): PopupData[];
|
|
7
|
-
export declare function classifyButtons(buttons: ButtonData[]):
|
|
8
|
-
rejectButtons: ButtonData[];
|
|
9
|
-
otherButtons: ButtonData[];
|
|
10
|
-
};
|
|
11
|
-
export declare function isRejectButton(buttonText: string, rejectPatterns?: (string | RegExp)[], neverMatchPatterns?: RegExp[]): boolean;
|
|
6
|
+
export declare function getActionablePopups(mode?: PopupHandlingMode, timeout?: number): PopupData[];
|
|
7
|
+
export declare function classifyButtons(buttons: ButtonData[]): void;
|
|
12
8
|
export declare function cleanButtonText(buttonText: string): string;
|
|
9
|
+
export declare function classifyButtonTextRegex(buttonText: string): ButtonRegexClassification;
|
|
13
10
|
export declare function isDialogLikeElement(node: HTMLElement): boolean;
|
|
14
11
|
/**
|
|
15
12
|
* Serialize all actionable buttons on the page
|
package/dist/types/types.d.ts
CHANGED
|
@@ -74,6 +74,7 @@ export type Config = {
|
|
|
74
74
|
};
|
|
75
75
|
performanceLoggingEnabled: boolean;
|
|
76
76
|
heuristicPopupSearchTimeout: number;
|
|
77
|
+
heuristicMode: PopupHandlingMode;
|
|
77
78
|
};
|
|
78
79
|
export type LifecycleState = 'loading' | 'initialized' | 'waitingForInitResponse' | 'started' | 'nothingDetected' | 'cosmeticFiltersDetected' | 'cmpDetected' | 'openPopupDetected' | 'runningOptOut' | 'runningOptIn' | 'optOutSucceeded' | 'optOutFailed' | 'optInSucceeded' | 'optInFailed' | 'done';
|
|
79
80
|
export type ConsentState = {
|
|
@@ -92,14 +93,29 @@ export type ConsentState = {
|
|
|
92
93
|
endTime: number;
|
|
93
94
|
performance?: Record<string, number[]>;
|
|
94
95
|
};
|
|
96
|
+
export type ButtonRegexClassification = 'reject' | 'settings' | 'accept' | 'acknowledge' | 'other';
|
|
95
97
|
export interface ButtonData {
|
|
96
98
|
text: string;
|
|
97
99
|
element: HTMLElement;
|
|
100
|
+
regexClassification?: ButtonRegexClassification;
|
|
98
101
|
}
|
|
99
102
|
export interface PopupData {
|
|
100
103
|
text: string;
|
|
101
104
|
element: HTMLElement;
|
|
102
105
|
buttons: ButtonData[];
|
|
103
|
-
|
|
104
|
-
otherButtons?: ButtonData[];
|
|
106
|
+
regexClassification?: PopupHandlingMode;
|
|
105
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Controls the behavior of the heuristic popup detection.
|
|
110
|
+
* - Reject: Will click the reject button on a popup if it exists.
|
|
111
|
+
* - Tier1: Will also click the acknowledge button on a popup if it exists, and no Reject button exists.
|
|
112
|
+
* - Tier2: Will also click the accept button on a popup if it exists, and no Reject or Acknowledge button exists.
|
|
113
|
+
* - None: Disabled
|
|
114
|
+
*/
|
|
115
|
+
export declare const PopupHandlingModes: {
|
|
116
|
+
readonly None: -1;
|
|
117
|
+
readonly Reject: 0;
|
|
118
|
+
readonly Tier1: 1;
|
|
119
|
+
readonly Tier2: 2;
|
|
120
|
+
};
|
|
121
|
+
export type PopupHandlingMode = (typeof PopupHandlingModes)[keyof typeof PopupHandlingModes];
|
package/lib/cmps/base.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AutoCMP, DomActionsProvider, PopupData } from '../types';
|
|
1
|
+
import { AutoCMP, DomActionsProvider, PopupData, PopupHandlingMode, PopupHandlingModes } from '../types';
|
|
2
2
|
import { AutoConsentCMPRule, AutoConsentRuleStep, ElementSelector, HideMethod, RunContext, VisibilityCheck } from '../rules';
|
|
3
3
|
import { requestEval } from '../eval-handler';
|
|
4
4
|
import AutoConsent from '../web';
|
|
@@ -419,14 +419,16 @@ export class AutoConsentCMP extends AutoConsentCMPBase {
|
|
|
419
419
|
|
|
420
420
|
export class AutoConsentHeuristicCMP extends AutoConsentCMPBase {
|
|
421
421
|
popups: PopupData[] = [];
|
|
422
|
+
mode: PopupHandlingMode;
|
|
422
423
|
|
|
423
|
-
constructor(autoconsentInstance: AutoConsent) {
|
|
424
|
+
constructor(autoconsentInstance: AutoConsent, mode: PopupHandlingMode = PopupHandlingModes.Reject) {
|
|
424
425
|
super(autoconsentInstance);
|
|
425
426
|
this.name = 'HEURISTIC';
|
|
426
427
|
this.runContext = {
|
|
427
428
|
main: true,
|
|
428
429
|
frame: false, // do not run in iframes for security reasons
|
|
429
430
|
} as RunContext;
|
|
431
|
+
this.mode = mode;
|
|
430
432
|
}
|
|
431
433
|
|
|
432
434
|
get hasSelfTest(): boolean {
|
|
@@ -445,11 +447,13 @@ export class AutoConsentHeuristicCMP extends AutoConsentCMPBase {
|
|
|
445
447
|
// wait for one tick to deprioritize heavy DOM operations
|
|
446
448
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
447
449
|
this.autoconsent.config.performanceLoggingEnabled && performance.mark('heuristicDetectorStart');
|
|
448
|
-
this.popups = getActionablePopups(this.autoconsent.config.heuristicPopupSearchTimeout);
|
|
450
|
+
this.popups = getActionablePopups(this.mode, this.autoconsent.config.heuristicPopupSearchTimeout);
|
|
449
451
|
this.autoconsent.config.performanceLoggingEnabled && performance.mark('heuristicDetectorEnd');
|
|
450
452
|
this.autoconsent.config.performanceLoggingEnabled &&
|
|
451
453
|
performance.measure('heuristicDetector', 'heuristicDetectorStart', 'heuristicDetectorEnd');
|
|
454
|
+
|
|
452
455
|
if (this.popups.length > 0) {
|
|
456
|
+
this.name = `HEURISTIC-TIER${this.popups[0].regexClassification}`;
|
|
453
457
|
return Promise.resolve(true);
|
|
454
458
|
}
|
|
455
459
|
return Promise.resolve(false);
|
|
@@ -457,17 +461,26 @@ export class AutoConsentHeuristicCMP extends AutoConsentCMPBase {
|
|
|
457
461
|
|
|
458
462
|
async detectPopup() {
|
|
459
463
|
if (this.popups.length > 0) {
|
|
460
|
-
if (this.popups.length > 1
|
|
461
|
-
this.autoconsent.config.logs.errors && console.warn('Heuristic found multiple
|
|
464
|
+
if (this.popups.length > 1) {
|
|
465
|
+
this.autoconsent.config.logs.errors && console.warn('Heuristic found multiple popups');
|
|
462
466
|
}
|
|
463
467
|
return true;
|
|
464
468
|
}
|
|
465
469
|
return false;
|
|
466
470
|
}
|
|
467
471
|
|
|
472
|
+
getTargetButton() {
|
|
473
|
+
const popup = this.popups[0];
|
|
474
|
+
const level = popup.regexClassification;
|
|
475
|
+
const buttons = popup.buttons;
|
|
476
|
+
const targetButtonType =
|
|
477
|
+
level === PopupHandlingModes.Reject ? 'reject' : level === PopupHandlingModes.Tier1 ? 'acknowledge' : 'accept';
|
|
478
|
+
return buttons.find((button) => button.regexClassification === targetButtonType);
|
|
479
|
+
}
|
|
480
|
+
|
|
468
481
|
optOut(): Promise<boolean> {
|
|
469
482
|
// use only the first found popup candidate
|
|
470
|
-
const button = this.
|
|
483
|
+
const button = this.getTargetButton();
|
|
471
484
|
if (button) {
|
|
472
485
|
return this.clickElement(button.element);
|
|
473
486
|
}
|
|
@@ -483,7 +496,7 @@ export class AutoConsentHeuristicCMP extends AutoConsentCMPBase {
|
|
|
483
496
|
}
|
|
484
497
|
|
|
485
498
|
async test(): Promise<boolean> {
|
|
486
|
-
const button = this.
|
|
499
|
+
const button = this.getTargetButton();
|
|
487
500
|
if (button) {
|
|
488
501
|
await this.wait(500);
|
|
489
502
|
return !isElementVisible(button.element);
|
|
@@ -115,14 +115,19 @@ const REJECT_PATTERNS_ENGLISH = [
|
|
|
115
115
|
|
|
116
116
|
// e.g. "do not sell or share my personal information", "do not sell my personal information"
|
|
117
117
|
// often used in CCPA
|
|
118
|
-
/^\s*do\s+not\s+sell(\s+or\s+share)?\s*my\s*personal\s*
|
|
118
|
+
/^\s*do\s+not\s+sell(\s+or\s+share)?\s*my\s*personal\s*info(rmation)?\s*$/is,
|
|
119
119
|
|
|
120
120
|
'allow selection',
|
|
121
121
|
'disagree and close',
|
|
122
122
|
|
|
123
123
|
// These are impactful, but look error-prone
|
|
124
|
+
// // e.g. "disagree"
|
|
125
|
+
/^(i)?\s*disagree\s*(and\s+close)?$/i,
|
|
124
126
|
// // e.g. "i do not agree"
|
|
125
127
|
// /^\s*(i\s+)?do\s+not\s+agree\s*$/i,
|
|
128
|
+
'no',
|
|
129
|
+
/^no,? thanks$/is,
|
|
130
|
+
/^opt[ -]out$/is,
|
|
126
131
|
];
|
|
127
132
|
|
|
128
133
|
const REJECT_PATTERNS_DUTCH = [
|
|
@@ -715,3 +720,65 @@ export const NEVER_MATCH_PATTERNS = [
|
|
|
715
720
|
/sostienici/is,
|
|
716
721
|
/suscribir/is,
|
|
717
722
|
];
|
|
723
|
+
|
|
724
|
+
export const SETTINGS_PATTERNS = [
|
|
725
|
+
'settings',
|
|
726
|
+
'preferences',
|
|
727
|
+
/customi(s|z)e/is,
|
|
728
|
+
'show details',
|
|
729
|
+
'more options',
|
|
730
|
+
/(manage|configure) (my|your) (preferences|choices|cookies)/is,
|
|
731
|
+
'manage choices',
|
|
732
|
+
/(cookie )?preference center/is,
|
|
733
|
+
'change settings',
|
|
734
|
+
'configure',
|
|
735
|
+
'change my preferences',
|
|
736
|
+
'cookie manager',
|
|
737
|
+
'cookie preference',
|
|
738
|
+
'let me choose',
|
|
739
|
+
'cookieconsent preferences',
|
|
740
|
+
/privacy choices/is,
|
|
741
|
+
/(privacy|cookie|custom) settings/is,
|
|
742
|
+
/cookies? (settings|preferences|setting)/is,
|
|
743
|
+
/(manage|customize|customise|opt-out|edit).*(cookies|preferences|settings|options)/is,
|
|
744
|
+
'cookie consent options',
|
|
745
|
+
'privacy controls',
|
|
746
|
+
'show purposes',
|
|
747
|
+
// German
|
|
748
|
+
'einstellungen',
|
|
749
|
+
];
|
|
750
|
+
|
|
751
|
+
export const ACCEPT_PATTERNS = [
|
|
752
|
+
/^(accept|allow)( all)?( cookies)?$/is,
|
|
753
|
+
/i (accept|allow)( all)?/is,
|
|
754
|
+
'yes',
|
|
755
|
+
/^(i )?agree$/is,
|
|
756
|
+
'continue with all',
|
|
757
|
+
'accept and continue',
|
|
758
|
+
/accept all above/is,
|
|
759
|
+
/^(accept|agree) and close/is,
|
|
760
|
+
'accept continue',
|
|
761
|
+
'agree proceed',
|
|
762
|
+
'allow and continue',
|
|
763
|
+
'close and accept',
|
|
764
|
+
/accept all$/is,
|
|
765
|
+
'im ok with that',
|
|
766
|
+
'accept optional cookies',
|
|
767
|
+
|
|
768
|
+
/^alle (cookies )?akzeptieren$/is,
|
|
769
|
+
];
|
|
770
|
+
|
|
771
|
+
export const ACKNOWLEDGE_PATTERNS = [
|
|
772
|
+
'ok',
|
|
773
|
+
'close',
|
|
774
|
+
'continue',
|
|
775
|
+
'x',
|
|
776
|
+
/^got it!?$/,
|
|
777
|
+
'i understand',
|
|
778
|
+
'dismiss',
|
|
779
|
+
'okay',
|
|
780
|
+
'acknowledge',
|
|
781
|
+
/^close (banner|cookie notification)$/is,
|
|
782
|
+
/understood$/is,
|
|
783
|
+
'confirm my choices',
|
|
784
|
+
];
|