From aca7674bacb9858b375c55f996d10dfabebbdb5e Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 19 Jun 2024 19:06:53 -0400 Subject: [PATCH] Add `:matches-prop()` pseudo CSS operator `subject:matches-prop(arg)` Description: Allows to select an element by a property name (or chain of properties), and optionally the property value. Chainable: Yes. `subject`: Can be a plain CSS selector, or a procedural cosmetic filter. `arg`: A declaration in the form `chain=value`, where `chain` is a dot- separated string for the target property, and `value` is the optional property value to match. `value` can be literal text or literal regular expression. When no `value` is declared, the operator only tests for the presence of the target property Example: example.org##div:matches-prop(imanad) example.org##img:matches-prop(naturalWidth=160) --- src/js/contentscript-extra.js | 21 ++ src/js/static-filtering-parser.js | 509 +++++++++++++++--------------- 2 files changed, 276 insertions(+), 254 deletions(-) diff --git a/src/js/contentscript-extra.js b/src/js/contentscript-extra.js index 0c3104d91bf11..4d10bb8782464 100644 --- a/src/js/contentscript-extra.js +++ b/src/js/contentscript-extra.js @@ -173,6 +173,26 @@ class PSelectorMatchesPathTask extends PSelectorTask { } } +class PSelectorMatchesPropTask extends PSelectorTask { + constructor(task) { + super(); + this.props = task[1].attr.split('.'); + this.reValue = task[1].value !== '' + ? regexFromString(task[1].value, true) + : null; + } + transpose(node, output) { + let value = node; + for ( const prop of this.props ) { + if ( value === undefined ) { return; } + if ( value === null ) { return; } + value = value[prop]; + } + if ( this.reValue !== null && this.reValue.test(value) === false ) { return; } + output.push(node); + } +} + class PSelectorMinTextLengthTask extends PSelectorTask { constructor(task) { super(); @@ -461,6 +481,7 @@ PSelector.prototype.operatorToTaskMap = new Map([ [ 'matches-css-before', PSelectorMatchesCSSBeforeTask ], [ 'matches-media', PSelectorMatchesMediaTask ], [ 'matches-path', PSelectorMatchesPathTask ], + [ 'matches-prop', PSelectorMatchesPropTask ], [ 'min-text-length', PSelectorMinTextLengthTask ], [ 'not', PSelectorIfNotTask ], [ 'others', PSelectorOthersTask ], diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js index 48c5f62e7aaf1..e89f7b3a30ce5 100644 --- a/src/js/static-filtering-parser.js +++ b/src/js/static-filtering-parser.js @@ -19,12 +19,10 @@ Home: https://github.com/gorhill/uBlock */ -'use strict'; - /******************************************************************************/ -import Regex from '../lib/regexanalyzer/regex.js'; import * as cssTree from '../lib/csstree/css-tree.js'; +import Regex from '../lib/regexanalyzer/regex.js'; /******************************************************************************* * @@ -781,21 +779,21 @@ class DomainListIterator { let ready = false; while ( node !== 0 ) { switch ( this.parser.getNodeType(node) ) { - case NODE_TYPE_OPTION_VALUE_DOMAIN_RAW: - this.item.hn = ''; - this.item.not = false; - this.item.bad = this.parser.getNodeFlags(node, NODE_FLAG_ERROR) !== 0; - break; - case NODE_TYPE_OPTION_VALUE_NOT: - this.item.not = true; - break; - case NODE_TYPE_OPTION_VALUE_DOMAIN: - this.item.hn = this.parser.getNodeTransform(node); - this.value = this.item; - ready = true; - break; - default: - break; + case NODE_TYPE_OPTION_VALUE_DOMAIN_RAW: + this.item.hn = ''; + this.item.not = false; + this.item.bad = this.parser.getNodeFlags(node, NODE_FLAG_ERROR) !== 0; + break; + case NODE_TYPE_OPTION_VALUE_NOT: + this.item.not = true; + break; + case NODE_TYPE_OPTION_VALUE_DOMAIN: + this.item.hn = this.parser.getNodeTransform(node); + this.value = this.item; + ready = true; + break; + default: + break; } node = this.walker.next(); if ( ready ) { return this; } @@ -859,17 +857,17 @@ export class AstFilterParser { this.reInlineComment = /(?:\s+#).*?$/; this.reNetException = /^@@/; this.reNetAnchor = /(?:)\$[^,\w~]/; - this.reHnAnchoredPlainAscii = /^\|\|[0-9a-z%&,\-.\/:;=?_]+$/; + this.reHnAnchoredPlainAscii = /^\|\|[0-9a-z%&,\-./:;=?_]+$/; this.reHnAnchoredHostnameAscii = /^\|\|(?:[\da-z][\da-z_-]*\.)*[\da-z_-]*[\da-z]\^$/; this.reHnAnchoredHostnameUnicode = /^\|\|(?:[\p{L}\p{N}][\p{L}\p{N}\u{2d}]*\.)*[\p{L}\p{N}\u{2d}]*[\p{L}\p{N}]\^$/u; this.reHn3pAnchoredHostnameAscii = /^\|\|(?:[\da-z][\da-z_-]*\.)*[\da-z_-]*[\da-z]\^\$third-party$/; - this.rePlainAscii = /^[0-9a-z%&\-.\/:;=?_]{2,}$/; + this.rePlainAscii = /^[0-9a-z%&\-./:;=?_]{2,}$/; this.reNetHosts1 = /^127\.0\.0\.1 (?:[\da-z][\da-z_-]*\.)+[\da-z-]*[a-z]$/; this.reNetHosts2 = /^0\.0\.0\.0 (?:[\da-z][\da-z_-]*\.)+[\da-z-]*[a-z]$/; this.rePlainGenericCosmetic = /^##[.#][A-Za-z_][\w-]*$/; this.reHostnameAscii = /^(?:[\da-z][\da-z_-]*\.)*[\da-z][\da-z-]*[\da-z]$/; this.rePlainEntity = /^(?:[\da-z][\da-z_-]*\.)+\*$/; - this.reHostsSink = /^[\w%.:\[\]-]+\s+/; + this.reHostsSink = /^[\w%.:[\]-]+\s+/; this.reHostsRedirect = /(?:0\.0\.0\.0|broadcasthost|local|localhost(?:\.localdomain)?|ip6-\w+)(?:[^\w.-]|$)/; this.reNetOptionComma = /,(?:~?[13a-z-]+(?:=.*?)?|_+)(?:,|$)/; this.rePointlessLeftAnchor = /^\|\|?\*+/; @@ -886,8 +884,8 @@ export class AstFilterParser { this.rePreparseDirectiveIf = /^!#if /; this.rePreparseDirectiveAny = /^!#(?:else|endif|if |include )/; this.reURL = /\bhttps?:\/\/\S+/; - this.reHasPatternSpecialChars = /[\*\^]/; - this.rePatternAllSpecialChars = /[\*\^]+|[^\x00-\x7f]+/g; + this.reHasPatternSpecialChars = /[*^]/; + this.rePatternAllSpecialChars = /[*^]+|[^\x00-\x7f]+/g; // https://github.com/uBlockOrigin/uBlock-issues/issues/1146 // From https://codemirror.net/doc/manual.html#option_specialChars this.reHasInvalidChar = /[\x00-\x1F\x7F-\x9F\xAD\u061C\u200B-\u200F\u2028\u2029\uFEFF\uFFF9-\uFFFC]/; @@ -1315,218 +1313,218 @@ export class AstFilterParser { const hasValue = (flags & NODE_FLAG_OPTION_HAS_VALUE) !== 0; bad = false; realBad = false; switch ( type ) { - case NODE_TYPE_NET_OPTION_NAME_ALL: - realBad = isNegated || hasValue || modifierType !== 0; - break; - case NODE_TYPE_NET_OPTION_NAME_1P: - case NODE_TYPE_NET_OPTION_NAME_3P: - realBad = hasValue; - break; - case NODE_TYPE_NET_OPTION_NAME_BADFILTER: - badfilter = true; - /* falls through */ - case NODE_TYPE_NET_OPTION_NAME_NOOP: - realBad = isNegated || hasValue; - break; - case NODE_TYPE_NET_OPTION_NAME_CSS: - case NODE_TYPE_NET_OPTION_NAME_FONT: - case NODE_TYPE_NET_OPTION_NAME_IMAGE: - case NODE_TYPE_NET_OPTION_NAME_MEDIA: - case NODE_TYPE_NET_OPTION_NAME_OBJECT: - case NODE_TYPE_NET_OPTION_NAME_OTHER: - case NODE_TYPE_NET_OPTION_NAME_SCRIPT: - case NODE_TYPE_NET_OPTION_NAME_XHR: - realBad = hasValue; - if ( realBad ) { break; } - requestTypeCount += 1; - break; - case NODE_TYPE_NET_OPTION_NAME_CNAME: - realBad = isException === false || isNegated || hasValue; - if ( realBad ) { break; } - modifierType = type; - break; - case NODE_TYPE_NET_OPTION_NAME_CSP: - realBad = (hasValue || isException) === false || - modifierType !== 0 || - this.reBadCSP.test( - this.getNetOptionValue(NODE_TYPE_NET_OPTION_NAME_CSP) - ); - if ( realBad ) { break; } - modifierType = type; - break; - case NODE_TYPE_NET_OPTION_NAME_DENYALLOW: - realBad = isNegated || hasValue === false || - this.getBranchFromType(NODE_TYPE_NET_OPTION_NAME_FROM) === 0; - break; - case NODE_TYPE_NET_OPTION_NAME_DOC: - case NODE_TYPE_NET_OPTION_NAME_FRAME: - realBad = hasValue; - if ( realBad ) { break; } - docTypeCount += 1; - break; - case NODE_TYPE_NET_OPTION_NAME_EHIDE: - case NODE_TYPE_NET_OPTION_NAME_GHIDE: - case NODE_TYPE_NET_OPTION_NAME_SHIDE: - realBad = isNegated || hasValue || modifierType !== 0; - if ( realBad ) { break; } - behaviorTypeCount += 1; - unredirectableTypeCount += 1; - break; - case NODE_TYPE_NET_OPTION_NAME_EMPTY: - case NODE_TYPE_NET_OPTION_NAME_MP4: - realBad = isNegated || hasValue || modifierType !== 0; - if ( realBad ) { break; } - modifierType = type; - break; - case NODE_TYPE_NET_OPTION_NAME_FROM: - case NODE_TYPE_NET_OPTION_NAME_METHOD: - case NODE_TYPE_NET_OPTION_NAME_TO: - realBad = isNegated || hasValue === false; - break; - case NODE_TYPE_NET_OPTION_NAME_GENERICBLOCK: - bad = true; - realBad = isException === false || isNegated || hasValue; - break; - case NODE_TYPE_NET_OPTION_NAME_HEADER: - realBad = isNegated || hasValue === false; - break; - case NODE_TYPE_NET_OPTION_NAME_IMPORTANT: - realBad = isException || isNegated || hasValue; - break; - case NODE_TYPE_NET_OPTION_NAME_INLINEFONT: - case NODE_TYPE_NET_OPTION_NAME_INLINESCRIPT: - realBad = hasValue; - if ( realBad ) { break; } - modifierType = type; - unredirectableTypeCount += 1; - break; - case NODE_TYPE_NET_OPTION_NAME_MATCHCASE: - realBad = this.isRegexPattern() === false; - break; - case NODE_TYPE_NET_OPTION_NAME_PERMISSIONS: - realBad = modifierType !== 0 || - (hasValue || isException) === false || - this.reBadPP.test( - this.getNetOptionValue(NODE_TYPE_NET_OPTION_NAME_PERMISSIONS) - ); - if ( realBad ) { break; } - modifierType = type; - break; - case NODE_TYPE_NET_OPTION_NAME_PING: - case NODE_TYPE_NET_OPTION_NAME_WEBSOCKET: - realBad = hasValue; - if ( realBad ) { break; } - requestTypeCount += 1; - unredirectableTypeCount += 1; - break; - case NODE_TYPE_NET_OPTION_NAME_POPUNDER: - case NODE_TYPE_NET_OPTION_NAME_POPUP: - realBad = hasValue; - if ( realBad ) { break; } - abstractTypeCount += 1; - unredirectableTypeCount += 1; - break; - case NODE_TYPE_NET_OPTION_NAME_REDIRECT: - case NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE: - case NODE_TYPE_NET_OPTION_NAME_REPLACE: - case NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM: - realBad = isNegated || (isException || hasValue) === false || - modifierType !== 0; - if ( realBad ) { break; } - modifierType = type; - break; - case NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM: - realBad = isNegated || modifierType !== 0; - if ( realBad ) { break; } - modifierType = type; - break; - case NODE_TYPE_NET_OPTION_NAME_STRICT1P: - case NODE_TYPE_NET_OPTION_NAME_STRICT3P: - realBad = isNegated || hasValue; - break; - case NODE_TYPE_NET_OPTION_NAME_UNKNOWN: - this.astError = AST_ERROR_OPTION_UNKNOWN; - realBad = true; - break; - case NODE_TYPE_NET_OPTION_NAME_WEBRTC: - realBad = true; - break; - case NODE_TYPE_NET_PATTERN_RAW: - realBad = this.hasOptions() === false && - this.getNetPattern().length <= 1; - break; - default: - break; - } - if ( bad || realBad ) { - this.addNodeFlags(targetNode, NODE_FLAG_ERROR); - } - if ( realBad ) { - this.addFlags(AST_FLAG_HAS_ERROR); - } - } - const requiresTrustedSource = ( ) => - this.options.trustedSource !== true && - isException === false && badfilter === false; - switch ( modifierType ) { + case NODE_TYPE_NET_OPTION_NAME_ALL: + realBad = isNegated || hasValue || modifierType !== 0; + break; + case NODE_TYPE_NET_OPTION_NAME_1P: + case NODE_TYPE_NET_OPTION_NAME_3P: + realBad = hasValue; + break; + case NODE_TYPE_NET_OPTION_NAME_BADFILTER: + badfilter = true; + /* falls through */ + case NODE_TYPE_NET_OPTION_NAME_NOOP: + realBad = isNegated || hasValue; + break; + case NODE_TYPE_NET_OPTION_NAME_CSS: + case NODE_TYPE_NET_OPTION_NAME_FONT: + case NODE_TYPE_NET_OPTION_NAME_IMAGE: + case NODE_TYPE_NET_OPTION_NAME_MEDIA: + case NODE_TYPE_NET_OPTION_NAME_OBJECT: + case NODE_TYPE_NET_OPTION_NAME_OTHER: + case NODE_TYPE_NET_OPTION_NAME_SCRIPT: + case NODE_TYPE_NET_OPTION_NAME_XHR: + realBad = hasValue; + if ( realBad ) { break; } + requestTypeCount += 1; + break; case NODE_TYPE_NET_OPTION_NAME_CNAME: - realBad = abstractTypeCount || behaviorTypeCount || requestTypeCount; + realBad = isException === false || isNegated || hasValue; + if ( realBad ) { break; } + modifierType = type; break; case NODE_TYPE_NET_OPTION_NAME_CSP: - case NODE_TYPE_NET_OPTION_NAME_PERMISSIONS: - realBad = abstractTypeCount || behaviorTypeCount || requestTypeCount; + realBad = (hasValue || isException) === false || + modifierType !== 0 || + this.reBadCSP.test( + this.getNetOptionValue(NODE_TYPE_NET_OPTION_NAME_CSP) + ); + if ( realBad ) { break; } + modifierType = type; + break; + case NODE_TYPE_NET_OPTION_NAME_DENYALLOW: + realBad = isNegated || hasValue === false || + this.getBranchFromType(NODE_TYPE_NET_OPTION_NAME_FROM) === 0; + break; + case NODE_TYPE_NET_OPTION_NAME_DOC: + case NODE_TYPE_NET_OPTION_NAME_FRAME: + realBad = hasValue; + if ( realBad ) { break; } + docTypeCount += 1; + break; + case NODE_TYPE_NET_OPTION_NAME_EHIDE: + case NODE_TYPE_NET_OPTION_NAME_GHIDE: + case NODE_TYPE_NET_OPTION_NAME_SHIDE: + realBad = isNegated || hasValue || modifierType !== 0; + if ( realBad ) { break; } + behaviorTypeCount += 1; + unredirectableTypeCount += 1; + break; + case NODE_TYPE_NET_OPTION_NAME_EMPTY: + case NODE_TYPE_NET_OPTION_NAME_MP4: + realBad = isNegated || hasValue || modifierType !== 0; + if ( realBad ) { break; } + modifierType = type; + break; + case NODE_TYPE_NET_OPTION_NAME_FROM: + case NODE_TYPE_NET_OPTION_NAME_METHOD: + case NODE_TYPE_NET_OPTION_NAME_TO: + realBad = isNegated || hasValue === false; + break; + case NODE_TYPE_NET_OPTION_NAME_GENERICBLOCK: + bad = true; + realBad = isException === false || isNegated || hasValue; + break; + case NODE_TYPE_NET_OPTION_NAME_HEADER: + realBad = isNegated || hasValue === false; + break; + case NODE_TYPE_NET_OPTION_NAME_IMPORTANT: + realBad = isException || isNegated || hasValue; break; case NODE_TYPE_NET_OPTION_NAME_INLINEFONT: case NODE_TYPE_NET_OPTION_NAME_INLINESCRIPT: - realBad = behaviorTypeCount; + realBad = hasValue; + if ( realBad ) { break; } + modifierType = type; + unredirectableTypeCount += 1; break; - case NODE_TYPE_NET_OPTION_NAME_EMPTY: - realBad = abstractTypeCount || behaviorTypeCount; + case NODE_TYPE_NET_OPTION_NAME_MATCHCASE: + realBad = this.isRegexPattern() === false; break; - case NODE_TYPE_NET_OPTION_NAME_MEDIA: - case NODE_TYPE_NET_OPTION_NAME_MP4: - realBad = abstractTypeCount || behaviorTypeCount || docTypeCount || requestTypeCount; + case NODE_TYPE_NET_OPTION_NAME_PERMISSIONS: + realBad = modifierType !== 0 || + (hasValue || isException) === false || + this.reBadPP.test( + this.getNetOptionValue(NODE_TYPE_NET_OPTION_NAME_PERMISSIONS) + ); + if ( realBad ) { break; } + modifierType = type; break; - case NODE_TYPE_NET_OPTION_NAME_REDIRECT: - case NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE: { - realBad = abstractTypeCount || behaviorTypeCount || unredirectableTypeCount; + case NODE_TYPE_NET_OPTION_NAME_PING: + case NODE_TYPE_NET_OPTION_NAME_WEBSOCKET: + realBad = hasValue; + if ( realBad ) { break; } + requestTypeCount += 1; + unredirectableTypeCount += 1; break; - } - case NODE_TYPE_NET_OPTION_NAME_REPLACE: { - realBad = abstractTypeCount || behaviorTypeCount || unredirectableTypeCount; + case NODE_TYPE_NET_OPTION_NAME_POPUNDER: + case NODE_TYPE_NET_OPTION_NAME_POPUP: + realBad = hasValue; if ( realBad ) { break; } - if ( requiresTrustedSource() ) { - this.astError = AST_ERROR_UNTRUSTED_SOURCE; - realBad = true; - break; - } - const value = this.getNetOptionValue(NODE_TYPE_NET_OPTION_NAME_REPLACE); - if ( parseReplaceValue(value) === undefined ) { - this.astError = AST_ERROR_OPTION_BADVALUE; - realBad = true; - } + abstractTypeCount += 1; + unredirectableTypeCount += 1; break; - } - case NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM: { - realBad = abstractTypeCount || behaviorTypeCount || unredirectableTypeCount; + case NODE_TYPE_NET_OPTION_NAME_REDIRECT: + case NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE: + case NODE_TYPE_NET_OPTION_NAME_REPLACE: + case NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM: + realBad = isNegated || (isException || hasValue) === false || + modifierType !== 0; if ( realBad ) { break; } - if ( requiresTrustedSource() ) { - this.astError = AST_ERROR_UNTRUSTED_SOURCE; - realBad = true; - break; - } - const value = this.getNetOptionValue(NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM); - if ( value !== '' && parseReplaceValue(value) === undefined ) { - this.astError = AST_ERROR_OPTION_BADVALUE; - realBad = true; - } + modifierType = type; break; - } case NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM: - realBad = abstractTypeCount || behaviorTypeCount; + realBad = isNegated || modifierType !== 0; + if ( realBad ) { break; } + modifierType = type; + break; + case NODE_TYPE_NET_OPTION_NAME_STRICT1P: + case NODE_TYPE_NET_OPTION_NAME_STRICT3P: + realBad = isNegated || hasValue; + break; + case NODE_TYPE_NET_OPTION_NAME_UNKNOWN: + this.astError = AST_ERROR_OPTION_UNKNOWN; + realBad = true; + break; + case NODE_TYPE_NET_OPTION_NAME_WEBRTC: + realBad = true; + break; + case NODE_TYPE_NET_PATTERN_RAW: + realBad = this.hasOptions() === false && + this.getNetPattern().length <= 1; break; default: break; + } + if ( bad || realBad ) { + this.addNodeFlags(targetNode, NODE_FLAG_ERROR); + } + if ( realBad ) { + this.addFlags(AST_FLAG_HAS_ERROR); + } + } + const requiresTrustedSource = ( ) => + this.options.trustedSource !== true && + isException === false && badfilter === false; + switch ( modifierType ) { + case NODE_TYPE_NET_OPTION_NAME_CNAME: + realBad = abstractTypeCount || behaviorTypeCount || requestTypeCount; + break; + case NODE_TYPE_NET_OPTION_NAME_CSP: + case NODE_TYPE_NET_OPTION_NAME_PERMISSIONS: + realBad = abstractTypeCount || behaviorTypeCount || requestTypeCount; + break; + case NODE_TYPE_NET_OPTION_NAME_INLINEFONT: + case NODE_TYPE_NET_OPTION_NAME_INLINESCRIPT: + realBad = behaviorTypeCount; + break; + case NODE_TYPE_NET_OPTION_NAME_EMPTY: + realBad = abstractTypeCount || behaviorTypeCount; + break; + case NODE_TYPE_NET_OPTION_NAME_MEDIA: + case NODE_TYPE_NET_OPTION_NAME_MP4: + realBad = abstractTypeCount || behaviorTypeCount || docTypeCount || requestTypeCount; + break; + case NODE_TYPE_NET_OPTION_NAME_REDIRECT: + case NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE: { + realBad = abstractTypeCount || behaviorTypeCount || unredirectableTypeCount; + break; + } + case NODE_TYPE_NET_OPTION_NAME_REPLACE: { + realBad = abstractTypeCount || behaviorTypeCount || unredirectableTypeCount; + if ( realBad ) { break; } + if ( requiresTrustedSource() ) { + this.astError = AST_ERROR_UNTRUSTED_SOURCE; + realBad = true; + break; + } + const value = this.getNetOptionValue(NODE_TYPE_NET_OPTION_NAME_REPLACE); + if ( parseReplaceValue(value) === undefined ) { + this.astError = AST_ERROR_OPTION_BADVALUE; + realBad = true; + } + break; + } + case NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM: { + realBad = abstractTypeCount || behaviorTypeCount || unredirectableTypeCount; + if ( realBad ) { break; } + if ( requiresTrustedSource() ) { + this.astError = AST_ERROR_UNTRUSTED_SOURCE; + realBad = true; + break; + } + const value = this.getNetOptionValue(NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM); + if ( value !== '' && parseReplaceValue(value) === undefined ) { + this.astError = AST_ERROR_OPTION_BADVALUE; + realBad = true; + } + break; + } + case NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM: + realBad = abstractTypeCount || behaviorTypeCount; + break; + default: + break; } if ( realBad ) { const targetNode = this.getBranchFromType(modifierType); @@ -2061,15 +2059,15 @@ export class AstFilterParser { parentBeg + optionEnd ); switch ( nodeOptionType ) { - case NODE_TYPE_NET_OPTION_NAME_DENYALLOW: - this.linkDown(next, this.parseDomainList(next, '|'), 0b00000); - break; - case NODE_TYPE_NET_OPTION_NAME_FROM: - case NODE_TYPE_NET_OPTION_NAME_TO: - this.linkDown(next, this.parseDomainList(next, '|', 0b11010)); - break; - default: - break; + case NODE_TYPE_NET_OPTION_NAME_DENYALLOW: + this.linkDown(next, this.parseDomainList(next, '|'), 0b00000); + break; + case NODE_TYPE_NET_OPTION_NAME_FROM: + case NODE_TYPE_NET_OPTION_NAME_TO: + this.linkDown(next, this.parseDomainList(next, '|', 0b11010)); + break; + default: + break; } this.linkRight(prev, next); return this.throwHeadNode(head); @@ -2284,27 +2282,27 @@ export class AstFilterParser { if ( (flags & NODE_FLAG_ERROR) !== 0 ) { continue; } realBad = false; switch ( type ) { - case NODE_TYPE_EXT_PATTERN_RESPONSEHEADER: { - const pattern = this.getNodeString(targetNode); - realBad = - pattern !== '' && removableHTTPHeaders.has(pattern) === false || - pattern === '' && isException === false; - break; - } - case NODE_TYPE_EXT_PATTERN_SCRIPTLET_TOKEN: { - if ( this.interactive !== true ) { break; } - if ( isException ) { break; } - const { trustedSource, trustedScriptletTokens } = this.options; - if ( trustedScriptletTokens instanceof Set === false ) { break; } - const token = this.getNodeString(targetNode); - if ( trustedScriptletTokens.has(token) && trustedSource !== true ) { - this.astError = AST_ERROR_UNTRUSTED_SOURCE; - realBad = true; - } - break; + case NODE_TYPE_EXT_PATTERN_RESPONSEHEADER: { + const pattern = this.getNodeString(targetNode); + realBad = + pattern !== '' && removableHTTPHeaders.has(pattern) === false || + pattern === '' && isException === false; + break; + } + case NODE_TYPE_EXT_PATTERN_SCRIPTLET_TOKEN: { + if ( this.interactive !== true ) { break; } + if ( isException ) { break; } + const { trustedSource, trustedScriptletTokens } = this.options; + if ( trustedScriptletTokens instanceof Set === false ) { break; } + const token = this.getNodeString(targetNode); + if ( trustedScriptletTokens.has(token) && trustedSource !== true ) { + this.astError = AST_ERROR_UNTRUSTED_SOURCE; + realBad = true; } - default: - break; + break; + } + default: + break; } if ( realBad ) { this.addNodeFlags(targetNode, NODE_FLAG_ERROR); @@ -2420,7 +2418,7 @@ export class AstFilterParser { parentBeg + argsEnd ); this.linkDown(next, this.parseExtPatternScriptletArglist(next)); - prev = this.linkRight(prev, next); + this.linkRight(prev, next); return this.throwHeadNode(head); } @@ -2474,12 +2472,12 @@ export class AstFilterParser { const walker = this.getWalker(root); for ( let node = walker.next(); node !== 0; node = walker.next() ) { switch ( this.getNodeType(node) ) { - case NODE_TYPE_EXT_PATTERN_SCRIPTLET_TOKEN: - case NODE_TYPE_EXT_PATTERN_SCRIPTLET_ARG: - args.push(this.getNodeTransform(node)); - break; - default: - break; + case NODE_TYPE_EXT_PATTERN_SCRIPTLET_TOKEN: + case NODE_TYPE_EXT_PATTERN_SCRIPTLET_ARG: + args.push(this.getNodeTransform(node)); + break; + default: + break; } } walker.dispose(); @@ -3206,6 +3204,7 @@ class ExtSelectorCompiler { 'matches-css-before', 'matches-media', 'matches-path', + 'matches-prop', 'min-text-length', 'others', 'shadow', @@ -3842,6 +3841,7 @@ class ExtSelectorCompiler { case 'if-not': return this.compileSelector(arg); case 'matches-attr': + case 'matches-prop': return this.compileMatchAttrArgument(arg); case 'matches-css': return this.compileCSSDeclaration(arg); @@ -4037,7 +4037,7 @@ class ExtSelectorCompiler { compileAttrList(s) { if ( s === '' ) { return s; } - const attrs = s.split('\s*,\s*'); + const attrs = s.split(/\s*,\s*/); const out = []; for ( const attr of attrs ) { if ( attr !== '' ) { @@ -4075,6 +4075,7 @@ export const proceduralOperatorTokens = new Map([ [ 'matches-css', 0b11 ], [ 'matches-media', 0b11 ], [ 'matches-path', 0b11 ], + [ 'matches-prop', 0b11 ], [ 'min-text-length', 0b01 ], [ 'not', 0b01 ], [ 'nth-ancestor', 0b00 ],