@duckduckgo/autoconsent 16.63.0 → 17.1.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 +98 -0
- package/dist/addon-firefox/background.bundle.js +1 -0
- package/dist/addon-firefox/compact-rules.json +1 -1
- package/dist/addon-firefox/content.bundle.js +177 -38
- package/dist/addon-firefox/manifest.json +1 -1
- package/dist/addon-firefox/popup.bundle.js +1 -0
- package/dist/addon-firefox/rule-index.json +1 -1
- package/dist/addon-firefox/rules.json +1 -1
- package/dist/addon-mv3/background.bundle.js +1 -0
- package/dist/addon-mv3/compact-rules.json +1 -1
- package/dist/addon-mv3/content.bundle.js +177 -38
- package/dist/addon-mv3/manifest.json +1 -1
- package/dist/addon-mv3/popup.bundle.js +1 -0
- package/dist/addon-mv3/rule-index.json +1 -1
- package/dist/addon-mv3/rules.json +1 -1
- package/dist/autoconsent.cjs.js +177 -38
- package/dist/autoconsent.esm.js +177 -38
- package/dist/autoconsent.playwright.js +180 -38
- package/dist/autoconsent.standalone.js +178 -39
- package/dist/heuristics.cjs.js +15 -3
- package/dist/heuristics.mjs +15 -3
- package/dist/types/heuristics.d.ts +12 -3
- package/dist/types/types.d.ts +12 -1
- package/lib/cmps/base.ts +5 -1
- package/lib/heuristic-patterns.ts +18 -2
- package/lib/heuristics.ts +240 -45
- package/lib/types.ts +16 -1
- package/lib/utils.ts +1 -0
- package/package.json +1 -1
- package/readme.md +1 -1
- package/rules/autoconsent/1pmobile.json +44 -0
- package/rules/autoconsent/amasty-gdpr-hyva.json +131 -0
- package/rules/autoconsent/amazonforum.json +86 -0
- package/rules/autoconsent/asrock.json +44 -0
- package/rules/autoconsent/babylist.json +23 -0
- package/rules/autoconsent/babylonbee.json +36 -0
- package/rules/autoconsent/bathnes-cookie-banner.json +31 -0
- package/rules/autoconsent/chairish.json +17 -0
- package/rules/autoconsent/chemicalbook.json +13 -0
- package/rules/autoconsent/chimnie-co-uk.json +21 -0
- package/rules/autoconsent/complianz-optin.json +5 -3
- package/rules/autoconsent/consentmo.json +5 -2
- package/rules/autoconsent/cookie-pref-form.json +48 -0
- package/rules/autoconsent/cookie-script.json +34 -15
- package/rules/autoconsent/endeavorhealth.json +31 -0
- package/rules/autoconsent/ensighten.json +3 -3
- package/rules/autoconsent/epaas.json +18 -1
- package/rules/autoconsent/fepblue-org.json +13 -0
- package/rules/autoconsent/fnsp-cookieconsent.json +30 -0
- package/rules/autoconsent/fod-fujitv.json +13 -0
- package/rules/autoconsent/fpl-com.json +34 -0
- package/rules/autoconsent/fuqer.json +11 -0
- package/rules/autoconsent/greatschools.json +13 -0
- package/rules/autoconsent/hamradio-com.json +13 -0
- package/rules/autoconsent/hkdir.json +33 -0
- package/rules/autoconsent/hubspot.json +7 -1
- package/rules/autoconsent/ibkr.json +16 -0
- package/rules/autoconsent/jetpens.json +44 -0
- package/rules/autoconsent/jokul-cookie-consent.json +28 -0
- package/rules/autoconsent/kanopy.json +88 -0
- package/rules/autoconsent/lego.json +27 -5
- package/rules/autoconsent/mainlinemenswear.json +39 -0
- package/rules/autoconsent/mars-cookies-prompt.json +20 -0
- package/rules/autoconsent/money-co-uk.json +21 -0
- package/rules/autoconsent/myjobquote.json +19 -0
- package/rules/autoconsent/niedziela-pl.json +35 -0
- package/rules/autoconsent/nikkeibp.json +10 -0
- package/rules/autoconsent/northwell-edu.json +29 -0
- package/rules/autoconsent/nts-org-uk.json +20 -0
- package/rules/autoconsent/nyu-langone.json +30 -0
- package/rules/autoconsent/ofsted-reports-cosmetic.json +13 -0
- package/rules/autoconsent/onecom-cookie-banner.json +25 -0
- package/rules/autoconsent/optum-legal-banner.json +10 -0
- package/rules/autoconsent/optum.json +35 -0
- package/rules/autoconsent/oslosportslager.no.json +34 -0
- package/rules/autoconsent/planned-parenthood.json +17 -0
- package/rules/autoconsent/plex.tv.json +52 -0
- package/rules/autoconsent/pornhub-compact-cookie-banner.json +1 -1
- package/rules/autoconsent/pornhub.json +1 -1
- package/rules/autoconsent/radio-canada.json +28 -0
- package/rules/autoconsent/regjeringen.no.json +13 -0
- package/rules/autoconsent/reviewsolicitors.json +41 -0
- package/rules/autoconsent/ring.json +1 -1
- package/rules/autoconsent/roll20.json +50 -0
- package/rules/autoconsent/sankei-digital.json +37 -0
- package/rules/autoconsent/sirdata.json +8 -3
- package/rules/autoconsent/site-notifications-cookie-usage.json +25 -0
- package/rules/autoconsent/sparebank1-optin.json +10 -0
- package/rules/autoconsent/texas-gov.json +22 -0
- package/rules/autoconsent/tiresize.com.json +17 -0
- package/rules/autoconsent/tri-table.json +6 -2
- package/rules/autoconsent/trillian.json +10 -0
- package/rules/autoconsent/tyreseeker.json +44 -0
- package/rules/autoconsent/uhcprovider.json +13 -0
- package/rules/autoconsent/united-online.json +66 -0
- package/rules/autoconsent/unitedutilities.json +52 -0
- package/rules/autoconsent/uttings.co.uk.json +22 -0
- package/rules/autoconsent/vinnova-se.json +50 -0
- package/rules/autoconsent/vortex-consent.json +33 -0
- package/rules/autoconsent/watchfinder.json +16 -0
- package/rules/compact-rules.json +1 -1
- package/rules/rule-index.json +1 -1
- package/rules/rules.json +1 -1
- package/tests/1pmobile.spec.ts +5 -0
- package/tests/amasty-gdpr-hyva.spec.ts +17 -0
- package/tests/amazonforum.spec.ts +9 -0
- package/tests/asrock.spec.ts +3 -0
- package/tests/babylist.spec.ts +3 -0
- package/tests/babylonbee.spec.ts +6 -0
- package/tests/bathnes-cookie-banner.spec.ts +3 -0
- package/tests/chairish.spec.ts +3 -0
- package/tests/chemicalbook.spec.ts +5 -0
- package/tests/chimnie-co-uk.spec.ts +3 -0
- package/tests/complianz-optin.spec.ts +2 -0
- package/tests/consentmo.spec.ts +2 -0
- package/tests/cookie-pref-form.spec.ts +8 -0
- package/tests/cookie-script.spec.ts +5 -0
- package/tests/endeavorhealth.spec.ts +3 -0
- package/tests/ensighten.spec.ts +7 -1
- package/tests/epaas.spec.ts +3 -0
- package/tests/fepblue-org.spec.ts +3 -0
- package/tests/fnsp-cookieconsent.spec.ts +9 -0
- package/tests/fod-fujitv.spec.ts +3 -0
- package/tests/fpl-com.spec.ts +3 -0
- package/tests/fuqer.spec.ts +3 -0
- package/tests/greatschools.spec.ts +3 -0
- package/tests/hamradio-com.spec.ts +3 -0
- package/tests/hkdir.spec.ts +10 -0
- package/tests/hubspot.spec.ts +6 -1
- package/tests/ibkr.spec.ts +18 -0
- package/tests/jetpens.spec.ts +5 -0
- package/tests/jokul-cookie-consent.spec.ts +3 -0
- package/tests/kanopy.spec.ts +5 -0
- package/tests/mainlinemenswear.spec.ts +3 -0
- package/tests/mars-cookies-prompt.spec.ts +3 -0
- package/tests/money-co-uk.spec.ts +3 -0
- package/tests/myjobquote.spec.ts +5 -0
- package/tests/niedziela-pl.spec.ts +3 -0
- package/tests/nikkeibp.spec.ts +8 -0
- package/tests/northwell-edu.spec.ts +3 -0
- package/tests/nts-org-uk.spec.ts +3 -0
- package/tests/nyu-langone.spec.ts +3 -0
- package/tests/ofsted-reports-cosmetic.spec.ts +3 -0
- package/tests/onecom-cookie-banner.spec.ts +5 -0
- package/tests/optum-legal-banner.spec.ts +9 -0
- package/tests/optum.spec.ts +6 -0
- package/tests/oslosportslager.no.spec.ts +3 -0
- package/tests/planned-parenthood.spec.ts +3 -0
- package/tests/plex.tv.spec.ts +5 -0
- package/tests/pornhub.spec.ts +1 -1
- package/tests/radio-canada.spec.ts +15 -0
- package/tests/regjeringen.no.spec.ts +3 -0
- package/tests/reviewsolicitors.spec.ts +3 -0
- package/tests/roll20.spec.ts +3 -0
- package/tests/sankei-digital.spec.ts +9 -0
- package/tests/sirdata.spec.ts +13 -1
- package/tests/site-notifications-cookie-usage.spec.ts +3 -0
- package/tests/sparebank1-optin.spec.ts +8 -0
- package/tests/texas-gov.spec.ts +3 -0
- package/tests/tiresize.com.spec.ts +3 -0
- package/tests/tri-table.spec.ts +1 -1
- package/tests/trillian.spec.ts +15 -0
- package/tests/tyreseeker.spec.ts +3 -0
- package/tests/uhcprovider.spec.ts +3 -0
- package/tests/united-online.spec.ts +4 -0
- package/tests/unitedutilities.spec.ts +11 -0
- package/tests/uttings.co.uk.spec.ts +3 -0
- package/tests/vinnova-se.spec.ts +6 -0
- package/tests/vortex-consent.spec.ts +18 -0
- package/tests/watchfinder.spec.ts +3 -0
- package/tests-wtr/heuristics/get-actionable-popups.ts +41 -7
- package/tests-wtr/heuristics/get-potential-popups.test.ts +235 -4
- package/tests-wtr/heuristics/heuristic-cmp.ts +16 -1
- package/tests-wtr/heuristics/heuristics-utils.test.ts +34 -4
- package/rules/generated/auto_CA_plex.tv_ee4.json +0 -40
- package/rules/generated/auto_CA_support.plex.tv_gn3.json +0 -40
- package/rules/generated/auto_US_interactivebrokers.com_0.json +0 -28
- package/tests/generated/auto_CA_plex.tv_ee4.spec.ts +0 -2
- package/tests/generated/auto_CA_support.plex.tv_gn3.spec.ts +0 -6
- package/tests/generated/auto_US_interactivebrokers.com_0.spec.ts +0 -3
package/dist/heuristics.cjs.js
CHANGED
|
@@ -46,8 +46,8 @@ var DETECT_PATTERNS = [
|
|
|
46
46
|
// "only necessary" is probably too broad
|
|
47
47
|
/(?:by continuing.{0,100}cookie)|(?:cookie.{0,100}by continuing)/gi,
|
|
48
48
|
/(?:by continuing.{0,100}privacy)|(?:privacy.{0,100}by continuing)/gi,
|
|
49
|
-
//
|
|
50
|
-
/we (?:use|serve)(?: \w+)
|
|
49
|
+
// up to two qualifiers are common: "optional", "essential", "analytics", "google analytics", "third party", ...
|
|
50
|
+
/we (?:use|serve)(?: \w+){0,2} cookies/gi,
|
|
51
51
|
/we are using cookies/gi,
|
|
52
52
|
/use of cookies/gi,
|
|
53
53
|
/website uses cookies to enhance your browsing experience/gi,
|
|
@@ -204,7 +204,10 @@ var DETECT_PATTERNS = [
|
|
|
204
204
|
/(?:cookie|クッキー).{0,10}を(?:使用|利用)/gi,
|
|
205
205
|
// Danish (DA)
|
|
206
206
|
// e.g. "vi bruger cookies", "hjemmesiden bruger cookies til at ..."
|
|
207
|
-
/bruger cookies/gi
|
|
207
|
+
/bruger cookies/gi,
|
|
208
|
+
// Norwegian (NO)
|
|
209
|
+
// e.g. "vi bruker informasjonskapsler", "kan vi bruke valgfrie informasjonskapsler", "nettstedet bruker cookies"
|
|
210
|
+
/\b(?:vi|nettstedet|nettsiden|siden)\s+bruker?\s.{0,60}?(?:informasjonskapsler|cookies)/gi
|
|
208
211
|
];
|
|
209
212
|
var REJECT_PATTERNS_ENGLISH = [
|
|
210
213
|
// e.g. "reject", "reject all", "reject all cookies", "deny all", "refuse cookies", "decline",
|
|
@@ -346,6 +349,14 @@ var REJECT_PATTERNS_DANISH = [
|
|
|
346
349
|
// "nej tak" (no thanks)
|
|
347
350
|
/^nej(,?\s+tak)?$/is
|
|
348
351
|
];
|
|
352
|
+
var REJECT_PATTERNS_NORWEGIAN = [
|
|
353
|
+
// avvis / avslå (reject verbs)
|
|
354
|
+
/(^|\s)(avvis|avslå)/is,
|
|
355
|
+
// (accept/allow/use) only necessary cookies
|
|
356
|
+
/^(godta\s+|tillat\s+|aksepter\s+|bruk\s+)?(kun|bare)\s+(strengt\s+)?nødvendige( (informasjonskapsler|cookies))?$/is,
|
|
357
|
+
// "nei" / "nei takk" (no thanks)
|
|
358
|
+
/^nei(,?\s+takk)?$/is
|
|
359
|
+
];
|
|
349
360
|
var REJECT_PATTERNS_CATALAN = [
|
|
350
361
|
/(^|\s)rebutj/is,
|
|
351
362
|
"no accepto",
|
|
@@ -401,6 +412,7 @@ var REJECT_PATTERNS = [
|
|
|
401
412
|
...REJECT_PATTERNS_SPANISH,
|
|
402
413
|
...REJECT_PATTERNS_SWEDISH,
|
|
403
414
|
...REJECT_PATTERNS_DANISH,
|
|
415
|
+
...REJECT_PATTERNS_NORWEGIAN,
|
|
404
416
|
...REJECT_PATTERNS_CATALAN,
|
|
405
417
|
...REJECT_PATTERNS_GALICIAN,
|
|
406
418
|
...REJECT_PATTERNS_BASQUE,
|
package/dist/heuristics.mjs
CHANGED
|
@@ -7,8 +7,8 @@ var DETECT_PATTERNS = [
|
|
|
7
7
|
// "only necessary" is probably too broad
|
|
8
8
|
/(?:by continuing.{0,100}cookie)|(?:cookie.{0,100}by continuing)/gi,
|
|
9
9
|
/(?:by continuing.{0,100}privacy)|(?:privacy.{0,100}by continuing)/gi,
|
|
10
|
-
//
|
|
11
|
-
/we (?:use|serve)(?: \w+)
|
|
10
|
+
// up to two qualifiers are common: "optional", "essential", "analytics", "google analytics", "third party", ...
|
|
11
|
+
/we (?:use|serve)(?: \w+){0,2} cookies/gi,
|
|
12
12
|
/we are using cookies/gi,
|
|
13
13
|
/use of cookies/gi,
|
|
14
14
|
/website uses cookies to enhance your browsing experience/gi,
|
|
@@ -165,7 +165,10 @@ var DETECT_PATTERNS = [
|
|
|
165
165
|
/(?:cookie|クッキー).{0,10}を(?:使用|利用)/gi,
|
|
166
166
|
// Danish (DA)
|
|
167
167
|
// e.g. "vi bruger cookies", "hjemmesiden bruger cookies til at ..."
|
|
168
|
-
/bruger cookies/gi
|
|
168
|
+
/bruger cookies/gi,
|
|
169
|
+
// Norwegian (NO)
|
|
170
|
+
// e.g. "vi bruker informasjonskapsler", "kan vi bruke valgfrie informasjonskapsler", "nettstedet bruker cookies"
|
|
171
|
+
/\b(?:vi|nettstedet|nettsiden|siden)\s+bruker?\s.{0,60}?(?:informasjonskapsler|cookies)/gi
|
|
169
172
|
];
|
|
170
173
|
var REJECT_PATTERNS_ENGLISH = [
|
|
171
174
|
// e.g. "reject", "reject all", "reject all cookies", "deny all", "refuse cookies", "decline",
|
|
@@ -307,6 +310,14 @@ var REJECT_PATTERNS_DANISH = [
|
|
|
307
310
|
// "nej tak" (no thanks)
|
|
308
311
|
/^nej(,?\s+tak)?$/is
|
|
309
312
|
];
|
|
313
|
+
var REJECT_PATTERNS_NORWEGIAN = [
|
|
314
|
+
// avvis / avslå (reject verbs)
|
|
315
|
+
/(^|\s)(avvis|avslå)/is,
|
|
316
|
+
// (accept/allow/use) only necessary cookies
|
|
317
|
+
/^(godta\s+|tillat\s+|aksepter\s+|bruk\s+)?(kun|bare)\s+(strengt\s+)?nødvendige( (informasjonskapsler|cookies))?$/is,
|
|
318
|
+
// "nei" / "nei takk" (no thanks)
|
|
319
|
+
/^nei(,?\s+takk)?$/is
|
|
320
|
+
];
|
|
310
321
|
var REJECT_PATTERNS_CATALAN = [
|
|
311
322
|
/(^|\s)rebutj/is,
|
|
312
323
|
"no accepto",
|
|
@@ -362,6 +373,7 @@ var REJECT_PATTERNS = [
|
|
|
362
373
|
...REJECT_PATTERNS_SPANISH,
|
|
363
374
|
...REJECT_PATTERNS_SWEDISH,
|
|
364
375
|
...REJECT_PATTERNS_DANISH,
|
|
376
|
+
...REJECT_PATTERNS_NORWEGIAN,
|
|
365
377
|
...REJECT_PATTERNS_CATALAN,
|
|
366
378
|
...REJECT_PATTERNS_GALICIAN,
|
|
367
379
|
...REJECT_PATTERNS_BASQUE,
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
import { ButtonData, HeuristicLevel, PopupData } from './types';
|
|
2
|
-
export declare function getActionablePopups(mode?: HeuristicLevel, timeout?: number): PopupData[];
|
|
1
|
+
import { ButtonData, HeuristicLevel, HitTestGrid, PopupData } from './types';
|
|
3
2
|
/**
|
|
3
|
+
* Popups the heuristic rule can act on in this mode, sorted so that a popup with reject comes first.
|
|
4
4
|
* @param timeout time budget for the whole search, in ms
|
|
5
|
+
* @param hitTest hit-test the viewport before the DOM walk, to find popups the walk misses; when this finds a consent
|
|
6
|
+
* popup with reject, the walk is skipped
|
|
5
7
|
*/
|
|
6
|
-
export declare function
|
|
8
|
+
export declare function getActionablePopups(mode?: HeuristicLevel, timeout?: number, hitTest?: boolean): PopupData[];
|
|
9
|
+
/**
|
|
10
|
+
* Popup candidates, each labelled with a regexClassification from its buttons.
|
|
11
|
+
* @param timeout time budget for the whole search, in ms
|
|
12
|
+
* @param hitTest hit-test the viewport before the DOM walk, to find popups the walk misses
|
|
13
|
+
* @param hitTestGrid points to hit-test, if not the default grid
|
|
14
|
+
*/
|
|
15
|
+
export declare function getPotentialPopups(timeout?: number, hitTest?: boolean, hitTestGrid?: HitTestGrid): PopupData[];
|
|
7
16
|
/**
|
|
8
17
|
* The whole document of a frame, if it is rendered and has text. The body can have zero height when all of its
|
|
9
18
|
* content is positioned, so only check visibility styles, not size.
|
package/dist/types/types.d.ts
CHANGED
|
@@ -69,6 +69,7 @@ export type Config = {
|
|
|
69
69
|
};
|
|
70
70
|
performanceLoggingEnabled: boolean;
|
|
71
71
|
heuristicPopupSearchTimeout: number;
|
|
72
|
+
enableHeuristicHitTest: boolean;
|
|
72
73
|
heuristicMode: HeuristicLevel;
|
|
73
74
|
};
|
|
74
75
|
export type LifecycleState = 'loading' | 'initialized' | 'waitingForInitResponse' | 'started' | 'nothingDetected' | 'cmpDetected' | 'openPopupDetected' | 'runningOptOut' | 'runningOptIn' | 'optOutSucceeded' | 'optOutFailed' | 'optInSucceeded' | 'optInFailed' | 'done';
|
|
@@ -94,7 +95,17 @@ export interface PopupData {
|
|
|
94
95
|
text: string;
|
|
95
96
|
element: HTMLElement;
|
|
96
97
|
buttons: ButtonData[];
|
|
97
|
-
regexClassification
|
|
98
|
+
regexClassification: PopupClassification;
|
|
99
|
+
source?: 'walk' | 'hitTest' | 'frame';
|
|
100
|
+
hitPoints?: [number, number][];
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Points to hit-test: columns are fractions of the viewport width, and rows are distances in px from both the top
|
|
104
|
+
* and the bottom edge. The middle row of the viewport is always tested.
|
|
105
|
+
*/
|
|
106
|
+
export interface HitTestGrid {
|
|
107
|
+
columns: number[];
|
|
108
|
+
edgeRows: number[];
|
|
98
109
|
}
|
|
99
110
|
/**
|
|
100
111
|
* The user setting for autoconsent and heuristic detection.
|
package/lib/cmps/base.ts
CHANGED
|
@@ -454,7 +454,11 @@ export class AutoConsentHeuristicCMP extends AutoConsentCMPBase {
|
|
|
454
454
|
// wait for one tick to deprioritize heavy DOM operations
|
|
455
455
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
456
456
|
this.autoconsent.config.performanceLoggingEnabled && performance.mark('heuristicDetectorStart');
|
|
457
|
-
this.popups = getActionablePopups(
|
|
457
|
+
this.popups = getActionablePopups(
|
|
458
|
+
this.mode,
|
|
459
|
+
this.autoconsent.config.heuristicPopupSearchTimeout,
|
|
460
|
+
this.autoconsent.config.enableHeuristicHitTest,
|
|
461
|
+
);
|
|
458
462
|
this.autoconsent.config.performanceLoggingEnabled && performance.mark('heuristicDetectorEnd');
|
|
459
463
|
this.autoconsent.config.performanceLoggingEnabled &&
|
|
460
464
|
performance.measure('heuristicDetector', 'heuristicDetectorStart', 'heuristicDetectorEnd');
|
|
@@ -7,8 +7,8 @@ export const DETECT_PATTERNS = [
|
|
|
7
7
|
/only necessary cookies/gi, // "only necessary" is probably too broad
|
|
8
8
|
/(?:by continuing.{0,100}cookie)|(?:cookie.{0,100}by continuing)/gi,
|
|
9
9
|
/(?:by continuing.{0,100}privacy)|(?:privacy.{0,100}by continuing)/gi,
|
|
10
|
-
//
|
|
11
|
-
/we (?:use|serve)(?: \w+)
|
|
10
|
+
// up to two qualifiers are common: "optional", "essential", "analytics", "google analytics", "third party", ...
|
|
11
|
+
/we (?:use|serve)(?: \w+){0,2} cookies/gi,
|
|
12
12
|
/we are using cookies/gi,
|
|
13
13
|
/use of cookies/gi,
|
|
14
14
|
/website uses cookies to enhance your browsing experience/gi,
|
|
@@ -182,6 +182,10 @@ export const DETECT_PATTERNS = [
|
|
|
182
182
|
// Danish (DA)
|
|
183
183
|
// e.g. "vi bruger cookies", "hjemmesiden bruger cookies til at ..."
|
|
184
184
|
/bruger cookies/gi,
|
|
185
|
+
|
|
186
|
+
// Norwegian (NO)
|
|
187
|
+
// e.g. "vi bruker informasjonskapsler", "kan vi bruke valgfrie informasjonskapsler", "nettstedet bruker cookies"
|
|
188
|
+
/\b(?:vi|nettstedet|nettsiden|siden)\s+bruker?\s.{0,60}?(?:informasjonskapsler|cookies)/gi,
|
|
185
189
|
];
|
|
186
190
|
|
|
187
191
|
const REJECT_PATTERNS_ENGLISH = [
|
|
@@ -373,6 +377,17 @@ const REJECT_PATTERNS_DANISH = [
|
|
|
373
377
|
/^nej(,?\s+tak)?$/is,
|
|
374
378
|
];
|
|
375
379
|
|
|
380
|
+
const REJECT_PATTERNS_NORWEGIAN = [
|
|
381
|
+
// avvis / avslå (reject verbs)
|
|
382
|
+
/(^|\s)(avvis|avslå)/is,
|
|
383
|
+
|
|
384
|
+
// (accept/allow/use) only necessary cookies
|
|
385
|
+
/^(godta\s+|tillat\s+|aksepter\s+|bruk\s+)?(kun|bare)\s+(strengt\s+)?nødvendige( (informasjonskapsler|cookies))?$/is,
|
|
386
|
+
|
|
387
|
+
// "nei" / "nei takk" (no thanks)
|
|
388
|
+
/^nei(,?\s+takk)?$/is,
|
|
389
|
+
];
|
|
390
|
+
|
|
376
391
|
const REJECT_PATTERNS_CATALAN = [
|
|
377
392
|
/(^|\s)rebutj/is,
|
|
378
393
|
'no accepto',
|
|
@@ -449,6 +464,7 @@ export const REJECT_PATTERNS = [
|
|
|
449
464
|
...REJECT_PATTERNS_SPANISH,
|
|
450
465
|
...REJECT_PATTERNS_SWEDISH,
|
|
451
466
|
...REJECT_PATTERNS_DANISH,
|
|
467
|
+
...REJECT_PATTERNS_NORWEGIAN,
|
|
452
468
|
...REJECT_PATTERNS_CATALAN,
|
|
453
469
|
...REJECT_PATTERNS_GALICIAN,
|
|
454
470
|
...REJECT_PATTERNS_BASQUE,
|
package/lib/heuristics.ts
CHANGED
|
@@ -1,41 +1,116 @@
|
|
|
1
1
|
import { classifyButtons, classifyPopup, hasHeuristicPatterns, isExcludedPopup } from './heuristic-classify';
|
|
2
|
-
import { ButtonData, HeuristicLevel, PopupClassification, PopupData } from './types';
|
|
2
|
+
import { ButtonData, HeuristicLevel, HitTestGrid, PopupClassification, PopupData } from './types';
|
|
3
3
|
import { isElementVisible, isTopFrame } from './utils';
|
|
4
4
|
|
|
5
5
|
const BUTTON_LIKE_ELEMENT_SELECTOR = 'button, input[type="button"], input[type="submit"], a, [role="button"], [class*="button"]';
|
|
6
6
|
const POPUP_SEARCH_MAX_TIME = 100;
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
8
|
+
// popup classifications the heuristic rule can act on, by heuristic mode
|
|
9
|
+
const ACCEPTED_LEVELS: Record<HeuristicLevel, PopupClassification[]> = {
|
|
10
|
+
off: [],
|
|
11
|
+
reject: ['reject'],
|
|
12
|
+
tier1: ['reject', 'tier1'],
|
|
13
|
+
tier2: ['reject', 'tier1', 'tier2'],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Popups the heuristic rule can act on in this mode, sorted so that a popup with reject comes first.
|
|
18
|
+
* @param timeout time budget for the whole search, in ms
|
|
19
|
+
* @param hitTest hit-test the viewport before the DOM walk, to find popups the walk misses; when this finds a consent
|
|
20
|
+
* popup with reject, the walk is skipped
|
|
21
|
+
*/
|
|
22
|
+
export function getActionablePopups(mode: HeuristicLevel = 'reject', timeout = POPUP_SEARCH_MAX_TIME, hitTest = false): PopupData[] {
|
|
23
|
+
const acceptedLevels = ACCEPTED_LEVELS[mode];
|
|
24
|
+
const isActionable = (popup: PopupData) => acceptedLevels.includes(popup.regexClassification) && isConsentPopup(popup);
|
|
25
|
+
const popups = searchPopups(timeout, hitTest, DEFAULT_HIT_TEST_GRID, true).filter(isActionable);
|
|
26
|
+
// stable, so popups with the same classification keep their search order
|
|
27
|
+
return popups.sort((a, b) => classificationRank(a) - classificationRank(b));
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
/**
|
|
31
|
+
* Popup candidates, each labelled with a regexClassification from its buttons.
|
|
24
32
|
* @param timeout time budget for the whole search, in ms
|
|
33
|
+
* @param hitTest hit-test the viewport before the DOM walk, to find popups the walk misses
|
|
34
|
+
* @param hitTestGrid points to hit-test, if not the default grid
|
|
35
|
+
*/
|
|
36
|
+
export function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME, hitTest = false, hitTestGrid = DEFAULT_HIT_TEST_GRID): PopupData[] {
|
|
37
|
+
return searchPopups(timeout, hitTest, hitTestGrid);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param stopOnReject skip the DOM walk when hit-testing finds a consent popup with reject
|
|
25
42
|
*/
|
|
26
|
-
|
|
43
|
+
function searchPopups(timeout: number, hitTest: boolean, hitTestGrid: HitTestGrid, stopOnReject = false): PopupData[] {
|
|
27
44
|
if (!isTopFrame()) {
|
|
28
|
-
|
|
29
|
-
if (window.parent && window.parent !== window.top) {
|
|
30
|
-
return [];
|
|
31
|
-
}
|
|
32
|
-
// for iframes, just take the whole document
|
|
33
|
-
const frameElement = getFramePopupElement(document);
|
|
34
|
-
const popup = frameElement && toPopupData(frameElement);
|
|
35
|
-
return popup ? [popup] : [];
|
|
45
|
+
return getFramePopups();
|
|
36
46
|
}
|
|
37
47
|
const deadline = performance.now() + timeout;
|
|
38
|
-
//
|
|
48
|
+
// hit-test first: it takes a few ms, while on large pages the walk can use the whole budget
|
|
49
|
+
const hitTestPopups = hitTest ? findPopupsByHitTest(deadline, hitTestGrid) : [];
|
|
50
|
+
// the walk cannot find a better popup than one with reject
|
|
51
|
+
if (stopOnReject && hitTestPopups.some((p) => p.regexClassification === 'reject' && isConsentPopup(p))) {
|
|
52
|
+
return hitTestPopups;
|
|
53
|
+
}
|
|
54
|
+
const walkPopups = collectPositionedPopups(deadline);
|
|
55
|
+
return [...walkPopups, ...mergeWalkOverlaps(hitTestPopups, walkPopups)];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// isConsentPopup scans the popup text with many patterns, and the search asks for the same popup more than once
|
|
59
|
+
const consentPopups = new WeakMap<PopupData, boolean>();
|
|
60
|
+
|
|
61
|
+
// reject, then tier1, then tier2; -1 for a popup no heuristic mode acts on
|
|
62
|
+
function classificationRank(popup: PopupData): number {
|
|
63
|
+
return ACCEPTED_LEVELS.tier2.indexOf(popup.regexClassification);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// a consent popup the heuristic rule could act on in some mode
|
|
67
|
+
function isConsentPopup(popup: PopupData): boolean {
|
|
68
|
+
if (!ACCEPTED_LEVELS.tier2.includes(popup.regexClassification)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
let result = consentPopups.get(popup);
|
|
72
|
+
if (result === undefined) {
|
|
73
|
+
const popupText = popup.text.trim();
|
|
74
|
+
result = !!popupText && !isExcludedPopup(popupText) && hasHeuristicPatterns(popupText);
|
|
75
|
+
consentPopups.set(popup, result);
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Where both searches found the same popup, keep the walk's candidate, with the hit points of the hit-test one.
|
|
82
|
+
* Keep both when the hit-test one is a better consent popup: the walk candidate is not one (such as a wrapper that also
|
|
83
|
+
* holds an age gate), or ranks lower (such as an accept-only fragment of a banner whose reject button the hit test found).
|
|
84
|
+
*/
|
|
85
|
+
function mergeWalkOverlaps(hitTestPopups: PopupData[], walkPopups: PopupData[]): PopupData[] {
|
|
86
|
+
return hitTestPopups.filter((h) => {
|
|
87
|
+
const hitIsConsent = isConsentPopup(h);
|
|
88
|
+
const absorbs = (p: PopupData) => !hitIsConsent || (isConsentPopup(p) && classificationRank(p) <= classificationRank(h));
|
|
89
|
+
const walkPopup = walkPopups.find((p) => composedOverlaps(p.element, h.element) && absorbs(p));
|
|
90
|
+
if (walkPopup) {
|
|
91
|
+
(walkPopup.hitPoints ??= []).push(...(h.hitPoints ?? []));
|
|
92
|
+
}
|
|
93
|
+
return !walkPopup;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function getFramePopups(): PopupData[] {
|
|
98
|
+
// do not inspect frames that are more than one level deep
|
|
99
|
+
if (window.parent && window.parent !== window.top) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
// for iframes, just take the whole document
|
|
103
|
+
const frameElement = getFramePopupElement(document);
|
|
104
|
+
const popup = frameElement && toPopupData(frameElement);
|
|
105
|
+
if (!popup) {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
popup.source = 'frame';
|
|
109
|
+
return [popup];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// fixed, sticky and dialog-like popups found by walking the DOM, with fragments expanded to their popup root
|
|
113
|
+
function collectPositionedPopups(deadline: number): PopupData[] {
|
|
39
114
|
const { elements, shadowHosts } = getPopupLikeElements(deadline);
|
|
40
115
|
const potentialPopups: PopupData[] = [];
|
|
41
116
|
const expanded: HTMLElement[] = [];
|
|
@@ -52,19 +127,107 @@ export function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME): PopupData[]
|
|
|
52
127
|
if (root !== popup) {
|
|
53
128
|
expanded.push(root.element);
|
|
54
129
|
}
|
|
130
|
+
root.source = 'walk';
|
|
55
131
|
potentialPopups.push(root);
|
|
56
132
|
}
|
|
57
133
|
// a fragment expanded to its popup root replaces any other candidates inside that root
|
|
58
134
|
return potentialPopups.filter((p) => !expanded.some((root) => root !== p.element && composedContains(root, p.element)));
|
|
59
135
|
}
|
|
60
136
|
|
|
137
|
+
const DEFAULT_HIT_TEST_GRID: HitTestGrid = {
|
|
138
|
+
columns: [0.1, 0.5, 0.9],
|
|
139
|
+
// banners sit along the top and bottom edges and modals in the middle; rows 150px in added almost nothing in crawls
|
|
140
|
+
edgeRows: [30, 90],
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Popups that are not fixed, sticky or dialog-like (for example banners in the normal page flow) are not found by
|
|
145
|
+
* the DOM walk. Hit-test points across the viewport and climb from the topmost element at each point instead.
|
|
146
|
+
* Prehide sets pointer-events: none, so a prehidden popup is only hit once prehide is lifted.
|
|
147
|
+
*/
|
|
148
|
+
function findPopupsByHitTest(deadline: number, grid: HitTestGrid): PopupData[] {
|
|
149
|
+
// the popup that each climbed element leads to, or null if its climb found none
|
|
150
|
+
const climbed = new Map<Element, PopupData | null>();
|
|
151
|
+
const found: PopupData[] = [];
|
|
152
|
+
// any page element can be hit, so leave out forms
|
|
153
|
+
const toPopup = (el: HTMLElement) => (el.querySelector(TEXT_ENTRY_SELECTOR) ? null : toPopupRoot(el));
|
|
154
|
+
for (const [x, y] of getHitTestPoints(grid)) {
|
|
155
|
+
if (performance.now() > deadline) {
|
|
156
|
+
return found;
|
|
157
|
+
}
|
|
158
|
+
const path: HTMLElement[] = [];
|
|
159
|
+
// the cast stops TypeScript narrowing to null, as only the shouldStop closure assigns it before use
|
|
160
|
+
let reached = null as PopupData | null;
|
|
161
|
+
// stop where an earlier climb already went, and at popups already found
|
|
162
|
+
const shouldStop = (el: HTMLElement) => {
|
|
163
|
+
if (climbed.has(el)) {
|
|
164
|
+
reached = climbed.get(el) ?? null;
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
// the climb went inside a popup already found, or reached one of its ancestors
|
|
168
|
+
const overlapped = found.find((p) => composedOverlaps(p.element, el));
|
|
169
|
+
if (overlapped) {
|
|
170
|
+
reached = composedContains(overlapped.element, el) ? overlapped : null;
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
path.push(el);
|
|
174
|
+
return false;
|
|
175
|
+
};
|
|
176
|
+
const hit = closestHTMLElement(deepElementFromPoint(x, y));
|
|
177
|
+
const popup = climbToPopup(hit, MAX_HIT_TEST_CLIMB_DEPTH, toPopup, shouldStop);
|
|
178
|
+
if (popup) {
|
|
179
|
+
popup.source = 'hitTest';
|
|
180
|
+
found.push(popup);
|
|
181
|
+
reached = popup;
|
|
182
|
+
}
|
|
183
|
+
for (const el of path) {
|
|
184
|
+
climbed.set(el, reached);
|
|
185
|
+
}
|
|
186
|
+
if (reached) {
|
|
187
|
+
(reached.hitPoints ??= []).push([x, y]);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return found;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// the grid as [x, y] viewport points in CSS px: the top edge rows, the middle, then the bottom edge rows, each column by column
|
|
194
|
+
function getHitTestPoints(grid: HitTestGrid): [number, number][] {
|
|
195
|
+
const { innerWidth: width, innerHeight: height } = window;
|
|
196
|
+
const rows = [...grid.edgeRows, height / 2, ...grid.edgeRows.map((y) => height - y)].map((y) => Math.round(y));
|
|
197
|
+
const ys = [...new Set(rows)].filter((y) => y >= 0 && y < height);
|
|
198
|
+
return ys.flatMap((y) => grid.columns.map((column): [number, number] => [Math.round(column * width), y]));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// precaution, not yet seen in crawls: login, search and sign-up forms can carry consent wording and a "Continue" button
|
|
202
|
+
const TEXT_ENTRY_SELECTOR =
|
|
203
|
+
'textarea, input:not([type]), input[type="text"], input[type="email"], input[type="password"], input[type="search"], input[type="tel"], input[type="number"], input[type="url"]';
|
|
204
|
+
|
|
205
|
+
function deepElementFromPoint(x: number, y: number): Element | null {
|
|
206
|
+
let el = document.elementFromPoint(x, y);
|
|
207
|
+
while (el?.shadowRoot) {
|
|
208
|
+
const inner = el.shadowRoot.elementFromPoint(x, y);
|
|
209
|
+
if (!inner || inner === el) {
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
el = inner;
|
|
213
|
+
}
|
|
214
|
+
return el;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function closestHTMLElement(el: Element | null): HTMLElement | null {
|
|
218
|
+
while (el && !(el instanceof HTMLElement)) {
|
|
219
|
+
el = el.parentElement;
|
|
220
|
+
}
|
|
221
|
+
return el;
|
|
222
|
+
}
|
|
223
|
+
|
|
61
224
|
function toPopupData(el: HTMLElement, shadowHosts?: HTMLElement[], text = getElementText(el)): PopupData | null {
|
|
62
225
|
if (!text) {
|
|
63
226
|
return null;
|
|
64
227
|
}
|
|
65
228
|
const buttons = getButtonData(el, shadowHosts);
|
|
66
229
|
classifyButtons(buttons);
|
|
67
|
-
return { text, element: el, buttons };
|
|
230
|
+
return { text, element: el, buttons, regexClassification: classifyPopup(buttons) };
|
|
68
231
|
}
|
|
69
232
|
|
|
70
233
|
// buttons beyond their labels, and at least one button the heuristics recognise (not just policy links)
|
|
@@ -81,35 +244,62 @@ function isOnlyButtonText(text: string, buttons: ButtonData[]): boolean {
|
|
|
81
244
|
return rest.replace(/\s+/g, '').length < 10;
|
|
82
245
|
}
|
|
83
246
|
|
|
84
|
-
|
|
85
|
-
|
|
247
|
+
/**
|
|
248
|
+
* The element as a popup, if it has consent text and popup buttons.
|
|
249
|
+
*/
|
|
250
|
+
function toPopupRoot(el: HTMLElement, shadowHosts?: HTMLElement[]): PopupData | null {
|
|
251
|
+
// check the text first: it is cheaper than collecting buttons
|
|
252
|
+
const text = getElementText(el);
|
|
253
|
+
if (!text || !hasHeuristicPatterns(text)) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
const popup = toPopupData(el, shadowHosts, text);
|
|
257
|
+
return popup && hasPopupButtons(text, popup.buttons) ? popup : null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Bound how far climbs reach. The page-level stop in climbToPopup is the main guard against false positives;
|
|
261
|
+
// these limits only keep the search cheap on deep trees.
|
|
86
262
|
const MAX_FRAGMENT_EXPANSION_DEPTH = 6;
|
|
263
|
+
const MAX_HIT_TEST_CLIMB_DEPTH = 8;
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Climb from el through up to maxDepth composed ancestors, and return the first popup that toPopup accepts.
|
|
267
|
+
*/
|
|
268
|
+
function climbToPopup(
|
|
269
|
+
el: HTMLElement | null,
|
|
270
|
+
maxDepth: number,
|
|
271
|
+
toPopup: (el: HTMLElement) => PopupData | null,
|
|
272
|
+
shouldStop?: (el: HTMLElement) => boolean,
|
|
273
|
+
): PopupData | null {
|
|
274
|
+
for (let depth = 0; el && depth < maxDepth; depth++, el = getComposedParent(el)) {
|
|
275
|
+
// stop before reaching page-level containers, whose text includes unrelated page copy
|
|
276
|
+
if (
|
|
277
|
+
shouldStop?.(el) ||
|
|
278
|
+
el === document.body ||
|
|
279
|
+
el === document.documentElement ||
|
|
280
|
+
el.getBoundingClientRect().height > window.innerHeight
|
|
281
|
+
) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
const popup = toPopup(el);
|
|
285
|
+
if (popup) {
|
|
286
|
+
return popup;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
87
291
|
|
|
88
292
|
/**
|
|
89
293
|
* A positioned candidate is often only part of a popup, such as a sticky button bar or heading.
|
|
90
294
|
* If it does not look like a consent popup on its own, use the nearest ancestor that does.
|
|
91
295
|
*/
|
|
92
296
|
function expandPopupFragment(popup: PopupData, shadowHosts?: HTMLElement[]): PopupData {
|
|
297
|
+
// the same acceptance check as toPopupRoot, on the buttons already collected
|
|
93
298
|
if (hasHeuristicPatterns(popup.text) && hasPopupButtons(popup.text, popup.buttons)) {
|
|
94
299
|
return popup;
|
|
95
300
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
// stop before reaching page-level containers, whose text includes unrelated page copy
|
|
99
|
-
if (el === document.body || el === document.documentElement || el.getBoundingClientRect().height > window.innerHeight) {
|
|
100
|
-
break;
|
|
101
|
-
}
|
|
102
|
-
// check the text first: it is cheaper than collecting buttons
|
|
103
|
-
const text = getElementText(el);
|
|
104
|
-
if (!text || !hasHeuristicPatterns(text)) {
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
const root = toPopupData(el, shadowHosts, text);
|
|
108
|
-
if (root && hasPopupButtons(text, root.buttons)) {
|
|
109
|
-
return root;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return popup;
|
|
301
|
+
const parent = getComposedParent(popup.element);
|
|
302
|
+
return climbToPopup(parent, MAX_FRAGMENT_EXPANSION_DEPTH, (el) => toPopupRoot(el, shadowHosts)) ?? popup;
|
|
113
303
|
}
|
|
114
304
|
|
|
115
305
|
function getComposedParent(el: HTMLElement): HTMLElement | null {
|
|
@@ -161,6 +351,11 @@ function isRenderedTextElement(el: Element): el is HTMLElement {
|
|
|
161
351
|
);
|
|
162
352
|
}
|
|
163
353
|
|
|
354
|
+
// either element contains the other, across shadow roots
|
|
355
|
+
function composedOverlaps(a: Node, b: Node): boolean {
|
|
356
|
+
return composedContains(a, b) || composedContains(b, a);
|
|
357
|
+
}
|
|
358
|
+
|
|
164
359
|
/**
|
|
165
360
|
* Like Node.contains, but also looks through shadow root boundaries.
|
|
166
361
|
*/
|
|
@@ -207,7 +402,7 @@ export function isDialogLikeElement(node: HTMLElement): boolean {
|
|
|
207
402
|
|
|
208
403
|
/**
|
|
209
404
|
* Heuristic to get all elements that look like "popups"
|
|
210
|
-
*
|
|
405
|
+
* Popups that are not positioned are found by hit-testing instead, when enabled (findPopupsByHitTest).
|
|
211
406
|
*/
|
|
212
407
|
function getPopupLikeElements(deadline: number): { elements: HTMLElement[]; shadowHosts: HTMLElement[] } {
|
|
213
408
|
const found: HTMLElement[] = [];
|
package/lib/types.ts
CHANGED
|
@@ -81,6 +81,7 @@ export type Config = {
|
|
|
81
81
|
};
|
|
82
82
|
performanceLoggingEnabled: boolean;
|
|
83
83
|
heuristicPopupSearchTimeout: number;
|
|
84
|
+
enableHeuristicHitTest: boolean; // hit-test the viewport before the DOM walk, to find popups the walk misses (e.g. not fixed, sticky or dialog-like)
|
|
84
85
|
heuristicMode: HeuristicLevel; // controls the behavior of the heuristic popup detection.
|
|
85
86
|
};
|
|
86
87
|
|
|
@@ -125,7 +126,21 @@ export interface PopupData {
|
|
|
125
126
|
text: string;
|
|
126
127
|
element: HTMLElement;
|
|
127
128
|
buttons: ButtonData[];
|
|
128
|
-
|
|
129
|
+
// from the buttons alone; whether the popup text is consent text is checked separately
|
|
130
|
+
regexClassification: PopupClassification;
|
|
131
|
+
// which search found the popup: the DOM walk, the viewport hit test, or the whole document of a frame
|
|
132
|
+
source?: 'walk' | 'hitTest' | 'frame';
|
|
133
|
+
// viewport points ([x, y] in CSS px) whose hit test reached this popup
|
|
134
|
+
hitPoints?: [number, number][];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Points to hit-test: columns are fractions of the viewport width, and rows are distances in px from both the top
|
|
139
|
+
* and the bottom edge. The middle row of the viewport is always tested.
|
|
140
|
+
*/
|
|
141
|
+
export interface HitTestGrid {
|
|
142
|
+
columns: number[];
|
|
143
|
+
edgeRows: number[];
|
|
129
144
|
}
|
|
130
145
|
|
|
131
146
|
/**
|
package/lib/utils.ts
CHANGED
|
@@ -109,6 +109,7 @@ export function normalizeConfig(providedConfig: any): Config {
|
|
|
109
109
|
},
|
|
110
110
|
performanceLoggingEnabled: false,
|
|
111
111
|
heuristicPopupSearchTimeout: 100,
|
|
112
|
+
enableHeuristicHitTest: false,
|
|
112
113
|
heuristicMode: 'off', // heuristic disabled by default
|
|
113
114
|
};
|
|
114
115
|
const updatedConfig: Config = copyObject(defaultConfig);
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -42,7 +42,7 @@ import { checkHeuristicPatterns, classifyButtonTextRegex, classifyButtons, class
|
|
|
42
42
|
classifyButtonTextRegex('Reject all'); // 'reject'
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
`dist/autoconsent.playwright.js` also exposes `window.autoconsentHeuristics.getPotentialPopups(timeout)` and `getButtonData(element)`, so other scripts injected into the same world can reuse autoconsent's popup and button discovery.
|
|
45
|
+
`dist/autoconsent.playwright.js` also exposes `window.autoconsentHeuristics.getPotentialPopups(timeout, hitTest)` and `getButtonData(element)`, so other scripts injected into the same world can reuse autoconsent's popup and button discovery. Each potential popup is labelled with the `regexClassification` of its buttons; `getActionablePopups` keeps the consent popups among them that the heuristic mode can act on.
|
|
46
46
|
|
|
47
47
|
## Browser extension
|
|
48
48
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "1pmobile.com",
|
|
3
|
+
"runContext": {
|
|
4
|
+
"urlPattern": "^https?://(\\w+\\.)?1pmobile\\.com/"
|
|
5
|
+
},
|
|
6
|
+
"prehideSelectors": [".cookie-disclaimer"],
|
|
7
|
+
"detectCmp": [
|
|
8
|
+
{
|
|
9
|
+
"exists": ".cookie-disclaimer a[href*='cprefs=change'], #myModal #cookieprefs"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"detectPopup": [
|
|
13
|
+
{
|
|
14
|
+
"visible": ".cookie-disclaimer, #myModal #cookieprefs"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"optIn": [
|
|
18
|
+
{
|
|
19
|
+
"waitForThenClick": ".cookie-disclaimer a[href*='cprefs=agree'], #myModal #cookieprefs a[href*='cprefs=agree']"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"optOut": [
|
|
23
|
+
{
|
|
24
|
+
"if": {
|
|
25
|
+
"exists": "#myModal #cookieprefs"
|
|
26
|
+
},
|
|
27
|
+
"then": [
|
|
28
|
+
{
|
|
29
|
+
"click": "#cookieprefs input[type=checkbox]:checked",
|
|
30
|
+
"all": true,
|
|
31
|
+
"optional": true
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"waitForThenClick": "#cookieprefs input[type=submit]"
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"else": [
|
|
38
|
+
{
|
|
39
|
+
"waitForThenClick": ".cookie-disclaimer a[href*='cprefs=change']"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|