diff --git a/src/js/static-ext-filtering.js b/src/js/static-ext-filtering.js index 161ac7696beed..87101558a417c 100644 --- a/src/js/static-ext-filtering.js +++ b/src/js/static-ext-filtering.js @@ -146,7 +146,7 @@ }; const compileProceduralSelector = (function() { - var reProceduralOperator = new RegExp([ + const reProceduralOperator = new RegExp([ '^(?:', [ '-abp-contains', @@ -165,17 +165,17 @@ ')\\(' ].join('')); - var reEscapeRegex = /[.*+?^${}()|[\]\\]/g, + const reEscapeRegex = /[.*+?^${}()|[\]\\]/g, reNeedScope = /^\s*[+>~]/, reIsDanglingSelector = /(?:[+>~]\s*|\s+)$/; - var lastProceduralSelector = '', - lastProceduralSelectorCompiled, - regexToRawValue = new Map(); + const regexToRawValue = new Map(); + let lastProceduralSelector = '', + lastProceduralSelectorCompiled; - var compileText = function(s) { - var regexDetails, - match = reParseRegexLiteral.exec(s); + const compileText = function(s) { + const match = reParseRegexLiteral.exec(s); + let regexDetails; if ( match !== null ) { regexDetails = match[1]; if ( isBadRegex(regexDetails) ) { return; } @@ -189,13 +189,13 @@ return regexDetails; }; - var compileCSSDeclaration = function(s) { - var name, value, regexDetails, - pos = s.indexOf(':'); + const compileCSSDeclaration = function(s) { + const pos = s.indexOf(':'); if ( pos === -1 ) { return; } - name = s.slice(0, pos).trim(); - value = s.slice(pos + 1).trim(); - var match = reParseRegexLiteral.exec(value); + const name = s.slice(0, pos).trim(); + const value = s.slice(pos + 1).trim(); + const match = reParseRegexLiteral.exec(value); + let regexDetails; if ( match !== null ) { regexDetails = match[1]; if ( isBadRegex(regexDetails) ) { return; } @@ -209,10 +209,7 @@ return { name: name, value: regexDetails }; }; - var compileConditionalSelector = function(s) { - if ( isValidCSSSelector(s) ) { - return { selector: s, tasks: [] }; - } + const compileConditionalSelector = function(s) { // https://github.com/AdguardTeam/ExtendedCss/issues/31#issuecomment-302391277 // Prepend `:scope ` if needed. if ( reNeedScope.test(s) ) { @@ -221,7 +218,17 @@ return compile(s); }; - var compileXpathExpression = function(s) { + const compileNotSelector = function(s) { + // https://github.com/uBlockOrigin/uBlock-issues/issues/341#issuecomment-447603588 + // Reject instances of :not() filters for which the argument is + // a valid CSS selector, otherwise we would be adversely + // changing the behavior of CSS4's :not(). + if ( isValidCSSSelector(s) === false ) { + return compileConditionalSelector(s); + } + }; + + const compileXpathExpression = function(s) { try { document.createExpression(s, null); } catch (e) { @@ -231,13 +238,13 @@ }; // https://github.com/gorhill/uBlock/issues/2793 - var normalizedOperators = new Map([ + const normalizedOperators = new Map([ [ ':-abp-contains', ':has-text' ], [ ':-abp-has', ':has' ], [ ':contains', ':has-text' ], ]); - var compileArgument = new Map([ + const compileArgument = new Map([ [ ':has', compileConditionalSelector ], [ ':has-text', compileText ], [ ':if', compileConditionalSelector ], @@ -245,7 +252,7 @@ [ ':matches-css', compileCSSDeclaration ], [ ':matches-css-after', compileCSSDeclaration ], [ ':matches-css-before', compileCSSDeclaration ], - [ ':not', compileConditionalSelector ], + [ ':not', compileNotSelector ], [ ':xpath', compileXpathExpression ] ]); @@ -256,15 +263,14 @@ // to other blockers. // The normalized string version is what is reported in the logger, // by design. - var decompile = function(compiled) { - var tasks = compiled.tasks; + const decompile = function(compiled) { + const tasks = compiled.tasks; if ( Array.isArray(tasks) === false ) { return compiled.selector; } - var raw = [ compiled.selector ], - value; - for ( var i = 0, n = tasks.length, task; i < n; i++ ) { - task = tasks[i]; + const raw = [ compiled.selector ]; + let value; + for ( let task of tasks ) { switch ( task[0] ) { case ':xpath': raw.push(task[0], '(', task[1], ')'); @@ -306,14 +312,15 @@ return raw.join(''); }; - var compile = function(raw) { + const compile = function(raw) { if ( raw === '' ) { return; } - var prefix = '', + let prefix = '', tasks = []; + let i = 0, + n = raw.length, + opPrefixBeg = 0; for (;;) { - var i = 0, - n = raw.length, - c, match; + let c, match; // Advance to next operator. while ( i < n ) { c = raw.charCodeAt(i++); @@ -323,14 +330,14 @@ } } if ( i === n ) { break; } - var opNameBeg = i - 1; - var opNameEnd = i + match[0].length - 1; + const opNameBeg = i - 1; + const opNameEnd = i + match[0].length - 1; i += match[0].length; // Find end of argument: first balanced closing parenthesis. // Note: unbalanced parenthesis can be used in a regex literal // when they are escaped using `\`. // TODO: need to handle quoted parentheses. - var pcnt = 1; + let pcnt = 1; while ( i < n ) { c = raw.charCodeAt(i++); if ( c === 0x5C /* '\\' */ ) { @@ -345,27 +352,39 @@ // Unbalanced parenthesis? An unbalanced parenthesis is fine // as long as the last character is a closing parenthesis. if ( pcnt !== 0 && c !== 0x29 ) { return; } + // https://github.com/uBlockOrigin/uBlock-issues/issues/341#issuecomment-447603588 + // Maybe that one operator is a valid CSS selector and if so, + // then consider it to be part of the prefix. If there is + // at least one task present, then we fail, as we do not + // support suffix CSS selectors. + // TODO: AdGuard does support suffix CSS selectors, so + // supporting this would increase compatibility with + // AdGuard filter lists. + if ( isValidCSSSelector(raw.slice(opNameBeg, i)) ) { + if ( opPrefixBeg !== 0 ) { return; } + continue; + } // Extract and remember operator details. - var operator = raw.slice(opNameBeg, opNameEnd); + let operator = raw.slice(opNameBeg, opNameEnd); operator = normalizedOperators.get(operator) || operator; - var args = raw.slice(opNameEnd + 1, i - 1); + let args = raw.slice(opNameEnd + 1, i - 1); args = compileArgument.get(operator)(args); if ( args === undefined ) { return; } - if ( tasks.length === 0 ) { + if ( opPrefixBeg === 0 ) { prefix = raw.slice(0, opNameBeg); - } else if ( opNameBeg !== 0 ) { + } else if ( opNameBeg !== opPrefixBeg ) { return; } tasks.push([ operator, args ]); - raw = raw.slice(i); if ( i === n ) { break; } + opPrefixBeg = i; } // No task found: then we have a CSS selector. // At least one task found: nothing should be left to parse. if ( tasks.length === 0 ) { prefix = raw; tasks = undefined; - } else if ( raw.length !== 0 ) { + } else if ( i < n ) { return; } // https://github.com/NanoAdblocker/NanoCore/issues/1#issuecomment-354394894 @@ -376,7 +395,7 @@ return { selector: prefix, tasks: tasks }; }; - var entryPoint = function(raw) { + const entryPoint = function(raw) { if ( raw === lastProceduralSelector ) { return lastProceduralSelectorCompiled; } @@ -391,7 +410,7 @@ }; entryPoint.reset = function() { - regexToRawValue = new Map(); + regexToRawValue.clear(); lastProceduralSelector = ''; lastProceduralSelectorCompiled = undefined; }; @@ -403,7 +422,18 @@ // Public API //-------------------------------------------------------------------------- - var api = {}; + const api = { + get acceptedCount() { + return µb.cosmeticFilteringEngine.acceptedCount + + µb.scriptletFilteringEngine.acceptedCount + + µb.htmlFilteringEngine.acceptedCount; + }, + get discardedCount() { + return µb.cosmeticFilteringEngine.discardedCount + + µb.scriptletFilteringEngine.discardedCount + + µb.htmlFilteringEngine.discardedCount; + } + }; //-------------------------------------------------------------------------- // Public classes @@ -464,12 +494,12 @@ }; api.HostnameBasedDB.prototype[Symbol.iterator] = (function() { - var Iter = function(db) { + const Iter = function(db) { this.mapIter = db.values(); this.arrayIter = undefined; }; Iter.prototype.next = function() { - var result; + let result; if ( this.arrayIter !== undefined ) { result = this.arrayIter.next(); if ( result.done === false ) { return result; } @@ -516,11 +546,11 @@ // Based on: FNV32a // http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-reference-source // The rest is custom, suited for uBlock. - let i1 = token.length; + const i1 = token.length; if ( i1 === 0 ) { return 0; } - let i2 = i1 >> 1; - let i4 = i1 >> 2; - let i8 = i1 >> 3; + const i2 = i1 >> 1; + const i4 = i1 >> 2; + const i8 = i1 >> 3; let hval = (0x811c9dc5 ^ token.charCodeAt(0)) >>> 0; hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24); hval >>>= 0; @@ -553,7 +583,7 @@ api.compileHostnameToHash = function(hostname) { let domain; if ( hostname.endsWith('.*') ) { - let pos = hostname.lastIndexOf('.', hostname.length - 3); + const pos = hostname.lastIndexOf('.', hostname.length - 3); domain = pos !== -1 ? hostname.slice(pos + 1) : hostname; } else { domain = µb.URI.domainFromHostnameNoCache(hostname); @@ -575,14 +605,14 @@ // Do not discard unknown pseudo-elements. api.compileSelector = (function() { - var reAfterBeforeSelector = /^(.+?)(::?after|::?before|::[a-z-]+)$/, - reStyleSelector = /^(.+?):style\((.+?)\)$/, - reStyleBad = /url\([^)]+\)/, - reExtendedSyntax = /\[-(?:abp|ext)-[a-z-]+=(['"])(?:.+?)(?:\1)\]/, - reExtendedSyntaxParser = /\[-(?:abp|ext)-([a-z-]+)=(['"])(.+?)\2\]/, - div = document.createElement('div'); - - var normalizedExtendedSyntaxOperators = new Map([ + const reAfterBeforeSelector = /^(.+?)(::?after|::?before|::[a-z-]+)$/; + const reStyleSelector = /^(.+?):style\((.+?)\)$/; + const reStyleBad = /url\([^)]+\)/; + const reExtendedSyntax = /\[-(?:abp|ext)-[a-z-]+=(['"])(?:.+?)(?:\1)\]/; + const reExtendedSyntaxParser = /\[-(?:abp|ext)-([a-z-]+)=(['"])(.+?)\2\]/; + const div = document.createElement('div'); + + const normalizedExtendedSyntaxOperators = new Map([ [ 'contains', ':has-text' ], [ 'has', ':has' ], [ 'matches-css', ':matches-css' ], @@ -590,7 +620,7 @@ [ 'matches-css-before', ':matches-css-before' ], ]); - var isValidStyleProperty = function(cssText) { + const isValidStyleProperty = function(cssText) { if ( reStyleBad.test(cssText) ) { return false; } div.style.cssText = cssText; if ( div.style.cssText === '' ) { return false; } @@ -598,8 +628,8 @@ return true; }; - var entryPoint = function(raw) { - var extendedSyntax = reExtendedSyntax.test(raw); + const entryPoint = function(raw) { + const extendedSyntax = reExtendedSyntax.test(raw); if ( isValidCSSSelector(raw) && extendedSyntax === false ) { return raw; } @@ -607,7 +637,7 @@ // We rarely reach this point -- majority of selectors are plain // CSS selectors. - var matches, operator; + let matches, operator; // Supported Adguard/ABP advanced selector syntax: will translate into // uBO's syntax before further processing. @@ -627,7 +657,7 @@ return entryPoint(raw); } - var selector = raw, + let selector = raw, pseudoclass, style; // `:style` selector? @@ -664,7 +694,7 @@ } // Procedural selector? - var compiled; + let compiled; if ( (compiled = compileProceduralSelector(raw)) ) { return compiled; } @@ -770,23 +800,6 @@ }; }; - Object.defineProperties(api, { - acceptedCount: { - get: function() { - return µb.cosmeticFilteringEngine.acceptedCount + - µb.scriptletFilteringEngine.acceptedCount + - µb.htmlFilteringEngine.acceptedCount; - } - }, - discardedCount: { - get: function() { - return µb.cosmeticFilteringEngine.discardedCount + - µb.scriptletFilteringEngine.discardedCount + - µb.htmlFilteringEngine.discardedCount; - } - } - }); - api.fromSelfie = function(selfie) { µb.cosmeticFilteringEngine.fromSelfie(selfie.cosmetic); µb.scriptletFilteringEngine.fromSelfie(selfie.scriptlets);