Skip to content

A javascript library that allows using extended CSS selectors (:has, :contains, etc)

License

Notifications You must be signed in to change notification settings

slavaleleka/ExtendedCss

 
 

Repository files navigation

Extended Css engine

Module for applying CSS styles with extended selection properties.

Extended capabilities

Pseudo-class :has()

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

Syntax

:has(selector)

Backward compatible syntax:

[-ext-has="selector"]

Supported synonyms for better compatibility: :-abp-has, :if.

Pseudo-class :has() selects the elements that includes the elements that fit to selector.

Examples

Selecting all div elements, which contain an element with the banner class:

<!-- HTML code -->
<div>Do not select this div</div>
<div>Select this div<span class="banner"></span></div>

Selector:

div:has(.banner)

Backward compatible syntax:

div[-ext-has=".banner"]

Pseudo-class :if-not()

This pseudo-class is basically a shortcut for :not(:has()). It is supported by ExtendedCss for better compatibility with some filters subscriptions, but it is not recommended to use it in AdGuard filters. The rationale 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. Regex flags are supported. Please note, that this pseudo-class uses textContent element property for matching (and not the innerHTML).

Syntax

// matching by plain text
:contains(text)

// matching by a regular expression
:contains(/regex/i)

Backward compatible syntax:

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

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

Supported synonyms for better compatibility: :-abp-contains, :has-text.

Examples

Selecting all div elements, which contain text banner:

<!-- HTML code -->
<div>Do not select this div</div>
<div id="selected">Select this div (banner)</div>
<div>Do not select this div <div class="banner"></div></div>

Selector:

// matching by plain text
div:contains(banner)

// matching by a regular expression
div:contains(/this .* banner/)

// also with regex flags
div:contains(/this .* banner/gi)

Backward compatible syntax:

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

// matching by a regular expression
div[-ext-contains="/this .* banner/"]

Please note that in this example only a div with id=selected will be selected, because the next element does not contain any text; banner is a part of code, not a text.

Pseudo-class :matches-css()

These pseudo-classes allow to select an element by its current style property. The work of this pseudo-class is based on using the window.getComputedStyle function.

Syntax

/* element style matching */
selector:matches-css(property-name ":" pattern)

/* ::before pseudo-element style matching */
selector:matches-css-before(property-name ":" pattern)

/* ::after pseudo-element style matching */
selector:matches-css-after(property-name ":" pattern)

Backward compatible syntax:

selector[-ext-matches-css="property-name ":" pattern"]
selector[-ext-matches-css-after="property-name ":" pattern"]
selector[-ext-matches-css-before="property-name ":" pattern"]
  • property-name — a name of CSS property to check the element for
  • pattern — 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 /regex/.

For non-regex patterns, (,),[,] must be unescaped, because we require escaping them in the filtering rules. For example, :matches-css(background-image:url(data:*)).

For regex patterns, " and \ should be escaped, because we manually escape those in extended-css-selector.js. For example: :matches-css(background-image: /^url\\(\\"data\\:\\image.+/).

Examples

Selecting all div elements which contain pseudo-class ::before with specified content:

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

Selector:

// Simple matching
div.banner:matches-css-before(content: block me)

// Regular expressions
div.banner:matches-css-before(content: /block me/)

Backward compatible syntax:

// Simple matching
div.banner[-ext-matches-css-before="content: block me"]

// Regular expressions
div.banner[-ext-matches-css-before="content: /block me/"]

Pseudo-class :matches-attr()

This pseudo-class allows to select an element by its attributes, especially if they are randomized.

Syntax

selector:matches-attr("name"[="value"])
  • name — attribute name OR regular expression for attribute name
  • value — optional, attribute value OR regular expression for attribute value

For regex patterns, " and \ should be escaped.

Examples

<!-- HTML code -->
<div id="targer1" class="matches-attr" ad-link="ssdgsg-banner_240x400"></div>

<div id="targer2" class="has matches-attr">
  <div data-sdfghlhw="adbanner"></div>
</div>

<div id="targer3-host" class="matches-attr has contains">
  <div id="not-targer3" wsdfg-unit012="click">
    <span>socials</span>
  </div>
  <div id="targer3" hrewq-unit094="click">
    <span>ads</span>
  </div>
</div>

<div id="targer4" class="matches-attr upward">
  <div >
    <inner-afhhw class="nyf5tx3" nt4f5be90delay="1000"></inner-afhhw>
  </div>
</div>
// for div#targer1
div:matches-attr("ad-link")

// for div#targer2
div:has(> div:matches-attr("/data-/"="adbanner"))

// for div#targer3
div:matches-attr("/-unit/"="/click/"):has(> span:contains(ads))

// for div#targer4
*[class]:matches-attr("/.{5,}delay$/"="/^[0-9]*$/"):upward(2)

Pseudo-class :matches-property()

This pseudo-class allows to select an element by its properties.

Syntax

selector:matches-property("name"[="value"])
  • name — property name OR regular expression for property name
  • value — optional, property value OR regular expression for property value

For regex patterns, " and \ should be escaped.

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

Examples

divProperties = {
    id: 1,
    check: {
        track: true,
        unit_2ksdf1: true,
    },
    memoizedProps: {
        key: null,
        tag: 12,
        _owner: {
            effectTag: 1,
            src: 'ad.com',
        },
    },
};
// element with such properties can be matched by any of such rules:

div:matches-property("check.track")

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

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

div:matches-property("memoizedProps._owner.src"="/ad/")
For filters maintainers

To check properties of specific element, do:

  1. Select the element on the page.
  2. Go to Console tab and run console.dir($0).

Pseudo-class :xpath()

This pseudo-class allows to select an element by evaluating a XPath expression.

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

The :xpath(...) pseudo is different than other pseudo-classes. Whereas all other operators are used to filter down a resultset of elements, the :xpath(...) operator can be used both to create a new resultset or filter down an existing one. For this reason, subject selector is optional. For example, an :xpath(...) operator could be used to create a new resultset consisting of all ancestors elements of a subject element, something not otherwise possible with either plain CSS selectors or other procedural operators.

Normally, a pseudo-class is applied to nodes selected by a selector. However, :xpath is special as the selector can be ommited. For any other pseudo-class that would mean "apply to ALL DOM nodes", but in case of :xpath it just means "apply me to the document", and that significantly slows elements selecting. That's why we convert #?#:xpath(...) rules for looking inside the body tag. Rules like #?#*:xpath(...) can still be used but we highly recommend you avoid it and specify the selector.

Syntax

[selector]:xpath(expression)
  • selector- optional, a plain CSS selector, or a Sizzle compatible selector
  • expression — a valid XPath expression

Examples

// Filtering results from selector
div:xpath(//*[@class="test-xpath-class"])
div:has-text(/test-xpath-content/):xpath(../../..)

// Use xpath only to select elements
facebook.com##:xpath(//div[@id="stream_pagelet"]//div[starts-with(@id,"hyperfeed_story_id_")][.//h6//span/text()="People You May Know"])

Pseudo-class :nth-ancestor()

This pseudo-class allows to lookup the nth ancestor relative to the currently selected node.

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

It is a low-overhead equivalent to :xpath(..[/..]*).

Syntax

selector:nth-ancestor(n)
  • selector — a plain CSS selector, or a Sizzle compatible selector.
  • n — positive number >= 1 and < 256, distance from the currently selected node.

Examples

div.test:nth-ancestor(4)

div:has-text(/test/):nth-ancestor(2)

Pseudo-class :upward()

This pseudo-class allows to lookup the ancestor relative to the currently selected node.

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

Syntax

/* selector parameter */
subjectSelector:upward(targetSelector)

/* number parameter */
subjectSelector:upward(n)
  • subjectSelector — a plain CSS selector, or a Sizzle compatible selector
  • targetSelector — a valid plain CSS selector
  • n — positive number >= 1 and < 256, distance from the currently selected node

Examples

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

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

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.

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

Syntax

! pseudo-class
selector:remove()

! pseudo-property
selector { remove: true; }
  • selector — a plain CSS selector, or a Sizzle compatible selector

Examples

div.inner:remove()
div:has(> div[ad-attr]):remove()
div:xpath(../..):remove()

div:contains(target text) { remove: true; }
div[class]:has(> a:not([id])) { remove: true; }

Please note, that all style properties will be ignored if :remove() pseudo-class or remove pseudo-property is used.

Pseudo-class :is()

This pseudo-class allown to match any element that can be selected by one of the selectors passed to :is(). If there is invalid selector passed, it will be passed and pseudo-class will deal with valid ones. Our implementation of matches-any pseudo-class https://developer.mozilla.org/en-US/docs/Web/CSS/:is

Syntax


:is(selectors)
  • selectors — list of plain CSS selector

Examples

#main :is(.header, .body, .footer) .banner-inner
:is(.div-inner, .div-inner2):contains(textmarker)

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 the "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; }

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:

(function() {
  var cssText = 'div.wrapper>div[-ext-has=".banner"] { display:none!important; }\n';
  cssText += 'div.wrapper>div[-ext-contains="some word"] { background:none!important; }';
  var extendedCss = new ExtendedCss({ cssText: cssText });
  extendedCss.apply();

  // Just an example of how to stop applying this extended CSS
  setTimeout(function() {
    extendedCss.dispose();
  }, 10 * 1000);
})();

Debugging extended selectors

To load ExtendedCss to a current page, copy and execute the following code in a browser console:

!function(E,x,t,C,s,s_){C=E.createElement(x),s=E.getElementsByTagName(x)[0],C.src=t,
C.onload=function(){alert('ExtCss loaded successfully')},s.parentNode.insertBefore(C,s)}
(document,'script','https://AdguardTeam.github.io/ExtendedCss/extended-css.min.js')

Alternative, install an "ExtendedCssDebugger" userscript: https://github.com/AdguardTeam/Userscripts/blob/master/extendedCssDebugger/extended-css.debugger.user.js

You can now use the ExtendedCss constructor in the global scope, and its method ExtendedCss.query as document.querySelectorAll.

var selectorText = "div.block[-ext-has='.header:matches-css-after(content: Anzeige)']";

ExtendedCss.query(selectorText) // returns an array of Elements matching selectorText

Projects using Extended Css

Test page

Link

About

A javascript library that allows using extended CSS selectors (:has, :contains, etc)

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript 94.5%
  • HTML 3.8%
  • CSS 1.7%