New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@adguard/extended-css

Package Overview
Dependencies
Maintainers
3
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@adguard/extended-css

AdGuard's TypeScript library for non-standard element selecting and applying CSS styles with extended properties.

  • 2.0.10
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
696
increased by25.86%
Maintainers
3
Weekly downloads
 
Created
Source

ExtendedCss npm install size GitHub

AdGuard's TypeScript library for non-standard element selecting and applying CSS styles with extended properties.

The 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.

Extended capabilities

Some pseudo-classes does not require selector before it. Still adding a universal selector * makes an extended selector easier to read, even though it has no effect on the matching behavior. So selector #block :has(> .inner) works exactly like #block *:has(> .inner) but second one is more obvious.

Pseudo-class names are case-insensitive, e.g. :HAS() will work as :has().

Limitations

  1. CSS comments and at-rules are not supported.

  2. Specific pseudo-class may have its own limitations: :has(), :xpath(), :nth-ancestor(), :upward(), :is(), :not(), and :remove().

Pseudo-class :has()

Draft CSS 4.0 specification describes pseudo-class :has. Unfortunately, it is not yet supported by all popular browsers.

Rules with :has() pseudo-class should use native implementation of :has() if rules use ## marker and it is possible, i.e. with no other extended pseudo-classes inside. To force ExtendedCss applying of rules with :has(), use #?#/#$?# marker obviously.

Synonyms :-abp-has and :if are supported by ExtendedCss for better compatibility.

Syntax

[target]:has(selector)
  • target — optional, standard or extended css selector, can be missed for checking any element
  • selector — required, standard or extended css selector

Pseudo-class :has() selects the target elements that includes the elements that fit to the selector. Also selector can start with a combinator. Selector list can be set in selector as well.

Limitations and notes

Usage of :has() pseudo-class is restricted for some cases (2, 3):

  • disallow :has() inside the pseudos accepting only compound selectors;
  • disallow :has() after regular pseudo-elements.

Native :has() pseudo-class does not allow :has(), :is(), :where() inside :has() argument to avoid increasing the :has() invalidation complexity (case 1). But ExtendedCss did not have such limitation earlier and filter lists already contain such rules, so we will not add this limitation in ExtendedCss and allow to use :has() inside :has() as it was possible before. To use it, just force ExtendedCss usage by setting #?#/#$?# rule marker.

Native implementation does not allow any usage of :scope inside :has() argument ([1], [2]). Still there some such rules in filter lists: div:has(:scope > a) which we will continue to support simply converting them to div:has(> a) as it was earlier.

Examples

div:has(.banner) will select all div elements, which includes an element with the banner class:

<!-- HTML code -->
<div>Not selected</div>
<div>Selected
  <span class="banner">inner element</span>
</div>

div:has(> .banner) will select all div elements, which includes an banner class element as a direct child of div:

<!-- HTML code -->
<div>Not selected</div>
<div>Selected
  <p class="banner">child element</p>
</div>

div:has(+ .banner) will select all div elements preceding banner class element which immediately follows the div and both are children of the same parent:

<!-- HTML code -->
<div>Not selected</div>
<div>Selected</div>
<p class="banner">adjacent sibling</p>
<span>Not selected</span>

div:has(~ .banner) will select all div elements preceding banner class element which follows the div but not necessarily immediately and both are children of the same parent:

<!-- HTML code -->
<div>Not selected</div>
<div>Selected</div>
<span>Not selected</span>
<p class="banner">general sibling</p>

div:has(span, .banner) will select all div elements, which includes both span element and banner class element:

<!-- HTML code -->
<div>Not selected</div>
<div>Selected
  <span>child span</span>
  <p class="banner">child .banner</p>
</div>

Backward compatible syntax for :has() is supported but not recommended.

Pseudo-class :if-not()

Pseudo-class :if-not() is basically a shortcut for :not(:has()). It is supported by ExtendedCss for better compatibility with some other filter lists.

:if-not() is not recommended to use in AdGuard filters. The reason is that one day browsers will add :has native support, but it will never happen to this pseudo-class.

Pseudo-class :contains()

This 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.

Pseudo-class :contains() uses the textContent element property for matching, not the innerHTML.

Synonyms :-abp-contains and :has-text are supported for better compatibility.

Syntax

[target]:contains(match)
  • target — optional, standard or extended css selector, can be missed for checking any element
  • match — required, string or regular expression for matching element textContent

Regexp flags are supported for match.

Examples

For such DOM:

<!-- HTML code -->
<div>Not selected</div>
<div id="match">Selected as IT contains "banner"</div>
<div>Not selected <div class="banner"></div></div>

div#match can be selected by any on these extended selectors:

! plain text
div:contains(banner)

! regular expression
div:contains(/as .* banner/)

! regular expression with flags
div:contains(/it .* banner/gi)

Only a div with id=match will be selected because the next element does not contain any text, and banner is a part of code, not a text.

Backward compatible syntax for :contains() is supported but not recommended.

Pseudo-class :matches-css()

Pseudo-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() method.

Syntax

[target]:matches-css([pseudo-element, ] property: pattern)
  • target — optional, standard or extended css selector, can be missed for checking any element
  • pseudo-element — optional, valid standard pseudo-element, e.g. before, after, first-line, etc.
  • property — required, a name of CSS property to check the element for
  • 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/.

For non-regexp patterns (,),[,] must be unescaped, e.g. :matches-css(background-image:url(data:*)).

For regexp patterns \ should be escaped, e.g. :matches-css(background-image: /^url\\("data:image\\/gif;base64.+/).

Regexp patterns does not support flags.

Examples

For such DOM:

<!-- HTML code -->
<style type="text/css">
    #matched::before {
        content: "Block me"
    }
</style>
<div id="matched"></div>
<div id="not-matched"></div>

div elements with pseudo-element ::before with specified content property can be selected by any of these extended selectors:

! string pattern
div:matches-css(before, content: block me)

! string pattern with wildcard
div:matches-css(before, content: block*)

! regular expression pattern
div:matches-css(before, content: /block me/)

Obsolete pseudo-classes :matches-css-before() and :matches-css-after() are supported for better compatibility.

Backward compatible syntax for :matches-css() is supported but not recommended.

Pseudo-class :matches-attr()

Pseudo-class :matches-attr() allows to select an element by its attributes, especially if they are randomized.

Syntax

[target]:matches-attr("name"[="value"])
  • target — optional, standard or extended css selector, can be missed for checking any element
  • name — required, simple string or string with wildcard or regular expression for attribute name matching
  • value — optional, simple string or string with wildcard or regular expression for attribute value matching

For regexp patterns " and \ should be escaped, e.g. div:matches-attr(class=/[\\w]{5}/).

Examples

div:matches-attr("ad-link") will select div#target1:

<!-- HTML code -->
<div id="target1" ad-link="ssdgsg-banner_240x400"></div>

div:matches-attr("data-*"="adBanner") will select div#target2:

<!-- HTML code -->
<div id="target2" data-sdfghlhw="adBanner"></div>

div:matches-attr(*unit*=/^click$/) will select div#target3:

<!-- HTML code -->
<div id="target3" hrewq-unit094="click"></div>

*:matches-attr("/.{5,}delay$/"="/^[0-9]*$/") will select #target4:

<!-- HTML code -->
<div>
  <inner-afhhw id="target4" nt4f5be90delay="1000"></inner-afhhw>
</div>

Pseudo-class :matches-property()

Pseudo-class :matches-property() allows to select an element by matching its properties.

Syntax

[target]:matches-property("name"[="value"])
  • target — optional, standard or extended css selector, can be missed for checking any element
  • name — required, simple string or string with wildcard or regular expression for element property name matching
  • value — optional, simple string or string with wildcard or regular expression for element property value matching

For regexp patterns " and \ should be escaped, e.g. div:matches-property(prop=/[\\w]{4}/).

name supports regexp for property in chain, e.g. prop./^unit[\\d]{4}$/.type.

Examples

Element with such properties:

divProperties = {
    id: 1,
    check: {
        track: true,
        unit_2ksdf1: true,
    },
    memoizedProps: {
        key: null,
        tag: 12,
        _owner: {
            effectTag: 1,
            src: 'ad.com',
        },
    },
};

can be selected by any of these extended selectors:

div:matches-property(check.track)

div:matches-property("check./^unit_.{4,6}$/")

div:matches-property("check.unit_*"=true)

div:matches-property(memoizedProps.key="null")

div:matches-property(memoizedProps._owner.src=/ad/)

For filters maintainers: To check properties of specific element, you should do:

  1. Inspect the needed page element or select it in Elements tab of browser DevTools.
  2. Run console.dir($0) in Console tab.

Pseudo-class :xpath()

Pseudo-class :xpath() allows to select an element by evaluating a XPath expression.

Syntax

[target]:xpath(expression)
  • target- optional, standard or extended css selector
  • expression — required, valid XPath expression

Limitations

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 for looking inside the body tag. For example, rule #?#:xpath(//div[@data-st-area=\'Advert\']) is parsed as #?#body:xpath(//div[@data-st-area=\'Advert\']).

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.

Works properly only at the end of selector, except of pseudo-class :remove().

Examples

:xpath(//*[@class="banner"]) will select div#target1:

<!-- HTML code -->
<div id="target1" class="banner"></div>

:xpath(//*[@class="inner"]/..) will select div#target2:

<!-- HTML code -->
<div id="target2">
  <div class="inner"></div>
</div>

Pseudo-class :nth-ancestor()

Pseudo-class :nth-ancestor() allows to lookup the nth ancestor relative to the previously selected element.

Syntax

subject:nth-ancestor(n)
  • subject — required, standard or extended css selector
  • n — required, number >= 1 and < 256, distance to the needed ancestor from the element selected by subject

Limitations

Pseudo-class :nth-ancestor() is not supported inside :not() pseudo-class argument.

Examples

For such DOM:

<!-- HTML code -->
<div id="target1">
  <div class="child"></div>

  <div id="target2">
    <div>
      <div>
        <div class="inner"></div>
      </div>
    </div>
  </div>
</div>

.child:nth-ancestor(1) will select div#target1 div[class="inner"]:nth-ancestor(3) will select div#target2

Pseudo-class :upward()

Pseudo-class :upward() allows to lookup the ancestor relative to the previously selected element.

Syntax

subject:upward(ancestor)
  • subject — required, standard or extended css selector
  • ancestor — required, specification for the ancestor of the element selected by subject, can be set as:
    • number >= 1 and < 256 for distance to the needed ancestor, same as :nth-ancestor()
    • standard css selector for matching closest ancestor

Limitations

Pseudo-class :upward() is not supported inside :not() pseudo-class argument.

Examples

div.child:upward(div[id])
div:contains(test):upward(div[class^="parent-wrapper-")

div.test:upward(4)
div:has-text(/test/):upward(2)

For such DOM:

<!-- HTML code -->
<div id="target1" data="true">
  <div class="child"></div>

  <div id="target2">
    <div>
      <div>
        <div class="inner"></div>
      </div>
    </div>
  </div>
</div>

.inner:upward(div[data]) will select div#target1 .inner:upward(div[id]) will select div#target2

.child:upward(1) will select div#target1 .inner:upward(3) will select div#target2

Pseudo-class :remove() and pseudo-property remove

Sometimes, it is necessary to remove a matching element instead of hiding it or applying custom styles. In order to do it, you can use pseudo-class :remove() as well as pseudo-property remove.

Syntax

! pseudo-class
selector:remove()

! pseudo-property
selector { remove: true; }
  • selector — required, standard or extended css selector

Limitations

Pseudo-class :remove() is limited to work properly only at the end of selector.

For applying :remove() pseudo-class to any element universal selector * should be used. Otherwise extended selector may be considered as invalid, e.g. .banner > :remove() is not valid for removing any child element of banner class element, so it should look like .banner > *:remove().

If :remove() pseudo-class or remove pseudo-property is used, all style properties will be ignored except of debug pseudo-property.

Examples

div.banner:remove()
div:has(> div[ad-attr]):remove()

div:contains(advertisement) { remove: true; }
div[class]:has(> a > img) { remove: true; }

Rules with remove pseudo-property should use #$?# marker: $ for CSS style rules syntax, ? for ExtendedCss syntax.

Pseudo-class :is()

Pseudo-class :is() allows to match any element that can be selected by any of selectors passed to it. Invalid selectors passed as arg will be skipped and pseudo-class will deal with valid ones with no error. Our implementation of :is() (:matches(), :any()) pseudo-class.

Syntax

[target]:is(selectors)
  • target — optional, standard or extended css selector, can be missed for checking any element
  • selectorsforgiving selector list of standard or extended selectors. For extended selectors only compound selectors are supported, not complex.

Limitations

If target is not defined or defined as universal selector *, pseudo-class :is() applying will be limited to html children, e.g. rules #?#:is(...) and #?#*:is(...) are parsed as #?#html *:is(...).

Complex selectors with extended pseudo-classes are not supported as selectors argument for :is() pseudo-class, only compound ones are allowed. Check examples below.

Examples

#container *:is(.inner, .footer) will select only div#target1:

<!-- HTML code -->
<div id="container">
  <div data="true">
    <div>
      <div id="target1" class="inner"></div>
    </div>
  </div>
</div>

Due to limitations :is(*:not([class]) > .banner)' will not work but :is(*:not([class]):has(> .banner)) can be used instead of it to select div#target2:

<!-- HTML code -->
<span class="span">text</span>
<div id="target2">
  <p class="banner">inner paragraph</p>
</div>

Pseudo-class :not()

Pseudo-class :not() allows to select elements which are not matched by selectors passed as arg. Invalid selectors in arg are not allowed and error will be thrown. Our implementation of :not() pseudo-class.

Syntax

[target]:not(selectors)
  • target — optional, standard or extended css selector, can be missed for checking any element
  • selectors — selector list of standard or extended selectors

Limitations

If target is not defined or defined as universal selector *, pseudo-class :not() applying will be limited to html children, e.g. rules #?#:not(...) and #?#*:not(...) are parsed as #?#html *:not(...).

Inside :upward() pseudo-class argument :not() is considered as a standard CSS pseudo-class because :upward() supports only standard selectors.

"Up-looking" pseudo-classes which are :nth-ancestor() and :upward() are not supported inside selectors argument for :not() pseudo-class.

Examples

#container > *:not(h2, .text) will select only div#target1

<!-- HTML code -->
<div id="container">
  <h2>Header</h2>
  <div id="target1"></div>
  <span class="text">text</span>
</div>

Selectors debug mode

Sometimes, 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 debug mode either for a single selector or for all selectors depending on the debug value.

Debugging a single selector

.banner { display: none; debug: true; }

Enabling global debug

.banner { display: none; debug: global; }

Global debugging mode also can be enabled by positive debug property in ExtCssConfiguration:

const extendedCss = new ExtendedCss({
  styleSheet, // required, rules as string
  debug,      // optional, boolean
});

Backward compatible syntax

Backward compatible syntax is supported but not recommended.

Old syntax for pseudo-class :has()

Syntax

target[-ext-has="selector"]

Examples

div[-ext-has=".banner"]
<!-- HTML code -->
<div>Not selected</div>
<div>Selected <span class="banner"></span></div>

Old syntax for pseudo-class :contains()

Syntax

// matching by plain text
target[-ext-contains="text"]

// matching by a regular expression
target[-ext-contains="/regex/"]

Examples

// matching by plain text
div[-ext-contains="banner"]

// matching by a regular expression
div[-ext-contains="/this .* banner/"]
<!-- HTML code -->
<div>Not selected</div>
<div id="selected">Selected as it contains "banner"</div>

Old syntax for pseudo-class :matches-css()

Syntax

target[-ext-matches-css="property: pattern"]
target[-ext-matches-css-after="property: pattern"]
target[-ext-matches-css-before="property: pattern"]

Examples

<!-- HTML code -->
<style type="text/css">
    #matched::before {
        content: "Block me"
    }
</style>
<div id="matched"></div>
<div id="not-matched"></div>
! string pattern
div[-ext-matches-css-before="content: block me"]

! regular expression pattern
div[-ext-matches-css-before="content: /block me/"]

How to build

Install dependencies

yarn install

And just run

yarn build

How to test

Install dependencies

yarn install

Run local node testing

yarn test local

Run performance tests which are not included in test local run and should be executed manually:

yarn test performance

Usage

You can import, require or copy IIFE module with ExtendedCss into your code, e.g.

import ExtendedCss from 'extended-css';

or

const ExtendedCss = require('extended-css');

IIFE module can be found by the following path ./dist/extended-css.js

After that you can use ExtendedCss as you wish.

API description

Constructor
/**
 * Creates an instance of ExtendedCss
 *
 * @param configuration — required
 */
constructor(configuration: ExtCssConfiguration)

where

interface ExtCssConfiguration {
  // css stylesheet
  styleSheet: string;

  // the callback that handles affected elements
  beforeStyleApplied?: BeforeStyleAppliedCallback;

  // flag for applied selectors logging; equals to `debug: global` in `styleSheet`
  debug?: boolean;
}
/**
 * Needed for getting affected node elements and handle style properties before they are applied to them if it is necessary.
 *
 * Used by AdGuard Browser extension to display rules in Filtering log and `collect-hits-count` (via tsurlfilter's CssHitsCounter)
 */
type BeforeStyleAppliedCallback = (x:IAffectedElement) => IAffectedElement;

/**
 * Simplified just for representation
 * there is a required property 'content' is an applied rule text
 */
interface IAffectedElement {
  rules: { style: { content: string }}[]
  node: HTMLElement;
}

After instance of ExtendedCss is created, it can be applied on page by apply() method. Its applying also can be stopped and styles will be restored by dispose() method.

(function() {
  let styleSheet = 'div.wrapper > div:has(.banner) { display:none!important; }\n';
  styleSheet += 'div.wrapper > div:contains(ads) { background:none!important; }';
  const extendedCss = new ExtendedCss({ styleSheet });

  // apply styleSheet
  extendedCss.apply();

  // stop applying of this styleSheet
  setTimeout(function() {
    extendedCss.dispose();
  }, 10 * 1000);
})();
Public method query()
/**
 * Returns a list of the document's elements that match the specified selector
 *
 * @param {string} selector — selector text
 * @param {boolean} [noTiming=true] — optional, if true -- do not print the timing to the console
 *
 * @returns a list of elements found
 * @throws an error if the argument is not a valid selector
 */
public static query(selector: string, noTiming = true): HTMLElement[]
Public method validate()
/**
 * Validates selector
 * @param selector — selector text
 */
public static validate(selector: string): ValidationResult

where

type ValidationResult = {
    // true for valid selector, false for invalid one
    ok: boolean,
    // specified for invalid selector
    error: string | null,
};

Debugging extended selectors

ExtendedCss 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:

!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");

Now you can now use the ExtendedCss from global scope, and run its method query() as Document.querySelectorAll().

const selector = 'div.block:has=(.header:matches-css-after(content: Ads))';

// array of HTMLElement matched the `selector` will be returned
ExtendedCss.query(selector);

Projects using ExtendedCss

Browser compatibility

BrowserVersion
Chrome✅ 55
Firefox✅ 52
Edge✅ 80
Opera✅ 80
Safari✅ 11.1
Internet Explorer

FAQs

Package last updated on 15 Nov 2022

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc