Skip to content

Commit

Permalink
ditch :if() and :if-not() pseudo-classes. #151 AG-17744
Browse files Browse the repository at this point in the history
Squashed commit of the following:

commit ff17117414a5e50cb2a6fe2d4cd5672a9d50a0da
Merge: 9f278b7 77a0083
Author: Slava Leleka <v.leleka@adguard.com>
Date:   Thu Dec 1 18:59:31 2022 +0200

    Merge branch 'master' into feature/AG-17744

commit 9f278b7fefab0c88e8b3f82c226a4983ac83989c
Author: Slava Leleka <v.leleka@adguard.com>
Date:   Thu Dec 1 17:33:32 2022 +0200

    ditch 'if' and 'if-not' pseudo-classes
  • Loading branch information
slavaleleka committed Dec 1, 2022
1 parent 77a0083 commit 5bd9ca4
Show file tree
Hide file tree
Showing 5 changed files with 6 additions and 116 deletions.
9 changes: 1 addition & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ The idea of extended capabilities is an opportunity to match DOM elements with s
* [Extended capabilities](#extended-capabilities)
* [Limitations](#extended-css-limitations)
* [Pseudo-class :has()](#extended-css-has)
* [Pseudo-class :if-not()](#extended-css-if-not)
* [Pseudo-class :contains()](#extended-css-contains)
* [Pseudo-class :matches-css()](#extended-css-matches-css)
* [Pseudo-class :matches-attr()](#extended-css-matches-attr)
Expand Down Expand Up @@ -53,7 +52,7 @@ Draft CSS 4.0 specification describes [pseudo-class `:has`](https://www.w3.org/T

> Rules with `:has()` pseudo-class should use [native implementation of `:has()`](https://developer.mozilla.org/en-US/docs/Web/CSS/: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.
> Synonym `:-abp-has` is supported by ExtendedCss for better compatibility.
**Syntax**

Expand Down Expand Up @@ -126,12 +125,6 @@ Pseudo-class `:has()` selects the `target` elements that includes the elements t
> [Backward compatible syntax for `:has()`](#old-syntax-has) is supported but not recommended.

### <a id="extended-css-if-not"></a> 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.
### <a id="extended-css-contains"></a> 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.
Expand Down
4 changes: 0 additions & 4 deletions src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,11 @@ export const REMOVE_PSEUDO_MARKER = 'remove';

// relative:
export const HAS_PSEUDO_CLASS_MARKER = 'has';
export const IF_PSEUDO_CLASS_MARKER = 'if';
export const ABP_HAS_PSEUDO_CLASS_MARKER = '-abp-has';
export const HAS_PSEUDO_CLASS_MARKERS = [
HAS_PSEUDO_CLASS_MARKER,
IF_PSEUDO_CLASS_MARKER,
ABP_HAS_PSEUDO_CLASS_MARKER,
];
export const IF_NOT_PSEUDO_CLASS_MARKER = 'if-not';
export const IS_PSEUDO_CLASS_MARKER = 'is';
export const NOT_PSEUDO_CLASS_MARKER = 'not';

Expand All @@ -164,7 +161,6 @@ export const ABSOLUTE_PSEUDO_CLASSES = [

export const RELATIVE_PSEUDO_CLASSES = [
...HAS_PSEUDO_CLASS_MARKERS,
IF_NOT_PSEUDO_CLASS_MARKER,
IS_PSEUDO_CLASS_MARKER,
NOT_PSEUDO_CLASS_MARKER,
];
Expand Down
16 changes: 3 additions & 13 deletions src/selector/utils/query-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,17 @@ import {
ASTERISK,
ABSOLUTE_PSEUDO_CLASSES,
RELATIVE_PSEUDO_CLASSES,
IF_NOT_PSEUDO_CLASS_MARKER,
IS_PSEUDO_CLASS_MARKER,
NOT_PSEUDO_CLASS_MARKER,
NTH_ANCESTOR_PSEUDO_CLASS_MARKER,
UPWARD_PSEUDO_CLASS_MARKER,
XPATH_PSEUDO_CLASS_MARKER,
HAS_PSEUDO_CLASS_MARKER,
ABP_HAS_PSEUDO_CLASS_MARKER,
IF_PSEUDO_CLASS_MARKER,
} from '../../common/constants';

/**
* Calculated selector text which is needed to :has(), :if-not(), :is() and :not() pseudo-classes.
* Calculated selector text which is needed to :has(), :is() and :not() pseudo-classes.
* Contains calculated part (depends on the processed element)
* and value of RegularSelector which is next to selector by.
*
Expand Down Expand Up @@ -68,7 +66,7 @@ interface RelativePredicateArgsInterface {

/**
* Checks whether the element has all relative elements specified by pseudo-class arg.
* Used for :has() and :if-not() pseudo-classes.
* Used for :has() pseudo-class.
*
* @param argsData Relative pseudo-class helpers args data.
*/
Expand Down Expand Up @@ -104,7 +102,7 @@ const hasRelativesBySelectorList = (argsData: RelativePredicateArgsInterface): b
* e.g. 'a:has(> img)' -> `aNode.querySelectorAll(':scope > img')`.
*
* For 'any selector' as arg of relative simplicity should be set for all inner elements
* e.g. 'div:if-not(*)' -> `divNode.querySelectorAll(':scope *')`
* e.g. 'div:has(*)' -> `divNode.querySelectorAll(':scope *')`
* which means empty div with no child element.
*/
rootElement = element;
Expand Down Expand Up @@ -336,21 +334,13 @@ export const getByExtendedSelector = (
let relativePredicate: (e: HTMLElement) => boolean;
switch (pseudoName) {
case HAS_PSEUDO_CLASS_MARKER:
case IF_PSEUDO_CLASS_MARKER:
case ABP_HAS_PSEUDO_CLASS_MARKER:
relativePredicate = (element: HTMLElement) => hasRelativesBySelectorList({
element,
relativeSelectorList,
pseudoName,
});
break;
case IF_NOT_PSEUDO_CLASS_MARKER:
relativePredicate = (element: HTMLElement) => !hasRelativesBySelectorList({
element,
relativeSelectorList,
pseudoName,
});
break;
case IS_PSEUDO_CLASS_MARKER:
relativePredicate = (element: HTMLElement) => isAnyElementBySelectorList({
element,
Expand Down
25 changes: 0 additions & 25 deletions test/selector/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -576,31 +576,6 @@ describe('relative extended selectors', () => {
});
});

describe('if-not', () => {
const name = 'if-not';
const testsInputs = [
{
actual: 'div.banner:if-not(> span)',
expected: [
{ isRegular: true, value: 'div.banner' },
{ isRelative: true, name, value: '> span' },
],
},
{
// eslint-disable-next-line max-len
actual: 'header[data-test-id="header"] ~ div[class]:last-child > div[class] > div[class]:if-not(a[data-test-id="logo-link"])',
expected: [
{
isRegular: true,
value: 'header[data-test-id="header"] ~ div[class]:last-child > div[class] > div[class]',
},
{ isRelative: true, name, value: 'a[data-test-id="logo-link"]' },
],
},
];
test.each(testsInputs)('%s', (input) => expectSingleSelectorAstWithAnyChildren(input));
});

it('is', () => {
let actual;
let expected;
Expand Down
68 changes: 2 additions & 66 deletions test/selector/query-jsdom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1045,70 +1045,6 @@ describe('extended pseudo-classes', () => {
});
});

describe('if-not pseudo-class', () => {
beforeEach(() => {
document.body.innerHTML = `
<div id="root" level="0">
<div id="parent" level="1">
<p id="paragraph">text</p>
<a href="test_url"></a>
<div id="child" class="base" level="2">
<a href="/inner_url" id="anchor" class="banner"></a>
<div id="inner" class="base" level="3"></div>
<div id="inner2" class="base" level="3">
<span id="innerSpan" class="span" level="4"></span>
<p id="innerParagraph">text</p>
</div>
</div>
<div id="child2" class="base2" level="2">
<div class="base" level="3">
<p id="child2InnerParagraph">text</p>
</div>
</div>
</div>
</div>
`;
});
afterEach(() => {
document.body.innerHTML = '';
});

describe('if-not - ok', () => {
const successInputs = [
{ actual: '#parent > div:if-not(#innerParagraph)', expected: 'div#child2' },
{ actual: '#parent > div:if-not(div > span)', expected: 'div#child2' },
// child combinator in arg
{ actual: '#child div:if-not(> span)', expected: 'div#inner' },
{ actual: '#parent > div:if-not(> a + div + div)', expected: 'div#child2' },
{ actual: '#parent > div:if-not(> a ~ div[level="3"])', expected: 'div#child2' },
{ actual: '#child > :if-not(> span)', expected: '#anchor, #inner' },
// next sibling combinator in arg
{ actual: 'a:if-not(+ [level="2"])', expected: 'a#anchor' },
{ actual: 'a:if-not(+ [level="2"] + [level="2"])', expected: 'a#anchor' },
// subsequent-sibling combinator in arg
{ actual: 'a:if-not(~ .base2)', expected: 'a#anchor' },
{ actual: 'a:if-not(~ * + div[id][class] > [level="3"])', expected: 'a#anchor' },
// selector list as arg
{ actual: '#parent > div[id][class]:if-not(a, span)', expected: 'div#child2' },
// complex selector as base
{ actual: '#root div > div:if-not(*)', expected: 'div#inner' },
{ actual: '#root > * > #paragraph ~ div:if-not(a[href^="/inner"])', expected: 'div#child2' },
];
test.each(successInputs)('%s', (input) => expectSuccessInput(input));
});

describe('if-not - no arg or invalid selectors', () => {
const invalidArgErrorText = 'Invalid selector for :if-not() pseudo-class';
const toThrowInputs = [
{ selector: 'div:if-not()', error: 'Missing arg for :if-not() pseudo-class' },
{ selector: 'div:if-not(1)', error: invalidArgErrorText },
{ selector: '#parent > :if-not(..banner)', error: invalidArgErrorText },
{ selector: '#parent > :if-not(id="123") > .test-inner', error: invalidArgErrorText },
];
test.each(toThrowInputs)('%s', (input) => expectToThrowInput(input));
});
});

describe('is pseudo-class', () => {
beforeEach(() => {
document.body.innerHTML = `
Expand Down Expand Up @@ -1314,8 +1250,8 @@ describe('combined pseudo-classes', () => {
{ actual: '#root > :is(div:not([class])):has(#paragraph)', expected: 'div#parent' },
// has-text xpath
{ actual: 'p:has-text(/inner/):xpath(../../..)', expected: 'div#parent' },
// upward(number) if
{ actual: 'div[level="2"]:upward(1):if(#disabledInput)', expected: 'div#parent2' },
// upward(number) has
{ actual: 'div[level="2"]:upward(1):has(#disabledInput)', expected: 'div#parent2' },
// upward(selector) -abp-has
{ actual: 'div[level="2"]:upward(div[id]):-abp-has(#disabledInput)', expected: 'div#parent2' },
// upward(not)
Expand Down

0 comments on commit 5bd9ca4

Please sign in to comment.