Fix use of sibling-related CSS syntax at prefix position

Related discussion:
- https://www.reddit.com/r/uBlockOrigin/comments/c6iem5/
This commit is contained in:
Raymond Hill 2019-06-29 14:07:54 -04:00
parent 3a8608b49a
commit c1bdc123f2
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
5 changed files with 43 additions and 31 deletions

View file

@ -163,8 +163,8 @@
</div> </div>
<div id="a17" class="tile"> <div id="a17" class="tile">
<div class="pass"><div class="fail"></div><a></a><b></b></div> <div class="pass"><div class="fail"></div><a><b></b></a></div>
<code>#pcf #a17 .fail:has(~ b)</code> <code>#pcf #a17 .fail:has(~ a:has(b))</code>
</div> </div>
</div> </div>

View file

@ -128,18 +128,18 @@
<code>^#phf #a11 .pass > a:has(b) + .fail:has(b)</code> <code>^#phf #a11 .pass > a:has(b) + .fail:has(b)</code>
</div> </div>
</div>
<div id="a12" class="tile"> <div id="a12" class="tile">
<div class="pass"><div class="fail"></div><a></a><b></b></div> <div class="pass"><div class="fail"></div><a></a><b></b></div>
<code>^#phf #a12 .fail:has(+ a)</code> <code>^#phf #a12 .fail:has(+ a)</code>
</div> </div>
<div id="a13" class="tile"> <div id="a13" class="tile">
<div class="pass"><div class="fail"></div><a></a><b></b></div> <div class="pass"><div class="fail"></div><a><b></b></a></div>
<code>^#phf #a13 .fail:has(~ b)</code> <code>^#phf #a13 .fail:has(~ a:has(b))</code>
</div> </div>
</div>
<script> <script>
const hostname = self.location.hostname; const hostname = self.location.hostname;
const filters = []; const filters = [];

View file

@ -660,10 +660,8 @@ vAPI.DOMFilterer = (function() {
} }
prime(input) { prime(input) {
const root = input || document; const root = input || document;
if ( this.selector !== '' ) { if ( this.selector === '' ) { return [ root ]; }
return root.querySelectorAll(this.selector); return root.querySelectorAll(this.selector);
}
return [ root ];
} }
exec(input) { exec(input) {
let nodes = this.prime(input); let nodes = this.prime(input);

View file

@ -169,10 +169,8 @@
} }
prime(input) { prime(input) {
const root = input || docRegister; const root = input || docRegister;
if ( this.selector !== '' ) { if ( this.selector === '' ) { return [ root ]; }
return root.querySelectorAll(this.selector); return root.querySelectorAll(this.selector);
}
return [ root ];
} }
exec(input) { exec(input) {
if ( this.invalid ) { return []; } if ( this.invalid ) { return []; }

View file

@ -68,9 +68,9 @@
parsed.suffix = ''; parsed.suffix = '';
}; };
const isValidCSSSelector = (function() { const isValidCSSSelector = (( ) => {
var div = document.createElement('div'), const div = document.createElement('div');
matchesFn; let matchesFn;
// Keep in mind: // Keep in mind:
// https://github.com/gorhill/uBlock/issues/693 // https://github.com/gorhill/uBlock/issues/693
// https://github.com/gorhill/uBlock/issues/1955 // https://github.com/gorhill/uBlock/issues/1955
@ -95,11 +95,11 @@
} }
// Quick regex-based validation -- most cosmetic filters are of the // Quick regex-based validation -- most cosmetic filters are of the
// simple form and in such case a regex is much faster. // simple form and in such case a regex is much faster.
var reSimple = /^[#.][\w-]+$/; const reSimple = /^[#.][\w-]+$/;
return function(s) { return s => {
if ( reSimple.test(s) ) { return true; } if ( reSimple.test(s) ) { return true; }
try { try {
matchesFn(s + ', ' + s + ':not(#foo)'); matchesFn(`${s}, ${s}:not(#foo)`);
} catch (ex) { } catch (ex) {
return false; return false;
} }
@ -152,7 +152,7 @@
return hostnames; return hostnames;
}; };
const compileProceduralSelector = (function() { const compileProceduralSelector = (( ) => {
const reProceduralOperator = new RegExp([ const reProceduralOperator = new RegExp([
'^(?:', '^(?:',
[ [
@ -178,8 +178,9 @@
const reEatBackslashes = /\\([()])/g; const reEatBackslashes = /\\([()])/g;
const reEscapeRegex = /[.*+?^${}()|[\]\\]/g; const reEscapeRegex = /[.*+?^${}()|[\]\\]/g;
const reNeedScope = /^\s*[+>~]/; const reNeedScope = /^\s*>/;
const reIsDanglingSelector = /(?:[+>~]\s*|\s+)$/; const reIsDanglingSelector = /[+>~\s]\s*$/;
const reIsSiblingSelector = /^\s*[+~]/;
const regexToRawValue = new Map(); const regexToRawValue = new Map();
let lastProceduralSelector = '', let lastProceduralSelector = '',
@ -226,9 +227,9 @@
const compileConditionalSelector = function(s) { const compileConditionalSelector = function(s) {
// https://github.com/AdguardTeam/ExtendedCss/issues/31#issuecomment-302391277 // https://github.com/AdguardTeam/ExtendedCss/issues/31#issuecomment-302391277
// Prepend `:scope ` if needed. // Prepend `:scope ` if needed.
if ( reNeedScope.test(s) ) { if ( reNeedScope.test(s) ) {
s = ':scope ' + s; s = `:scope ${s}`;
} }
return compile(s); return compile(s);
}; };
@ -367,7 +368,7 @@
return raw.join(''); return raw.join('');
}; };
const compile = function(raw) { const compile = function(raw, root = false) {
if ( raw === '' ) { return; } if ( raw === '' ) { return; }
let prefix = '', let prefix = '',
tasks = []; tasks = [];
@ -436,16 +437,31 @@
// At least one task found: nothing should be left to parse. // At least one task found: nothing should be left to parse.
if ( tasks.length === 0 ) { if ( tasks.length === 0 ) {
prefix = raw; prefix = raw;
tasks = undefined;
} else if ( opPrefixBeg < n ) { } else if ( opPrefixBeg < n ) {
const spath = compileSpathExpression(raw.slice(opPrefixBeg)); const spath = compileSpathExpression(raw.slice(opPrefixBeg));
if ( spath === undefined ) { return; } if ( spath === undefined ) { return; }
tasks.push([ ':spath', spath ]); tasks.push([ ':spath', spath ]);
} }
// https://github.com/NanoAdblocker/NanoCore/issues/1#issuecomment-354394894 // https://github.com/NanoAdblocker/NanoCore/issues/1#issuecomment-354394894
// https://www.reddit.com/r/uBlockOrigin/comments/c6iem5/
// Convert sibling-selector prefix into :spath operator, but
// only if context is not the root.
if ( prefix !== '' ) { if ( prefix !== '' ) {
if ( reIsDanglingSelector.test(prefix) ) { prefix += '*'; } if ( reIsDanglingSelector.test(prefix) ) { prefix += '*'; }
if ( isValidCSSSelector(prefix) === false ) { return; } if ( isValidCSSSelector(prefix) === false ) {
if (
root ||
reIsSiblingSelector.test(prefix) === false ||
compileSpathExpression(prefix) === undefined
) {
return;
}
tasks.unshift([ ':spath', prefix ]);
prefix = '';
}
}
if ( tasks.length === 0 ) {
tasks = undefined;
} }
return { selector: prefix, tasks: tasks }; return { selector: prefix, tasks: tasks };
}; };
@ -455,7 +471,7 @@
return lastProceduralSelectorCompiled; return lastProceduralSelectorCompiled;
} }
lastProceduralSelector = raw; lastProceduralSelector = raw;
let compiled = compile(raw); let compiled = compile(raw, true);
if ( compiled !== undefined ) { if ( compiled !== undefined ) {
compiled.raw = decompile(compiled); compiled.raw = decompile(compiled);
compiled = JSON.stringify(compiled); compiled = JSON.stringify(compiled);
@ -717,8 +733,8 @@
} }
// Procedural selector? // Procedural selector?
let compiled; const compiled = compileProceduralSelector(raw);
if ( (compiled = compileProceduralSelector(raw)) ) { if ( compiled !== undefined ) {
return compiled; return compiled;
} }
}; };