@duckduckgo/autoconsent 14.68.0 → 14.69.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.
@@ -1575,6 +1575,15 @@ function collectPotentialPopups(isFramed) {
1575
1575
  }
1576
1576
  return potentialPopups;
1577
1577
  }
1578
+ function isDialogLikeElement(node) {
1579
+ if (node.tagName === "DIALOG" && node.hasAttribute("open")) {
1580
+ return true;
1581
+ }
1582
+ if (node.getAttribute("role") === "dialog" || node.getAttribute("aria-modal") === "true") {
1583
+ return true;
1584
+ }
1585
+ return false;
1586
+ }
1578
1587
  function getPopupLikeElements() {
1579
1588
  const walker = document.createTreeWalker(
1580
1589
  document.documentElement,
@@ -1585,9 +1594,14 @@ function getPopupLikeElements() {
1585
1594
  if (node.tagName === "BODY") {
1586
1595
  return NodeFilter.FILTER_SKIP;
1587
1596
  }
1588
- const cssPosition = window.getComputedStyle(node).position;
1589
- if ((cssPosition === "fixed" || cssPosition === "sticky") && isElementVisible(node)) {
1590
- return NodeFilter.FILTER_ACCEPT;
1597
+ if (isElementVisible(node)) {
1598
+ const cssPosition = window.getComputedStyle(node).position;
1599
+ if (cssPosition === "fixed" || cssPosition === "sticky") {
1600
+ return NodeFilter.FILTER_ACCEPT;
1601
+ }
1602
+ if (isDialogLikeElement(node)) {
1603
+ return NodeFilter.FILTER_ACCEPT;
1604
+ }
1591
1605
  }
1592
1606
  return NodeFilter.FILTER_SKIP;
1593
1607
  }
@@ -1545,6 +1545,15 @@ function collectPotentialPopups(isFramed) {
1545
1545
  }
1546
1546
  return potentialPopups;
1547
1547
  }
1548
+ function isDialogLikeElement(node) {
1549
+ if (node.tagName === "DIALOG" && node.hasAttribute("open")) {
1550
+ return true;
1551
+ }
1552
+ if (node.getAttribute("role") === "dialog" || node.getAttribute("aria-modal") === "true") {
1553
+ return true;
1554
+ }
1555
+ return false;
1556
+ }
1548
1557
  function getPopupLikeElements() {
1549
1558
  const walker = document.createTreeWalker(
1550
1559
  document.documentElement,
@@ -1555,9 +1564,14 @@ function getPopupLikeElements() {
1555
1564
  if (node.tagName === "BODY") {
1556
1565
  return NodeFilter.FILTER_SKIP;
1557
1566
  }
1558
- const cssPosition = window.getComputedStyle(node).position;
1559
- if ((cssPosition === "fixed" || cssPosition === "sticky") && isElementVisible(node)) {
1560
- return NodeFilter.FILTER_ACCEPT;
1567
+ if (isElementVisible(node)) {
1568
+ const cssPosition = window.getComputedStyle(node).position;
1569
+ if (cssPosition === "fixed" || cssPosition === "sticky") {
1570
+ return NodeFilter.FILTER_ACCEPT;
1571
+ }
1572
+ if (isDialogLikeElement(node)) {
1573
+ return NodeFilter.FILTER_ACCEPT;
1574
+ }
1561
1575
  }
1562
1576
  return NodeFilter.FILTER_SKIP;
1563
1577
  }
@@ -12397,6 +12397,15 @@ function collectPotentialPopups(isFramed) {
12397
12397
  }
12398
12398
  return potentialPopups;
12399
12399
  }
12400
+ function isDialogLikeElement(node) {
12401
+ if (node.tagName === "DIALOG" && node.hasAttribute("open")) {
12402
+ return true;
12403
+ }
12404
+ if (node.getAttribute("role") === "dialog" || node.getAttribute("aria-modal") === "true") {
12405
+ return true;
12406
+ }
12407
+ return false;
12408
+ }
12400
12409
  function getPopupLikeElements() {
12401
12410
  const walker = document.createTreeWalker(
12402
12411
  document.documentElement,
@@ -12407,9 +12416,14 @@ function getPopupLikeElements() {
12407
12416
  if (node.tagName === "BODY") {
12408
12417
  return NodeFilter.FILTER_SKIP;
12409
12418
  }
12410
- const cssPosition = window.getComputedStyle(node).position;
12411
- if ((cssPosition === "fixed" || cssPosition === "sticky") && isElementVisible(node)) {
12412
- return NodeFilter.FILTER_ACCEPT;
12419
+ if (isElementVisible(node)) {
12420
+ const cssPosition = window.getComputedStyle(node).position;
12421
+ if (cssPosition === "fixed" || cssPosition === "sticky") {
12422
+ return NodeFilter.FILTER_ACCEPT;
12423
+ }
12424
+ if (isDialogLikeElement(node)) {
12425
+ return NodeFilter.FILTER_ACCEPT;
12426
+ }
12413
12427
  }
12414
12428
  return NodeFilter.FILTER_SKIP;
12415
12429
  }
@@ -12331,6 +12331,15 @@ function collectPotentialPopups(isFramed) {
12331
12331
  }
12332
12332
  return potentialPopups;
12333
12333
  }
12334
+ function isDialogLikeElement(node) {
12335
+ if (node.tagName === "DIALOG" && node.hasAttribute("open")) {
12336
+ return true;
12337
+ }
12338
+ if (node.getAttribute("role") === "dialog" || node.getAttribute("aria-modal") === "true") {
12339
+ return true;
12340
+ }
12341
+ return false;
12342
+ }
12334
12343
  function getPopupLikeElements() {
12335
12344
  const walker = document.createTreeWalker(
12336
12345
  document.documentElement,
@@ -12341,9 +12350,14 @@ function getPopupLikeElements() {
12341
12350
  if (node.tagName === "BODY") {
12342
12351
  return NodeFilter.FILTER_SKIP;
12343
12352
  }
12344
- const cssPosition = window.getComputedStyle(node).position;
12345
- if ((cssPosition === "fixed" || cssPosition === "sticky") && isElementVisible(node)) {
12346
- return NodeFilter.FILTER_ACCEPT;
12353
+ if (isElementVisible(node)) {
12354
+ const cssPosition = window.getComputedStyle(node).position;
12355
+ if (cssPosition === "fixed" || cssPosition === "sticky") {
12356
+ return NodeFilter.FILTER_ACCEPT;
12357
+ }
12358
+ if (isDialogLikeElement(node)) {
12359
+ return NodeFilter.FILTER_ACCEPT;
12360
+ }
12347
12361
  }
12348
12362
  return NodeFilter.FILTER_SKIP;
12349
12363
  }
@@ -1547,6 +1547,15 @@
1547
1547
  }
1548
1548
  return potentialPopups;
1549
1549
  }
1550
+ function isDialogLikeElement(node) {
1551
+ if (node.tagName === "DIALOG" && node.hasAttribute("open")) {
1552
+ return true;
1553
+ }
1554
+ if (node.getAttribute("role") === "dialog" || node.getAttribute("aria-modal") === "true") {
1555
+ return true;
1556
+ }
1557
+ return false;
1558
+ }
1550
1559
  function getPopupLikeElements() {
1551
1560
  const walker = document.createTreeWalker(
1552
1561
  document.documentElement,
@@ -1557,9 +1566,14 @@
1557
1566
  if (node.tagName === "BODY") {
1558
1567
  return NodeFilter.FILTER_SKIP;
1559
1568
  }
1560
- const cssPosition = window.getComputedStyle(node).position;
1561
- if ((cssPosition === "fixed" || cssPosition === "sticky") && isElementVisible(node)) {
1562
- return NodeFilter.FILTER_ACCEPT;
1569
+ if (isElementVisible(node)) {
1570
+ const cssPosition = window.getComputedStyle(node).position;
1571
+ if (cssPosition === "fixed" || cssPosition === "sticky") {
1572
+ return NodeFilter.FILTER_ACCEPT;
1573
+ }
1574
+ if (isDialogLikeElement(node)) {
1575
+ return NodeFilter.FILTER_ACCEPT;
1576
+ }
1563
1577
  }
1564
1578
  return NodeFilter.FILTER_SKIP;
1565
1579
  }
@@ -10,6 +10,7 @@ export declare function classifyButtons(buttons: ButtonData[]): {
10
10
  };
11
11
  export declare function isRejectButton(buttonText: string, rejectPatterns?: (string | RegExp)[], neverMatchPatterns?: RegExp[]): boolean;
12
12
  export declare function cleanButtonText(buttonText: string): string;
13
+ export declare function isDialogLikeElement(node: HTMLElement): boolean;
13
14
  /**
14
15
  * Serialize all actionable buttons on the page
15
16
  */
@@ -22,9 +22,14 @@ Both JSON and class implementations have the following components:
22
22
  * `frame` - boolean, set to `true` if the rule should be executed in nested frames (default: `false`)
23
23
  * `urlPattern` - string, specifies a regular expression that should match the page URL (default: empty)
24
24
  * (optional) `test` - a list of actions to verify a successful opt-out. This is currently only used in Playwright tests.
25
+ * (optional) `minimumRuleStepVersion` - the minimum rule step version needed to execute this rule. Defaults to `1`. See [Rule Step Versioning](#rule-step-versioning).
25
26
 
26
27
 
27
- `detectCMP`, `detectPopup`, `optOut`, `optIn`, and `test` are defined as a set of checks or actions on the page. In the JSON syntax this is a list of `AutoConsentRuleStep` objects. For `detect` checks, we return true for the check if all steps return true. For opt in and out, we execute actions in order, exiting if one fails. The following checks/actions are supported:
28
+ `detectCMP`, `detectPopup`, `optOut`, `optIn`, and `test` are defined as a set of checks or actions on the page. In the JSON syntax this is a list of `AutoConsentRuleStep` objects. For `detect` checks, we return true for the check if all steps return true. For opt in and out, we execute actions in order, exiting if one fails.
29
+
30
+ **Important:** Do not use `wait` steps in `detectCmp` or `detectPopup` arrays. Detection must be fast and non-blocking -- the engine already retries detection automatically with its own timing. Adding `wait` steps to detection slows down detection of other rules.
31
+
32
+ The following checks/actions are supported:
28
33
 
29
34
  ## Element selectors
30
35
 
@@ -240,3 +245,29 @@ new AutoConsent({
240
245
  ## Context filters
241
246
 
242
247
  By default, rules will be executed in all top-level documents. Some rules are designed for specific contexts (e.g. only nested iframes, or only specific URLs). This can be configured in `runContext` field (see [runContext](#rule-syntax-reference) above).
248
+
249
+ ## Rule Step Versioning
250
+
251
+ New step types are occasionally added to the autoconsent engine (e.g. `removeClass`, `setStyle`, `addStyle` were added in version 2). Because rules can be shipped to clients independently of app releases, a rule that uses a newer step type could end up on a client that doesn't support it yet.
252
+
253
+ The `minimumRuleStepVersion` field solves this: clients compare the rule's declared version against their own supported version (`SUPPORTED_RULE_STEP_VERSION` in `lib/rules.ts`) and silently skip rules they cannot execute.
254
+
255
+ ### Version history
256
+
257
+ | Version | Step types added |
258
+ |---------|-----------------|
259
+ | 1 | All original step types (`exists`, `visible`, `waitFor`, `waitForVisible`, `click`, `waitForThenClick`, `wait`, `hide`, `if`/`then`/`else`, `any`, `eval`, `cookieContains`, `negated`) |
260
+ | 2 | `removeClass`, `setStyle`, `addStyle` |
261
+
262
+ ### When to set it
263
+
264
+ - If a rule only uses version-1 step types, omit the field (defaults to `1`).
265
+ - If a rule uses `removeClass`, `setStyle`, or `addStyle`, set `"minimumRuleStepVersion": 2`.
266
+ - When a future version introduces new step types, any rule using them must set `minimumRuleStepVersion` to the corresponding version number.
267
+
268
+ ### Adding new step types
269
+
270
+ When introducing a new step type:
271
+ 1. Bump `SUPPORTED_RULE_STEP_VERSION` in `lib/rules.ts` and add a comment describing what was added.
272
+ 2. Add the new step type definition to the `AutoConsentRuleStep` union in the same file.
273
+ 3. Any rule using the new step type must set `minimumRuleStepVersion` to the new version number.
package/lib/heuristics.ts CHANGED
@@ -126,6 +126,16 @@ function collectPotentialPopups(isFramed: boolean): PopupData[] {
126
126
  return potentialPopups;
127
127
  }
128
128
 
129
+ export function isDialogLikeElement(node: HTMLElement): boolean {
130
+ if (node.tagName === 'DIALOG' && node.hasAttribute('open')) {
131
+ return true;
132
+ }
133
+ if (node.getAttribute('role') === 'dialog' || node.getAttribute('aria-modal') === 'true') {
134
+ return true;
135
+ }
136
+ return false;
137
+ }
138
+
129
139
  /**
130
140
  * Heuristic to get all elements that look like "popups"
131
141
  * TODO: this heuristic is too strict, not all popups are actually sticky/fixed
@@ -139,9 +149,14 @@ function getPopupLikeElements(): HTMLElement[] {
139
149
  if (node.tagName === 'BODY') {
140
150
  return NodeFilter.FILTER_SKIP;
141
151
  }
142
- const cssPosition = window.getComputedStyle(node).position;
143
- if ((cssPosition === 'fixed' || cssPosition === 'sticky') && isElementVisible(node)) {
144
- return NodeFilter.FILTER_ACCEPT;
152
+ if (isElementVisible(node)) {
153
+ const cssPosition = window.getComputedStyle(node).position;
154
+ if (cssPosition === 'fixed' || cssPosition === 'sticky') {
155
+ return NodeFilter.FILTER_ACCEPT;
156
+ }
157
+ if (isDialogLikeElement(node)) {
158
+ return NodeFilter.FILTER_ACCEPT;
159
+ }
145
160
  }
146
161
  return NodeFilter.FILTER_SKIP;
147
162
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckduckgo/autoconsent",
3
- "version": "14.68.0",
3
+ "version": "14.69.0",
4
4
  "description": "",
5
5
  "types": "./dist/types/web.d.ts",
6
6
  "exports": {