From fbc7a0e0aefdfcf3548acf18ea59a76bea23cd4d Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 15 Aug 2023 10:07:42 -0400 Subject: [PATCH] Properly serialize CSS combinators according to position in selector Related issue: https://github.com/uBlockOrigin/uBlock-issues/issues/2778 Regression from: https://github.com/gorhill/uBlock/commit/bb41d9594fda7d06499a215992905e4e650ed30b The regression occurred because the modified code made the assumption that a leading combinator would never be preceded by whitespace, while the parser didn't prevent this. The parser has been fixed to ensure there is never a leading whitespace in a selector. --- src/js/static-filtering-parser.js | 33 ++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js index a84112c03..ffe5ed12e 100644 --- a/src/js/static-filtering-parser.js +++ b/src/js/static-filtering-parser.js @@ -3364,7 +3364,7 @@ class ExtSelectorCompiler { out.push(`.${data.name}`); break; case 'Combinator': - out.push(data.name === ' ' ? ' ' : ` ${data.name} `); + out.push(data.name); break; case 'Identifier': if ( this.reInvalidIdentifier.test(data.name) ) { return; } @@ -3436,6 +3436,28 @@ class ExtSelectorCompiler { return out.join(''); } + astAppendPart(part, out) { + const { data } = part; + switch ( data.type ) { + case 'Combinator': { + const s = this.astSerializePart(part); + if ( s === undefined ) { return false; } + if ( out.length === 0 ) { + if ( s !== ' ' ) { + out.push(s, ' '); + } + } else { + out.push(' '); + if ( s !== ' ' ) { + out.push(s, ' '); + } + } + break; + } + } + return true; + } + astSerialize(parts, plainCSS = true) { const out = []; for ( const part of parts ) { @@ -3443,7 +3465,6 @@ class ExtSelectorCompiler { switch ( data.type ) { case 'AttributeSelector': case 'ClassSelector': - case 'Combinator': case 'Identifier': case 'IdSelector': case 'Nth': @@ -3455,6 +3476,9 @@ class ExtSelectorCompiler { out.push(s); break; } + case 'Combinator': + if ( this.astAppendPart(part, out) === false ) { return; } + break; case 'Raw': if ( plainCSS ) { return; } out.push(this.astSerializePart(part)); @@ -3499,7 +3523,6 @@ class ExtSelectorCompiler { } case 'AttributeSelector': case 'ClassSelector': - case 'Combinator': case 'IdSelector': case 'PseudoClassSelector': case 'PseudoElementSelector': @@ -3509,6 +3532,10 @@ class ExtSelectorCompiler { prelude.push(component); break; } + case 'Combinator': { + if ( this.astAppendPart(part, prelude) === false ) { return; } + break; + } case 'ProceduralSelector': { if ( prelude.length !== 0 ) { let spath = prelude.join('');