Skip to content

Commit

Permalink
Single selector in hide rules (#334)
Browse files Browse the repository at this point in the history
* Improve Tealium rule

* Change compile target for the test bundle to support optional chaining in evals

* Add test cases for tealium

* Make the hide rule accept a single selector (#316)

* Make the `hide` rule accept a single selector instead of a list

* Change the Hide rule in json files

* Update element selector docs

* Add a note about hide rule only supporting CSS selectors
  • Loading branch information
muodov authored Jan 6, 2024
1 parent 3d0d21d commit 4d1d551
Show file tree
Hide file tree
Showing 67 changed files with 109 additions and 107 deletions.
2 changes: 1 addition & 1 deletion lib/cmps/evidon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default class Evidon extends AutoConsentCMPBase {
return true;
}

hideElements(getStyleElement(), ["#evidon-prefdiag-overlay", "#evidon-prefdiag-background"]);
hideElements(getStyleElement(), "#evidon-prefdiag-overlay,#evidon-prefdiag-background");
click("#_evidon-option-button");

await waitForElement("#evidon-prefdiag-overlay", 5000);
Expand Down
2 changes: 1 addition & 1 deletion lib/cmps/trustarc-top.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export default class TrustArcTop extends AutoConsentCMPBase {
// hide elements permanently, so user doesn't see the popup
hideElements(
getStyleElement(),
[".truste_popframe", ".truste_overlay", ".truste_box_overlay", bannerContainer],
`.truste_popframe, .truste_overlay, .truste_box_overlay, ${bannerContainer}`,
);
click(cookieSettingsButton);

Expand Down
8 changes: 4 additions & 4 deletions lib/rule-executors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,16 @@ export function wait(ms: number): Promise<true> {
});
}

export function hide(selectors: string[], method: HideMethod): boolean {
export function hide(selector: string, method: HideMethod): boolean {
// enableLogs && console.log("[hide]", ruleStep.hide, ruleStep.method);
const styleEl = getStyleElement();
return hideElements(styleEl, selectors, method);
return hideElements(styleEl, selector, method);
}

export function prehide(selectors: string[]): boolean {
export function prehide(selector: string): boolean {
const styleEl = getStyleElement('autoconsent-prehide');
enableLogs && console.log("[prehide]", styleEl, location.href);
return hideElements(styleEl, selectors, "opacity");
return hideElements(styleEl, selector, "opacity");
}

export function undoPrehide(): boolean {
Expand Down
7 changes: 1 addition & 6 deletions lib/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ export type AutoConsentRuleStep = { optional?: boolean } &
Partial<ClickRule> &
Partial<WaitForThenClickRule> &
Partial<WaitRule> &
Partial<UrlRule> &
Partial<HideRule> &
Partial<IfRule> &
Partial<AnyRule>
Expand Down Expand Up @@ -78,14 +77,10 @@ export type WaitRule = {
wait: number;
};

export type UrlRule = {
url: string;
};

export type HideMethod = 'display' | 'opacity';

export type HideRule = {
hide: string[];
hide: string;
method?: HideMethod;
};

Expand Down
8 changes: 3 additions & 5 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,15 @@ export function getStyleElement(styleOverrideElementId = "autoconsent-css-rules"
// hide elements with a CSS rule
export function hideElements(
styleEl: HTMLStyleElement,
selectors: string[],
selector: string,
method: HideMethod = 'display',
): boolean {
const hidingSnippet = method === "opacity" ? `opacity: 0` : `display: none`; // use display by default
const rule = `${selectors.join(
","
)} { ${hidingSnippet} !important; z-index: -1 !important; pointer-events: none !important; } `;
const rule = `${selector} { ${hidingSnippet} !important; z-index: -1 !important; pointer-events: none !important; } `;

if (styleEl instanceof HTMLStyleElement) {
styleEl.innerText += rule;
return selectors.length > 0;
return selector.length > 0;
}
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ export default class AutoConsent {
this.undoPrehide();
}
}, this.config.prehideTimeout || 2000);
return prehide(selectors);
return prehide(selectors.join(','));
}

undoPrehide(): boolean {
Expand Down
65 changes: 37 additions & 28 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ that are installed on multiple sites. Each CMP ruleset defines:

There are currently three ways of implementing a CMP:

1. As a [JSON ruleset](./rules/autoconsent/), intepreted by the `AutoConsent` class.
1. As a [JSON ruleset](./rules/autoconsent/), intepreted by the `AutoConsent` class.
1. As a class implementing the `AutoCMP` interface. This enables more complex logic than the linear AutoConsent
rulesets allow.
3. As a [Consent-O-Matic](https://github.com/cavi-au/Consent-O-Matic) rule. The `ConsentOMaticCMP` class implements
Expand Down Expand Up @@ -79,91 +79,100 @@ Both JSON and class implementations have the following components:

### Element selectors

Most rules use "selectors" to locate elements in a page. In most cases, a selector can be a string, or array of strings, which are used to locale elements as follows:
Many rules use `ElementSelector` to locate elements in a page. `ElementSelector` can be a string, or array of strings, which are used to locate elements as follows:
- By default, strings are treated as [CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/API/Document_object_model/Locating_DOM_elements_using_selectors) via the `querySelector` API. e.g. `#reject-cookies` to find an element whose `id` is 'reject-cookies'.
- Selectors prefixed with `xpath/` are [Xpath](https://developer.mozilla.org/en-US/docs/Web/XPath) selectors which can locate elements in the page via [`document.evaluate`](https://developer.mozilla.org/en-US/docs/Web/XPath/Introduction_to_using_XPath_in_JavaScript#document.evaluate). e.g. `xpath///*[@id="reject-cookies"]` can find an element whose `id` is 'reject-cookies'.
- If an array of selectors is given, the selectors are applied in array order, with the search scope constrained each time but the first match of the previous selector. e.g. `['#reject-cookies', 'button']` first looks for an element with `id` 'reject-cookies', then looks for a match for `button` _that is a decendant_ of that element.
- If an array of selectors is given, and a selector returns an element that has a `shadowRoot` attribute, the next selector will run within that element's shadow DOM.
- Strings prefixed with `xpath/` are [Xpath](https://developer.mozilla.org/en-US/docs/Web/XPath) selectors which can locate elements in the page via [`document.evaluate`](https://developer.mozilla.org/en-US/docs/Web/XPath/Introduction_to_using_XPath_in_JavaScript#document.evaluate). e.g. `xpath///*[@id="reject-cookies"]` can find an element whose `id` is 'reject-cookies'.
- If an array of strings is given, the selectors are applied in array order, with the search scope constrained each time but the first match of the previous selector. e.g. `['#reject-cookies', 'button']` first looks for an element with `id="reject-cookies"`, then looks for a match for `button` _that is a descendant_ of that element.
**If one of the selectors returns an element that has a `shadowRoot` property, the next selector will run within that element's shadow DOM.** This is the main difference from nested CSS selectors, which do not cross shadow DOM boundaries.

For example, consider the following DOM fragment:
```html
<open-shadow-root-element>
<button>X</button>
</open-shadow-root-element>
```

Then `['open-shadow-root-element', 'button']` will find the button, but a usual CSS selector `'open-shadow-root-element button'` will not.

### Element exists

```json
```javascript
{
"exists": "selector"
"exists": ElementSelector
}
```
Returns true if the given selector matches one or more elements.

### Element visible

```json
```javascript
{
"visible": "selector",
"visible": ElementSelector,
"check": "any" | "all" | "none"
}
```
Returns true if elements returned matched by "selector" are currently visible on the page. If `check` is `all`, every element must be visible. If `check` is `none`, no element should be visible. Visibility check is a CSS-based heuristic.
Returns true if elements matched by ElementSelector are currently visible on the page. If `check` is `all`, every element must be visible. If `check` is `none`, no element should be visible. Visibility check is a CSS-based heuristic.

### Wait for element

```json
```javascript
{
"waitFor": "selector",
"waitFor": ElementSelector,
"timeout": 1000
}
```
Waits until `selector` exists in the page. After `timeout` ms the step fails.

### Wait for visibility

```json
```javascript
{
"waitForVisible": "selector",
"waitForVisible": ElementSelector,
"timeout": 1000,
"check": "any" | "all" | "none"
}
```
Waits until element is visible in the page. After `timeout` ms the step fails.

### Click an element
```json
```javascript
{
"click": "selector",
"click": ElementSelector,
"all": true | false,
}
```
Click on an element returned by `selector`. If `all` is `true`, all matching elements are clicked. If `all` is `false`, only the first returned value is clicked.

### Wait for then click
```json
```javascript
{
"waitForThenClick": "selector",
"waitForThenClick": ElementSelector,
"timeout": 1000,
"all": true | false
}
```
Combines `waitFor` and `click`.

### Unconditional wait
```json
```javascript
{
"wait": 1000,
}
```
Wait for the specified number of milliseconds.

### Hide
```json
```javascript
{
"hide": ["selector", ...],
"hide": "CSS selector",
"method": "display" | "opacity"
}
```
Hide the elements matched by the selectors. `method` defines how elements are hidden: "display" sets `display: none`, "opacity" sets `opacity: 0`. Method is "display" by default.
Hide the elements matched by the selectors. `method` defines how elements are hidden: "display" sets `display: none`, "opacity" sets `opacity: 0`. Method is "display" by default. Note that only a single string CSS selector is supported here, not an array.

### Eval

```json
```javascript
{
"eval": "SNIPPET_ID"
}
Expand All @@ -173,9 +182,9 @@ Eval rules are not 100% reliable because they can be affected by the page script

### Conditionals

```json
```javascript
{
"if": { "exists": "selector" },
"if": { "exists": ElementSelector },
"then": [
{ "click": ".button1" },
{ "click": ".button3" }
Expand All @@ -191,7 +200,7 @@ The "if" rule is considered successful as long as all rules inside the chosen br

### Any

```json
```javascript
{
"any": [
{ "exists": ".button1" },
Expand All @@ -200,11 +209,11 @@ The "if" rule is considered successful as long as all rules inside the chosen br
}
```

Evaluates a list of steps in order. If any return true (success), then the step exists and returns true. If all steps return false, the `any` step returns false.
Evaluates a list of steps in order. If any return true (success), then the step returns true. If all steps return false, the `any` step returns false.

### Optional actions

Any rule can include the `"optional": true` to ignore failure.
All rules can include the `"optional": true` to ignore failure.

## API

Expand Down
2 changes: 1 addition & 1 deletion rules/autoconsent/1password-com.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
"detectPopup": [{ "visible": "footer #footer-root [aria-label=\"Cookie Consent\"]" }],
"optIn":
[{"click": "footer #footer-root [aria-label=\"Cookie Consent\"] button" }],
"optOut": [{ "hide": ["footer #footer-root [aria-label=\"Cookie Consent\"]"] }]
"optOut": [{ "hide": "footer #footer-root [aria-label=\"Cookie Consent\"]" }]
}

2 changes: 1 addition & 1 deletion rules/autoconsent/agolde-com.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
{ "click": "button[aria-label=\"Close modal\"]" }
],
"optOut": [
{ "hide": ["#modal-1 div[data-micromodal-close]"] }
{ "hide": "#modal-1 div[data-micromodal-close]" }
]
}
2 changes: 1 addition & 1 deletion rules/autoconsent/altium-com.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"detectCmp": [{ "exists": ".altium-privacy-bar" }],
"detectPopup": [{ "exists": ".altium-privacy-bar" }],
"optIn": [{ "click": "a.altium-privacy-bar__btn" }],
"optOut": [{ "hide": [".altium-privacy-bar"] }]
"optOut": [{ "hide": ".altium-privacy-bar" }]
}
2 changes: 1 addition & 1 deletion rules/autoconsent/aquasana-com.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"detectCmp": [{ "exists": "#consent-tracking" }],
"detectPopup": [{ "exists": "#consent-tracking" }],
"optIn": [{ "click": "#accept_consent" }],
"optOut": [{ "hide": ["#consent-tracking"] }]
"optOut": [{ "hide": "#consent-tracking" }]
}
2 changes: 1 addition & 1 deletion rules/autoconsent/athlinks-com.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"detectCmp": [{ "exists": "#footer-container ~ div" }],
"detectPopup": [{ "visible": "#footer-container > div" }],
"optIn": [{"click": "#footer-container ~ div button" }],
"optOut": [{ "hide": ["#footer-container ~ div"] }]
"optOut": [{ "hide": "#footer-container ~ div" }]
}
2 changes: 1 addition & 1 deletion rules/autoconsent/ausopen.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"cosmetic": true,
"detectCmp": [{ "exists": ".gdpr-popup__message" }],
"detectPopup": [{ "visible": ".gdpr-popup__message" }],
"optOut": [{ "hide": [".gdpr-popup__message"]}],
"optOut": [{ "hide": ".gdpr-popup__message"}],
"optIn": [{ "click": ".gdpr-popup__message button"}]
}
2 changes: 1 addition & 1 deletion rules/autoconsent/baden-wuerttemberg-de.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
{ "click": ".cookie-alert__button button" }
],
"optOut": [
{ "hide": [".cookie-alert.t-dark"] }
{ "hide": ".cookie-alert.t-dark" }
]
}
2 changes: 1 addition & 1 deletion rules/autoconsent/bbb.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"detectCmp": [{ "exists": "div[aria-label=\"use of cookies on bbb.org\"]" }],
"detectPopup": [{ "visible": "div[aria-label=\"use of cookies on bbb.org\"]" }],
"optIn": [{ "click": "div[aria-label=\"use of cookies on bbb.org\"] button.bds-button-unstyled span.visually-hidden" }],
"optOut": [{ "hide": ["div[aria-label=\"use of cookies on bbb.org\"]"] }]
"optOut": [{ "hide": "div[aria-label=\"use of cookies on bbb.org\"]" }]
}
2 changes: 1 addition & 1 deletion rules/autoconsent/burpee-com.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
{ "click": "#btn-cookie-allow" }
],
"optOut": [
{ "hide": ["#html-body #notice-cookie-block", "#notice-cookie"] }
{ "hide": "#html-body #notice-cookie-block, #notice-cookie" }
]
}
2 changes: 1 addition & 1 deletion rules/autoconsent/cc-banner.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"detectCmp": [{ "exists": ".cc_banner-wrapper" }],
"detectPopup": [{ "visible": ".cc_banner" }],
"optIn": [{ "click": ".cc_btn_accept_all" }],
"optOut": [{ "hide": [".cc_banner-wrapper"] }]
"optOut": [{ "hide": ".cc_banner-wrapper" }]
}
2 changes: 1 addition & 1 deletion rules/autoconsent/clustrmaps-com.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"detectCmp": [{ "exists": "#gdpr-cookie-message" }],
"detectPopup": [{ "visible": "#gdpr-cookie-message" }],
"optIn": [{ "click": "button#gdpr-cookie-accept" }],
"optOut": [{ "hide": ["#gdpr-cookie-message"] }]
"optOut": [{ "hide": "#gdpr-cookie-message" }]
}
2 changes: 1 addition & 1 deletion rules/autoconsent/complianz-notice.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
{ "click": ".cc-dismiss", "optional": true }
],
"optOut": [
{ "hide": ["[aria-describedby=\"cookieconsent:desc\"]"] }
{ "hide": "[aria-describedby=\"cookieconsent:desc\"]" }
]
}
2 changes: 1 addition & 1 deletion rules/autoconsent/cookie-law-info.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"detectPopup": [{ "visible": "#cookie-law-info-bar" }],
"optIn": [{ "click": "[data-cli_action=\"accept_all\"]" }],
"optOut": [
{ "hide": ["#cookie-law-info-bar"] },
{ "hide": "#cookie-law-info-bar" },
{
"eval": "EVAL_COOKIE_LAW_INFO_0"
}
Expand Down
2 changes: 1 addition & 1 deletion rules/autoconsent/cookie-notice.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
],
"detectPopup": [{ "visible": "#cookie-notice" }],
"optIn": [{ "click": "#cn-accept-cookie" }],
"optOut": [{ "hide": ["#cookie-notice"] }]
"optOut": [{ "hide": "#cookie-notice" }]
}
2 changes: 1 addition & 1 deletion rules/autoconsent/cookieinformation.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"detectPopup": [{ "visible": "#cookie-information-template-wrapper" }],
"optIn": [ { "eval": "EVAL_COOKIEINFORMATION_1"} ],
"optOut": [
{ "hide": ["#cookie-information-template-wrapper"], "comment": "some templates don't hide the banner automatically" },
{ "hide": "#cookie-information-template-wrapper", "comment": "some templates don't hide the banner automatically" },
{ "eval": "EVAL_COOKIEINFORMATION_0"}
],
"test": [
Expand Down
2 changes: 1 addition & 1 deletion rules/autoconsent/cookieyes.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
{ "waitForThenClick": ".cky-modal [data-cky-tag=detail-save-button]" }
],
"else": [
{ "hide": [".cky-consent-container,.cky-overlay"] }
{ "hide": ".cky-consent-container,.cky-overlay" }
]
}
]
Expand Down
2 changes: 1 addition & 1 deletion rules/autoconsent/crossfit-com.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
"detectCmp": [{ "exists": "body #modal > div > div[class^=\"_wrapper_\"]" }],
"detectPopup": [{ "visible": "body #modal > div > div[class^=\"_wrapper_\"]" }],
"optIn": [{"click": "button[aria-label=\"accept cookie policy\"]" }],
"optOut": [{ "hide": ["body #modal > div > div[class^=\"_wrapper_\"]"] }]
"optOut": [{ "hide": "body #modal > div > div[class^=\"_wrapper_\"]" }]
}

Loading

0 comments on commit 4d1d551

Please sign in to comment.