{"id":13596838,"url":"https://github.com/AdguardTeam/ExtendedCss","last_synced_at":"2025-04-09T20:31:30.291Z","repository":{"id":10480035,"uuid":"65893322","full_name":"AdguardTeam/ExtendedCss","owner":"AdguardTeam","description":"A TypeScript library for non-standard element selecting — :contains(), :matches-css(), etc., and applying CSS styles with extended properties.","archived":false,"fork":false,"pushed_at":"2024-03-11T16:33:36.000Z","size":2967,"stargazers_count":57,"open_issues_count":26,"forks_count":9,"subscribers_count":20,"default_branch":"master","last_synced_at":"2024-04-14T05:57:59.669Z","etag":null,"topics":["adguard","css","open-source"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AdguardTeam.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-08-17T09:11:34.000Z","updated_at":"2024-06-17T12:03:15.976Z","dependencies_parsed_at":"2024-06-17T12:02:51.843Z","dependency_job_id":"449aec13-120c-4686-b663-b2edb07dd961","html_url":"https://github.com/AdguardTeam/ExtendedCss","commit_stats":{"total_commits":541,"total_committers":11,"mean_commits":49.18181818181818,"dds":0.7430683918669132,"last_synced_commit":"b1b3184a219bb04d3edc5efde8e502323e177a89"},"previous_names":[],"tags_count":66,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AdguardTeam%2FExtendedCss","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AdguardTeam%2FExtendedCss/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AdguardTeam%2FExtendedCss/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AdguardTeam%2FExtendedCss/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AdguardTeam","download_url":"https://codeload.github.com/AdguardTeam/ExtendedCss/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248107349,"owners_count":21048908,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["adguard","css","open-source"],"created_at":"2024-08-01T16:02:51.347Z","updated_at":"2025-04-09T20:31:29.415Z","avatar_url":"https://github.com/AdguardTeam.png","language":"TypeScript","readme":"# \u003ca name=\"homepage\"\u003e\u003c/a\u003e ExtendedCss [![npm-badge]][npm-url] [![install-size-badge]][install-size-url] [![license-badge]][license-url]\n\nAdGuard's TypeScript library for non-standard element selecting and applying CSS styles with extended properties.\n\nThe idea of extended capabilities is an opportunity to match DOM elements with selectors based on their own representation (style, text content, etc.) or relations with other elements. There is also an opportunity to apply styles with non-standard CSS properties.\n\n* [Extended capabilities](#extended-capabilities)\n  * [Limitations](#extended-css-limitations)\n  * [Pseudo-class `:has()`](#extended-css-has)\n  * [Pseudo-class `:contains()`](#extended-css-contains)\n  * [Pseudo-class `:matches-css()`](#extended-css-matches-css)\n  * [Pseudo-class `:matches-attr()`](#extended-css-matches-attr)\n  * [Pseudo-class `:matches-property()`](#extended-css-matches-property)\n  * [Pseudo-class `:xpath()`](#extended-css-xpath)\n  * [Pseudo-class `:nth-ancestor()`](#extended-css-nth-ancestor)\n  * [Pseudo-class `:upward()`](#extended-css-upward)\n  * [Pseudo-class `:remove()` and pseudo-property `remove`](#remove-pseudos)\n  * [Pseudo-class `:is()`](#extended-css-is)\n  * [Pseudo-class `:not()`](#extended-css-not)\n  * [Pseudo-class `:if-not()` (deprecated)](#extended-css-if-not)\n  * [Selectors debugging mode](#selectors-debug-mode)\n  * [Backward compatible syntax](#extended-css-old-syntax)\n* [How to build](#how-to-build)\n* [How to test](#how-to-test)\n* [Usage](#usage)\n  * [API description](#extended-css-api)\n    * [Constructor](#extended-css-constructor)\n    * [init()](#extended-css-init)\n    * [apply() and dispose()](#extended-css-apply-dispose)\n    * [query()](#extended-css-query)\n    * [validate()](#extended-css-validate)\n    * [EXTENDED_CSS_VERSION](#extended-css-version)\n  * [Debugging extended selectors](#debugging-extended-selectors)\n* [Projects using ExtendedCss](#projects-using-extended-css)\n* [Browser compatibility](#browser-compatibility)\n* [Known issues](#known-issues)\n\n\n## Extended capabilities\n\n\u003e Some pseudo-classes does not require a selector before them. Still adding a [universal selector](https://www.w3.org/TR/selectors-4/#the-universal-selector) `*` makes an extended selector easier to read, even though it has no effect on the matching behavior. So the selector `#block :has(\u003e .inner)` works exactly like `#block *:has(\u003e .inner)` but second one is more obvious.\n\n\u003e Pseudo-class names are case-insensitive, e.g. `:HAS()` works as `:has()`. Still the lower-case names are used commonly.\n\n### \u003ca name=\"extended-css-limitations\"\u003e\u003c/a\u003e Limitations\n\n1. CSS [comments](https://developer.mozilla.org/en-US/docs/Web/CSS/Comments) and [at-rules](https://developer.mozilla.org/en-US/docs/Web/CSS/At-rule) are not supported.\n\n2. Specific pseudo-class may have its own limitations:\n[`:has()`](#extended-css-has-limitations), [`:xpath()`](#extended-css-xpath-limitations), [`:nth-ancestor()`](#extended-css-nth-ancestor-limitations), [`:upward()`](#extended-css-upward-limitations), [`:is()`](#extended-css-is-limitations), [`:not()`](#extended-css-not-limitations), and [`:remove()`](#extended-css-remove-limitations).\n\n\n### \u003ca name=\"extended-css-has\"\u003e\u003c/a\u003e Pseudo-class `:has()`\n\nDraft CSS 4.0 specification describes the [`:has()` pseudo-class](https://www.w3.org/TR/selectors-4/#relational). Unfortunately, [it is not yet supported](https://caniuse.com/css-has) by all popular browsers.\n\n\u003e Rules with the `:has()` pseudo-class should use [native implementation of `:has()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:has) if they use `##` marker and if it is possible, i.e. with no other extended selectors inside. To force applying ExtendedCss rules with `:has()`, use `#?#`/`#$?#` marker explicitly.\n\n\u003e Synonym `:-abp-has` is supported by ExtendedCss for better compatibility.\n\n\u003e `:if()` is no longer supported as a synonym for `:has()`.\n\n**Syntax**\n\n```\n[target]:has(selector)\n```\n- `target` — optional, standard or extended CSS selector, can be missed for checking *any* element\n- `selector` — required, standard or extended CSS selector\n\nThe pseudo-class `:has()` selects the `target` elements that fit to the `selector`. Also the `selector` can start with a combinator.\n\nA selector list can be set in `selector` as well. In this case **all** selectors in the list are being matched for now. It is [one of the known issues](#known-issues) and will be fixed for `\u003cforgiving-relative-selector-list\u003e` as argument.\n\n\u003ca name=\"extended-css-has-limitations\"\u003e\u003c/a\u003e **Limitations and notes**\n\n\u003e Usage of the `:has()` pseudo-class is [restricted for some cases (2, 3)](https://bugs.chromium.org/p/chromium/issues/detail?id=669058#c54):\n\u003e - disallow `:has()` inside the pseudos accepting only compound selectors;\n\u003e - disallow `:has()` after regular pseudo-elements.\n\n\u003e Native `:has()` pseudo-class does not allow `:has()`, `:is()`, `:where()` inside `:has()` argument to avoid increasing the `:has()` invalidation complexity ([case 1](https://bugs.chromium.org/p/chromium/issues/detail?id=669058#c54)). But ExtendedCss did not have such limitation earlier and filter lists already contain such rules, so we have not added this limitation to ExtendedCss and allow to use `:has()` inside `:has()` as it was possible before. To use it, just force ExtendedCss usage by setting `#?#`/`#$?#` rule marker.\n\n\u003e Native implementation does not allow any usage of `:scope` inside `:has()` argument ([[1]](https://github.com/w3c/csswg-drafts/issues/7211), [[2]](https://github.com/w3c/csswg-drafts/issues/6399)). Still, there are some such rules in filter lists: `div:has(:scope \u003e a)` which we continue to support by simply converting them to `div:has(\u003e a)`, as it used to be done previously.\n\n**Examples**\n\n`div:has(.banner)` selects all `div` elements which **include** an element with the `banner` class:\n```html\n\u003c!-- HTML code --\u003e\n\u003cdiv\u003eNot selected\u003c/div\u003e\n\u003cdiv\u003eSelected\n  \u003cspan class=\"banner\"\u003einner element\u003c/span\u003e\n\u003c/div\u003e\n```\n\n`div:has(\u003e .banner)` selects all `div` elements which **include** an `banner` class element as a *direct child* of `div`:\n```html\n\u003c!-- HTML code --\u003e\n\u003cdiv\u003eNot selected\u003c/div\u003e\n\u003cdiv\u003eSelected\n  \u003cp class=\"banner\"\u003echild element\u003c/p\u003e\n\u003c/div\u003e\n```\n\n`div:has(+ .banner)` selects all `div` elements **preceding** `banner` class element which *immediately follows* the `div` and both are children of the same parent:\n```html\n\u003c!-- HTML code --\u003e\n\u003cdiv\u003eNot selected\u003c/div\u003e\n\u003cdiv\u003eSelected\u003c/div\u003e\n\u003cp class=\"banner\"\u003eadjacent sibling\u003c/p\u003e\n\u003cspan\u003eNot selected\u003c/span\u003e\n```\n\n`div:has(~ .banner)` selects all `div` elements **preceding** `banner` class element which *follows* the `div` but *not necessarily immediately* and both are children of the same parent:\n```html\n\u003c!-- HTML code --\u003e\n\u003cdiv\u003eNot selected\u003c/div\u003e\n\u003cdiv\u003eSelected\u003c/div\u003e\n\u003cspan\u003eNot selected\u003c/span\u003e\n\u003cp class=\"banner\"\u003egeneral sibling\u003c/p\u003e\n```\n\n`div:has(span, .banner)` selects all `div` elements which **include both** `span` element and `banner` class element:\n```html\n\u003c!-- HTML code --\u003e\n\u003cdiv\u003eNot selected\u003c/div\u003e\n\u003cdiv\u003eSelected\n  \u003cspan\u003echild span\u003c/span\u003e\n  \u003cp class=\"banner\"\u003echild .banner\u003c/p\u003e\n\u003c/div\u003e\n```\n\n\u003e [Backward compatible syntax for `:has()`](#old-syntax-has) is supported but not recommended.\n\n\n### \u003ca name=\"extended-css-contains\"\u003e\u003c/a\u003e Pseudo-class `:contains()`\n\nThis pseudo-class principle is very simple: it allows to select the elements that contain specified text or which content matches a specified regular expression. Regexp flags are supported.\n\n\u003e Pseudo-class `:contains()` uses the `textContent` element property for matching, not the `innerHTML`.\n\n\u003e Synonyms `:-abp-contains` and `:has-text` are supported for better compatibility.\n\n**Syntax**\n\n```\n[target]:contains(match)\n```\n- `target` — optional, standard or extended CSS selector, can be missed for checking *any* element\n- `match` — required, string or regular expression for matching element textContent\n\n\u003e Regexp flags are supported for `match`.\n\n**Examples**\n\nFor such DOM:\n```html\n\u003c!-- HTML code --\u003e\n\u003cdiv\u003eNot selected\u003c/div\u003e\n\u003cdiv id=\"match\"\u003eSelected as IT contains \"banner\"\u003c/div\u003e\n\u003cdiv\u003eNot selected \u003cdiv class=\"banner\"\u003e\u003c/div\u003e\u003c/div\u003e\n```\n\n`div#match` can be selected by any on these extended selectors:\n```\n! plain text\ndiv:contains(banner)\n\n! regular expression\ndiv:contains(/as .* banner/)\n\n! regular expression with flags\ndiv:contains(/it .* banner/gi)\n```\n\n\u003e Only the `div` with `id=match` is selected because the next element does not contain any text, and `banner` is a part of code, not a text.\n\n\u003e [Backward compatible syntax for `:contains()`](#old-syntax-contains) is supported but not recommended.\n\n\n### \u003ca name=\"extended-css-matches-css\"\u003e\u003c/a\u003e Pseudo-class `:matches-css()`\n\nPseudo-class `:matches-css()` allows to match the element by its current style properties. The work of the pseudo-class is based on using the [`Window.getComputedStyle()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle) method.\n\n**Syntax**\n\n```\n[target]:matches-css([pseudo-element, ] property: pattern)\n```\n- `target` — optional, standard or extended CSS selector, can be missed for checking *any* element\n- `pseudo-element` — optional, valid standard pseudo-element, e.g. `before`, `after`, `first-line`, etc.\n- `property` — required, a name of CSS property to check the element for\n- `pattern` —  required, a value pattern that is using the same simple wildcard matching as in the basic url filtering rules OR a regular expression. For this type of matching, AdGuard always does matching in a case-insensitive manner. In the case of a regular expression, the pattern looks like `/regexp/`.\n\n\u003e For **non-regexp** patterns `(`,`)`,`[`,`]` must be **unescaped**, e.g. `:matches-css(background-image:url(data:*))`.\n\n\u003e For **regexp** patterns `\\` should be **escaped**, e.g. `:matches-css(background-image: /^url\\\\(\"data:image\\\\/gif;base64.+/)`.\n\n\u003c!-- TODO: https://github.com/AdguardTeam/ExtendedCss/issues/138 --\u003e\n\u003e Regexp patterns **do not support** flags.\n\n**Examples**\n\nFor such DOM:\n```html\n\u003c!-- HTML code --\u003e\n\u003cstyle type=\"text/css\"\u003e\n    #matched::before {\n        content: \"Block me\"\n    }\n\u003c/style\u003e\n\u003cdiv id=\"matched\"\u003e\u003c/div\u003e\n\u003cdiv id=\"not-matched\"\u003e\u003c/div\u003e\n```\n\n`div` elements with pseudo-element `::before` with specified `content` property can be selected by any of these extended selectors:\n```\n! string pattern\ndiv:matches-css(before, content: block me)\n\n! string pattern with wildcard\ndiv:matches-css(before, content: block*)\n\n! regular expression pattern\ndiv:matches-css(before, content: /block me/)\n```\n\n\u003e Obsolete pseudo-classes `:matches-css-before()` and `:matches-css-after()` are supported for better compatibility.\n\n\u003e [Backward compatible syntax for `:matches-css()`](#old-syntax-matches-css) is supported but not recommended.\n\n\n### \u003ca name=\"extended-css-matches-attr\"\u003e\u003c/a\u003e Pseudo-class `:matches-attr()`\n\nPseudo-class `:matches-attr()` allows to select an element by its attributes, especially if they are randomized.\n\n**Syntax**\n\n```\n[target]:matches-attr(\"name\"[=\"value\"])\n```\n- `target` — optional, standard or extended CSS selector, can be missed for checking *any* element\n- `name` — required, simple string *or* string with wildcard *or* regular expression for attribute name matching\n- `value` — optional, simple string *or* string with wildcard *or* regular expression for attribute value matching\n\n\u003e For **regexp** patterns `\"` and `\\` should be **escaped**, e.g. `div:matches-attr(class=/[\\\\w]{5}/)`.\n\n\u003e Regexp patterns **do not support** flags.\n\n**Examples**\n\n`div:matches-attr(\"ad-link\")` selects the element `div#target1`:\n```html\n\u003c!-- HTML code --\u003e\n\u003cdiv id=\"target1\" ad-link=\"1random23-banner_240x400\"\u003e\u003c/div\u003e\n```\n\n`div:matches-attr(\"data-*\"=\"adBanner\")` selects the element `div#target2`:\n```html\n\u003c!-- HTML code --\u003e\n\u003cdiv id=\"target2\" data-1random23=\"adBanner\"\u003e\u003c/div\u003e\n```\n\n`div:matches-attr(*unit*=/^click$/)` selects the element `div#target3`:\n```html\n\u003c!-- HTML code --\u003e\n\u003cdiv id=\"target3\" random123-unit094=\"click\"\u003e\u003c/div\u003e\n```\n\n`*:matches-attr(\"/.{5,}delay$/\"=\"/^[0-9]*$/\")` selects the element `#target4`:\n```html\n\u003c!-- HTML code --\u003e\n\u003cdiv\u003e\n  \u003cinner-random23 id=\"target4\" nt4f5be90delay=\"1000\"\u003e\u003c/inner-random23\u003e\n\u003c/div\u003e\n```\n\n\n### \u003ca name=\"extended-css-matches-property\"\u003e\u003c/a\u003e Pseudo-class `:matches-property()`\n\nPseudo-class `:matches-property()` allows to select an element by matching its properties.\n\n**Syntax**\n\n```\n[target]:matches-property(\"name\"[=\"value\"])\n```\n- `target` — optional, standard or extended CSS selector, can be missed for checking *any* element\n- `name` — required, simple string *or* string with wildcard *or* regular expression for element property name matching\n- `value` — optional, simple string *or* string with wildcard *or* regular expression for element property value matching\n\n\u003e For **regexp** patterns `\"` and `\\` should be escaped, e.g. `div:matches-property(prop=/[\\\\w]{4}/)`.\n\n\u003e Regexp patterns are supported in `name` for any property in chain, e.g. `prop./^unit[\\\\d]{4}$/.type`.\n\n\u003e Regexp patterns **do not support** flags.\n\n**Examples**\n\nAn element with such properties:\n```javascript\ndivProperties = {\n  id: 1,\n  check: {\n    track: true,\n    unit_2random1: true,\n  },\n  memoizedProps: {\n    key: null,\n    tag: 12,\n    _owner: {\n      effectTag: 1,\n      src: 'ad.com',\n    },\n  },\n};\n```\n\ncan be selected by any of these extended selectors:\n```\ndiv:matches-property(check.track)\n\ndiv:matches-property(\"check./^unit_.{4,8}$/\")\n\ndiv:matches-property(\"check.unit_*\"=true)\n\ndiv:matches-property(memoizedProps.key=\"null\")\n\ndiv:matches-property(memoizedProps._owner.src=/ad/)\n```\n\n\u003e **For filters maintainers:** To check properties of a specific element, do the following:\n\u003e 1. Inspect the page element or select it in `Elements` tab of browser DevTools.\n\u003e 2. Run `console.dir($0)` in `Console` tab.\n\n\n### \u003ca name=\"extended-css-xpath\"\u003e\u003c/a\u003e Pseudo-class `:xpath()`\n\nThe `:xpath()` pseudo-class allows to select an element by evaluating an XPath expression.\n\n**Syntax**\n\n```\n[target]:xpath(expression)\n```\n- `target`- optional, standard or extended CSS selector\n- `expression` — required, valid XPath expression\n\n\u003ca name=\"extended-css-xpath-limitations\"\u003e\u003c/a\u003e **Limitations**\n\n\u003e `target` can be omitted so it is optional. For any other pseudo-class that would mean \"apply to *all* DOM nodes\", but in case of `:xpath()` it just means \"apply to the *whole* document\", and such applying slows elements selecting significantly. That's why rules like `#?#:xpath(expression)` are limited to looking inside the `body` tag. For example, rule `#?#:xpath(//div[@data-st-area=\\'Advert\\'])` is parsed as `#?#body:xpath(//div[@data-st-area=\\'Advert\\'])`.\n\n\u003e Extended selectors with defined `target` as *any* selector — `*:xpath(expression)` — can still be used but it is not recommended, so `target` should be specified instead.\n\n\u003e Works properly only at the end of selector, except for [pseudo-class :remove()](#remove-pseudos).\n\n**Examples**\n\n`:xpath(//*[@class=\"banner\"])` selects the element `div#target1`:\n```html\n\u003c!-- HTML code --\u003e\n\u003cdiv id=\"target1\" class=\"banner\"\u003e\u003c/div\u003e\n```\n\n`:xpath(//*[@class=\"inner\"]/..)` selects the element `div#target2`:\n```html\n\u003c!-- HTML code --\u003e\n\u003cdiv id=\"target2\"\u003e\n  \u003cdiv class=\"inner\"\u003e\u003c/div\u003e\n\u003c/div\u003e\n```\n\n\n### \u003ca name=\"extended-css-nth-ancestor\"\u003e\u003c/a\u003e Pseudo-class `:nth-ancestor()`\n\nThe `:nth-ancestor()` pseudo-class allows to lookup the *nth* ancestor relative to the previously selected element.\n\n**Syntax**\n\n```\nsubject:nth-ancestor(n)\n```\n- `subject` — required, standard or extended CSS selector\n- `n` — required, number \u003e= 1 and \u003c 256, distance to the needed ancestor from the element selected by `subject`\n\n\u003ca name=\"extended-css-nth-ancestor-limitations\"\u003e\u003c/a\u003e **Limitations**\n\n\u003e Pseudo-class `:nth-ancestor()` is not supported inside the argument of the [`:not()` pseudo-class](#extended-css-not). It is [one of the known issues](#known-issues).\n\n**Examples**\n\nFor such DOM:\n```html\n\u003c!-- HTML code --\u003e\n\u003cdiv id=\"target1\"\u003e\n  \u003cdiv class=\"child\"\u003e\u003c/div\u003e\n\n  \u003cdiv id=\"target2\"\u003e\n    \u003cdiv\u003e\n      \u003cdiv\u003e\n        \u003cdiv class=\"inner\"\u003e\u003c/div\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\n`.child:nth-ancestor(1)` selects the element `div#target1`,\n`div[class=\"inner\"]:nth-ancestor(3)` selects the element `div#target2`.\n\n\n### \u003ca name=\"extended-css-upward\"\u003e\u003c/a\u003e Pseudo-class `:upward()`\n\nThe `:upward()` pseudo-class allows to lookup the ancestor relative to the previously selected element.\n\n**Syntax**\n\n```\nsubject:upward(ancestor)\n```\n- `subject` — required, standard or extended CSS selector\n- `ancestor` — required, specification for the ancestor of the element selected by `subject`, can be set as:\n  - *number* \u003e= 1 and \u003c 256 for distance to the needed ancestor, same as [`:nth-ancestor()`](#extended-css-nth-ancestor)\n  - *standard CSS selector* for matching closest ancestor\n\n\u003ca name=\"extended-css-upward-limitations\"\u003e\u003c/a\u003e **Limitations**\n\n\u003e Pseudo-class `:upward()` is not supported inside the argument of the [`:not()` pseudo-class](#extended-css-not) argument. It is [one of the known issues](#known-issues).\n\n**Examples**\n\nFor such DOM:\n```html\n\u003c!-- HTML code --\u003e\n\u003cdiv id=\"target1\" data=\"true\"\u003e\n  \u003cdiv class=\"child\"\u003e\u003c/div\u003e\n\n  \u003cdiv id=\"target2\"\u003e\n    \u003cdiv\u003e\n      \u003cdiv\u003e\n        \u003cdiv class=\"inner\"\u003e\u003c/div\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\n`.inner:upward(div[data])` selects the element `div#target1`,\n`.inner:upward(div[id])` selects the element `div#target2`,\n`.child:upward(1)` selects the element `div#target1`,\n`.inner:upward(3)` selects the element `div#target2`.\n\n\n### \u003ca name=\"remove-pseudos\"\u003e\u003c/a\u003e Pseudo-class `:remove()` and pseudo-property `remove`\n\nSometimes, it is necessary to remove a matching element instead of hiding it or applying custom styles. In order to do it, you can use the `:remove()` pseudo-class as well as the `remove` pseudo-property.\n\n**Syntax**\n\n```\n! pseudo-class\nselector:remove()\n\n! pseudo-property\nselector { remove: true; }\n```\n- `selector` — required, standard or extended CSS selector\n\n\u003ca name=\"extended-css-remove-limitations\"\u003e\u003c/a\u003e **Limitations**\n\n\u003e The `:remove()` pseudo-class is limited to work properly only at the end of selector.\n\n\u003e For applying the `:remove()` pseudo-class to any element [universal selector](https://www.w3.org/TR/selectors-4/#the-universal-selector) `*` should be used. Otherwise such extended selector may be considered as invalid, e.g. `.banner \u003e :remove()` is not valid for removing any child element of `banner` class element, so it should look like `.banner \u003e *:remove()`.\n\n\u003e If the `:remove()` pseudo-class or the `remove` pseudo-property is used, all style properties are ignored except for the [`debug` pseudo-property](#selectors-debug-mode).\n\n**Examples**\n\n```\ndiv.banner:remove()\ndiv:has(\u003e div[ad-attr]):remove()\n\ndiv:contains(advertisement) { remove: true; }\ndiv[class]:has(\u003e a \u003e img) { remove: true; }\n```\n\n\u003e Rules with the `remove` pseudo-property should use `#$?#` marker: `$` for CSS style rules syntax, `?` for ExtendedCss syntax.\n\n\u003c!-- TODO: consider :remove() pseudo-class deprecation --\u003e\n\u003c!-- https://github.com/AdguardTeam/ExtendedCss/issues/160 --\u003e\n\u003e Both `:remove()` pseudo-class and `remove` pseudo-property works the same, but we recommend to use the pseudo-property as it is related to an action which should be applied to element, since pseudo-classes is more about elements matching.\n\n### \u003ca name=\"extended-css-is\"\u003e\u003c/a\u003e Pseudo-class `:is()`\n\nThe `:is()` pseudo-class allows to match any element that can be selected by any of selectors passed to it. Invalid selectors are skipped and the pseudo-class deals with valid ones with no error thrown. Our implementation of the [native `:is()` pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:is).\n\n**Syntax**\n\n```\n[target]:is(selectors)\n```\n- `target` — optional, standard or extended CSS selector, can be missed for checking *any* element\n- `selectors` — [*forgiving selector list*](https://drafts.csswg.org/selectors-4/#typedef-forgiving-selector-list) of standard or extended selectors. For extended selectors only compound selectors are supported, not complex.\n\n\u003ca name=\"extended-css-is-limitations\"\u003e\u003c/a\u003e **Limitations**\n\n\u003e Rules with the `:is()` pseudo-class should use the [native implementation of `:is()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:is) if rules use `##` marker and it is possible, i.e. with no other extended selectors inside. To force applying ExtendedCss rules with `:is()`, use `#?#`/`#$?#` marker explicitly.\n\n\u003e If the `:is()` pseudo-class argument `selectors` is an extended selector, due to the way how the `:is()` pseudo-class is implemented in ExtendedCss v2.0, it is impossible to apply it to the top DOM node which is `html`, i.e. `#?#html:is(\u003cextended-selectors\u003e)` does not work. So if `target` is not defined or defined as an [universal selector](https://www.w3.org/TR/selectors-4/#the-universal-selector) `*`, the extended pseudo-class applying is limited to **`html`'s children**, e.g. rules `#?#:is(...)` and `#?#*:is(...)` are parsed as `#?#html *:is(...)`. Please note that there is no such limitation for a standard selector argument, i.e. `#?#html:is(.locked)` works fine.\n\n\u003e [Complex selectors](https://www.w3.org/TR/selectors-4/#complex) with extended pseudo-classes are not supported as `selectors` argument for `:is()` pseudo-class, only [compound ones](https://www.w3.org/TR/selectors-4/#compound) are allowed. It is [one of the known issues](#known-issues). Check examples below for more details.\n\n**Examples**\n\n`#container *:is(.inner, .footer)` selects only the element `div#target1`:\n```html\n\u003c!-- HTML code --\u003e\n\u003cdiv id=\"container\"\u003e\n  \u003cdiv data=\"true\"\u003e\n    \u003cdiv\u003e\n      \u003cdiv id=\"target1\" class=\"inner\"\u003e\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\nDue to limitations `:is(*:not([class]) \u003e .banner)'` does not work\nbut `:is(*:not([class]):has(\u003e .banner))` can be used instead of it to select the element `div#target2`:\n```html\n\u003c!-- HTML code --\u003e\n\u003cspan class=\"span\"\u003etext\u003c/span\u003e\n\u003cdiv id=\"target2\"\u003e\n  \u003cp class=\"banner\"\u003einner paragraph\u003c/p\u003e\n\u003c/div\u003e\n```\n\n\n### \u003ca name=\"extended-css-not\"\u003e\u003c/a\u003e Pseudo-class `:not()`\n\nThe `:not()` pseudo-class allows to select elements which are *not matched* by selectors passed as argument. Invalid argument selectors are not allowed and error is to be thrown. Our implementation of the [`:not()` pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:not).\n\n**Syntax**\n\n```\n[target]:not(selectors)\n```\n- `target` — optional, standard or extended CSS selector, can be missed for checking *any* element\n- `selectors` — list of standard or extended selectors\n\n\u003ca name=\"extended-css-not-limitations\"\u003e\u003c/a\u003e **Limitations**\n\n\u003e Rules with the `:not()` pseudo-class should use the [native implementation of `:not()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:not) if rules use `##` marker and it is possible, i.e. with no other extended selectors inside. To force applying ExtendedCss rules with `:not()`, use `#?#`/`#$?#` marker explicitly.\n\n\u003e If the `:not()` pseudo-class argument `selectors` is an extended selector, due to the way how the `:not()` pseudo-class is implemented in ExtendedCss v2.0, it is impossible to apply it to the top DOM node which is `html`, i.e. `#?#html:not(\u003cextended-selectors\u003e)` does not work. So if `target` is not defined or defined as an [universal selector](https://www.w3.org/TR/selectors-4/#the-universal-selector) `*`, the extended pseudo-class applying is limited to **`html`'s children**, e.g. rules `#?#:not(...)` and `#?#*:not(...)` are parsed as `#?#html *:not(...)`. Please note that there is no such limitation for a standard selector argument, i.e. `#?#html:not(.locked)` works fine.\n\n\u003e The `:not()` is considered as a standard CSS pseudo-class inside argument of the [`:upward()` pseudo-class](#extended-css-upward) because `:upward()` supports only standard selectors.\n\n\u003e \"Up-looking\" pseudo-classes which are [`:nth-ancestor()`](#extended-css-nth-ancestor) and [`:upward()`](#extended-css-upward) are not supported inside `selectors` argument for `:not()` pseudo-class. It is [one of the known issues](#known-issues).\n\n**Examples**\n\n`#container \u003e *:not(h2, .text)` selects only the element `div#target1`:\n```html\n\u003c!-- HTML code --\u003e\n\u003cdiv id=\"container\"\u003e\n  \u003ch2\u003eHeader\u003c/h2\u003e\n  \u003cdiv id=\"target1\"\u003e\u003c/div\u003e\n  \u003cspan class=\"text\"\u003etext\u003c/span\u003e\n\u003c/div\u003e\n```\n\n\n### \u003ca name=\"extended-css-if-not\"\u003e\u003c/a\u003e Pseudo-class `:if-not()` (deprecated)\n\n\u003e The `:if-not()` pseudo-class is deprecated and is no longer supported. Rules with it are considered as invalid.\n\nThis pseudo-class was basically a shortcut for `:not(:has())`. It was supported by ExtendedCss for better compatibility with some filters subscriptions.\n\n\n### \u003ca name=\"selectors-debug-mode\"\u003e\u003c/a\u003e Selectors debugging mode\n\nSometimes, you might need to check the performance of a given selector or a stylesheet. In order to do it without interacting with JavaScript directly, you can use a special `debug` style property. When `ExtendedCss` meets this property, it enables the debugging mode either for a single selector or for all selectors, depending on the `debug` value.\n\nSometimes, you might need to check the performance of a given selector or a stylesheet. In order to do it without interacting with JavaScript directly, you can use a special `debug` style property. When `ExtendedCss` meets this property, it enables the debugging mode either for a single selector or for all selectors, depending on the `debug` value.\n\nOpen the browser console while on a web page to see the timing statistics for selector(s) that were applied there. Debugging mode displays the following stats as object where each of the debugged selectors are keys, and value is an object with such properties:\n\n**Always printed:**\n* `selectorParsed` — text of eventually parsed selector\n* `timings` — list of DOM nodes matched by the selector\n  * `appliesCount` — total number of times that the selector has been applied on the page\n  * `appliesTimings` — time that it took to apply the selector on the page, for each of the instances that it has been applied (in milliseconds)\n  * `meanTiming` — mean time that it took to apply the selector on the page\n  * `standardDeviation` — standard deviation\n  * `timingsSum` — total time it took to apply the selector on the page across all instances\n\n**Printed only for remove pseudos:**\n* `removed` — flag to signal if elements we removed\n\n**Printed if elements are not removed:**\n* `matchedElements` — list of DOM nodes matched by the selector\n* `styleApplied` — parsed rule style declaration related to the selector\n\n**Examples**\n\n**Debugging a single selector:**\n\nWhen the value of the `debug` property is `true`, only information about this selector will be shown in the browser console.\n\n```\n#$?#.banner { display: none; debug: true; }\n```\n\n**Enabling global debug:**\n\nWhen the value of the `debug` property is `global`, the console will display information about all extended CSS selectors that have matches on the current page, for all the rules from any of the enabled filters.\n\n```\n#$?#.banner { display: none; debug: global; }\n```\n\n\u003e Global debugging mode also can be enabled by positive `debug` property in [`ExtCssConfiguration`](#ext-css-configuration-interface):\n```js\nconst extendedCss = new ExtendedCss({\n  styleSheet, // required, rules as string\n  debug,      // optional, boolean\n});\n```\n\n\n### \u003ca name=\"extended-css-old-syntax\"\u003e\u003c/a\u003e Backward compatible syntax\n\n**Backward compatible syntax is supported but not recommended.**\n\n### \u003ca name=\"old-syntax-has\"\u003e\u003c/a\u003e Old syntax for pseudo-class `:has()`\n\n**Syntax**\n```\ntarget[-ext-has=\"selector\"]\n```\n\n**Examples**\n```\ndiv[-ext-has=\".banner\"]\n```\n```html\n\u003c!-- HTML code --\u003e\n\u003cdiv\u003eNot selected\u003c/div\u003e\n\u003cdiv\u003eSelected \u003cspan class=\"banner\"\u003e\u003c/span\u003e\u003c/div\u003e\n```\n\n\n### \u003ca name=\"old-syntax-contains\"\u003e\u003c/a\u003e Old syntax for pseudo-class `:contains()`\n\n**Syntax**\n```\n// matching by plain text\ntarget[-ext-contains=\"text\"]\n\n// matching by a regular expression\ntarget[-ext-contains=\"/regex/\"]\n```\n\n**Examples**\n```\n// matching by plain text\ndiv[-ext-contains=\"banner\"]\n\n// matching by a regular expression\ndiv[-ext-contains=\"/this .* banner/\"]\n```\n\n```html\n\u003c!-- HTML code --\u003e\n\u003cdiv\u003eNot selected\u003c/div\u003e\n\u003cdiv id=\"selected\"\u003eSelected as it contains \"banner\"\u003c/div\u003e\n```\n\n\n### \u003ca name=\"old-syntax-matches-css\"\u003e\u003c/a\u003e Old syntax for pseudo-class `:matches-css()`\n\n**Syntax**\n```\ntarget[-ext-matches-css=\"property: pattern\"]\ntarget[-ext-matches-css-after=\"property: pattern\"]\ntarget[-ext-matches-css-before=\"property: pattern\"]\n```\n\n**Examples**\n```html\n\u003c!-- HTML code --\u003e\n\u003cstyle type=\"text/css\"\u003e\n    #matched::before {\n        content: \"Block me\"\n    }\n\u003c/style\u003e\n\u003cdiv id=\"matched\"\u003e\u003c/div\u003e\n\u003cdiv id=\"not-matched\"\u003e\u003c/div\u003e\n```\n\n```\n! string pattern\ndiv[-ext-matches-css-before=\"content: block me\"]\n\n! regular expression pattern\ndiv[-ext-matches-css-before=\"content: /block me/\"]\n```\n\n\n## How to build\n\nInstall dependencies\n```\nyarn install\n```\n\nAnd just run\n```\nyarn build\n```\n\n## How to test\n\nInstall dependencies\n```\nyarn install\n```\n\nRun local node testing\n```\nyarn test local\n```\n\nRun performance tests which are not included in `test local` run and should be executed manually:\n```\nyarn test performance\n```\n\n## Usage\n\nYou can import, require or copy IIFE module with ExtendedCss into your code, e.g.\n```\nimport ExtendedCss from 'extended-css';\n```\nor\n```\nconst ExtendedCss = require('extended-css');\n```\nIIFE module can be found by the following path `./dist/extended-css.js`\n\nAfter that you can use ExtendedCss as you wish.\n\n### \u003ca name=\"extended-css-api\"\u003e\u003c/a\u003e API description\n\n#### \u003ca name=\"extended-css-constructor\"\u003e\u003c/a\u003e Constructor\n\n```\n/**\n * Creates an instance of ExtendedCss\n *\n * @param configuration — required\n */\nconstructor(configuration: ExtCssConfiguration)\n```\n\n\u003ca name=\"ext-css-configuration-interface\"\u003e\u003c/a\u003e\nwhere\n```ts\ninterface ExtCssConfiguration {\n  // css stylesheet — css rules combined in one string\n  styleSheet?: string;\n\n  // css rules — array of separated css rules\n  cssRules?: string;\n\n  // the callback that handles affected elements\n  beforeStyleApplied?: BeforeStyleAppliedCallback;\n\n  // flag for applied selectors logging; equals to `debug: global` in `styleSheet`\n  debug?: boolean;\n}\n```\n\n\u003e Both `styleSheet` and `cssRules` are optional but at least one of them should be set.\n\n\u003e If both `styleSheet` and `cssRules` are set, both of them are to be applied.\n\n```ts\n/**\n * Needed for getting affected node elements and handle style properties before they are applied to them if it is necessary.\n *\n * Used by AdGuard Browser extension to display rules in Filtering log and `collect-hits-count` (via tsurlfilter's CssHitsCounter)\n */\ntype BeforeStyleAppliedCallback = (x:IAffectedElement) =\u003e IAffectedElement;\n\n/**\n * Simplified just for representation.\n * Its optional property 'content' may contain the applied rule text\n */\ninterface IAffectedElement {\n  rules: { style: { content?: string }}[]\n  node: HTMLElement;\n}\n```\n\n#### \u003ca name=\"extended-css-init\"\u003e\u003c/a\u003e Public method `init()`\n\nThe `init()` public method initializes ExtendedCss on a page.\nIt should be executed on page **as soon as possible**,\neven before the ExtendedCss instance is constructed,\notherwise the `:contains()` pseudo-class may work incorrectly.\n\n#### \u003ca name=\"extended-css-apply-dispose\"\u003e\u003c/a\u003e Public methods `apply()` and `dispose()`\n\nAfter the instance of ExtendedCss is created, it can be applied on the page by the `apply()` method. Its applying also can be stopped and styles are to be restored by the `dispose()` method.\n\n```js\n(function() {\n  let styleSheet = 'div.wrapper \u003e div:has(.banner) { display:none!important; }\\n';\n  styleSheet += 'div.wrapper \u003e div:contains(ads) { background:none!important; }';\n  const extendedCss = new ExtendedCss({ styleSheet });\n\n  // apply styleSheet\n  extendedCss.apply();\n\n  // stop applying of this styleSheet\n  setTimeout(function() {\n    extendedCss.dispose();\n  }, 10 * 1000);\n})();\n```\n\n#### \u003ca name=\"extended-css-query\"\u003e\u003c/a\u003e Public method `query()`\n```ts\n/**\n * Returns a list of the document's elements that match the specified selector\n *\n * @param {string} selector — selector text\n * @param {boolean} [noTiming=true] — optional, if true -- do not print the timing to the console\n *\n * @returns a list of elements found\n * @throws an error if the argument is not a valid selector\n */\npublic static query(selector: string, noTiming = true): HTMLElement[]\n```\n\n#### \u003ca name=\"extended-css-validate\"\u003e\u003c/a\u003e Public method `validate()`\n\n```ts\n/**\n * Validates selector\n * @param selector — selector text\n */\npublic static validate(selector: string): ValidationResult\n```\n\nwhere\n```ts\ntype ValidationResult = {\n    // true for valid selector, false for invalid one\n    ok: boolean,\n    // specified for invalid selector\n    error: string | null,\n};\n```\n\n#### \u003ca name=\"extended-css-version\"\u003e\u003c/a\u003e Public property `EXTENDED_CSS_VERSION`\n\ntype: `string`\n\nCurrent version of ExtendedCss.\n\n### Debugging extended selectors\n\nExtendedCss can be executed on any page without using any AdGuard product. In order to do that you should copy and execute the following code in a browser console:\n```js\n!function(e,t,d){C=e.createElement(t),C.src=d,C.onload=function(){alert(\"ExtendedCss loaded successfully\")},s=e.getElementsByTagName(t)[0],s?s.parentNode.insertBefore(C,s):(h=e.getElementsByTagName(\"head\")[0],h.appendChild(C))}(document,\"script\",\"https://AdguardTeam.github.io/ExtendedCss/extended-css.min.js\");\n```\n\nAlternatively, install the [`ExtendedCssDebugger` userscript](https://github.com/AdguardTeam/Userscripts/blob/master/extendedCssDebugger/extended-css.debugger.user.js).\n\nNow you can now use the `ExtendedCss` from global scope, and run its method [`query()`](#extended-css-query) as `Document.querySelectorAll()`\n\n**Examples**\n\n```js\nconst selector = 'div.block:has(.header:matches-css(after, content: Ads))';\n\n// array of HTMLElements matched the `selector` is to be returned\nExtendedCss.query(selector);\n```\n\n\n## \u003ca name=\"projects-using-extended-css\"\u003e\u003c/a\u003e Projects using ExtendedCss\n\n* [CoreLibs](https://github.com/AdguardTeam/CoreLibs) — `Content Script` dist should be updated\n* [TSUrlFilter](https://github.com/AdguardTeam/tsurlfilter)\n* [FiltersCompiler](https://github.com/AdguardTeam/FiltersCompiler)\n* [AdguardBrowserExtension](https://github.com/AdguardTeam/AdguardBrowserExtension) — `TSUrlFilter` should be updated\n* [AdguardForSafari](https://github.com/AdguardTeam/AdGuardForSafari) — `adguard-resources` should be updated\n* [AdguardForiOS](https://github.com/AdguardTeam/AdguardForiOS)  — both `ExtendedCss` and `TSUrlFilter` should be updated in `advanced-adblocker-web-extension`\n\n\n### \u003ca name=\"browser-compatibility\"\u003e\u003c/a\u003e Browser compatibility\n\n| Browser               | Version   |\n|-----------------------|:----------|\n| Chrome                | ✅ 88     |\n| Firefox               | ✅ 84     |\n| Edge                  | ✅ 88     |\n| Opera                 | ✅ 80     |\n| Safari                | ✅ 14     |\n| Internet Explorer     | ❌        |\n\n\n### \u003ca name=\"known-issues\"\u003e\u003c/a\u003e Known issues\n\n- `:has()` pseudo-class should take [`\u003cforgiving-relative-selector-list\u003e` as argument](https://github.com/AdguardTeam/ExtendedCss/issues/154)\n- `:nth-ancestor()` and `:upward()` are not supported [inside of `:not()` pseudo-class argument](https://github.com/AdguardTeam/ExtendedCss/issues/155)\n- `:is()` pseudo-class does not support [complex selectors](https://github.com/AdguardTeam/ExtendedCss/issues/156)\n\n\n[npm-badge]: https://img.shields.io/npm/v/@adguard/extended-css\n[npm-url]: https://www.npmjs.com/package/@adguard/extended-css\n\n[install-size-badge]: https://packagephobia.com/badge?p=@adguard/extended-css\n[install-size-url]: https://packagephobia.com/result?p=@adguard/extended-css\n\n[license-badge]: https://img.shields.io/github/license/AdGuardTeam/ExtendedCss\n[license-url]: https://github.com/AdguardTeam/ExtendedCss/blob/master/LICENSE\n","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FAdguardTeam%2FExtendedCss","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FAdguardTeam%2FExtendedCss","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FAdguardTeam%2FExtendedCss/lists"}