Prevent highly generic cosmetic filters from affecting html/body elements

Related issue:
- https://github.com/uBlockOrigin/uBlock-issues/issues/1692
This commit is contained in:
Raymond Hill 2022-01-12 10:11:49 -05:00
parent 0bc0af9d8d
commit 7c8aec250f
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
4 changed files with 37 additions and 23 deletions

View File

@ -676,6 +676,11 @@ vAPI.DOMFilterer = class {
getAllExceptionSelectors() {
return this.exceptions.join(',\n');
}
unwrapSelector(s) {
const match = /^:is\((.+)\):not\(html,body\)\/\*hg\*\/$/.exec(s);
return match !== null ? match[1] : s;
}
};
/******************************************************************************/

View File

@ -227,13 +227,11 @@ const FilterContainer = function() {
this.highlyGeneric.simple = {
canonical: 'highGenericHideSimple',
dict: new Set(),
str: '',
mru: new µb.MRUCache(16)
};
this.highlyGeneric.complex = {
canonical: 'highGenericHideComplex',
dict: new Set(),
str: '',
mru: new µb.MRUCache(16)
};
@ -281,11 +279,14 @@ FilterContainer.prototype.reset = function() {
// highly generic selectors sets
this.highlyGeneric.simple.dict.clear();
this.highlyGeneric.simple.str = '';
this.highlyGeneric.simple.mru.reset();
this.highlyGeneric.complex.dict.clear();
this.highlyGeneric.complex.str = '';
this.highlyGeneric.complex.mru.reset();
// https://developer.mozilla.org/en-US/docs/Web/CSS/:is#browser_compatibility
this.cssIs =
vAPI.webextFlavor.soup.has('firefox') && vAPI.webextFlavor.major >= 84 ||
vAPI.webextFlavor.soup.has('chromium') && vAPI.webextFlavor.major >= 88;
};
/******************************************************************************/
@ -300,9 +301,7 @@ FilterContainer.prototype.freeze = function() {
this.lowlyGeneric.cl.simple.size !== 0 ||
this.lowlyGeneric.cl.complex.size !== 0;
this.highlyGeneric.simple.str = Array.from(this.highlyGeneric.simple.dict).join(',\n');
this.highlyGeneric.simple.mru.reset();
this.highlyGeneric.complex.str = Array.from(this.highlyGeneric.complex.dict).join(',\n');
this.highlyGeneric.complex.mru.reset();
this.frozen = true;
@ -706,9 +705,7 @@ FilterContainer.prototype.fromSelfie = function(selfie) {
this.lowlyGeneric.cl.simple = new Set(selfie.lowlyGenericSCL);
this.lowlyGeneric.cl.complex = new Map(selfie.lowlyGenericCCL);
this.highlyGeneric.simple.dict = new Set(selfie.highSimpleGenericHideArray);
this.highlyGeneric.simple.str = selfie.highSimpleGenericHideArray.join(',\n');
this.highlyGeneric.complex.dict = new Set(selfie.highComplexGenericHideArray);
this.highlyGeneric.complex.str = selfie.highComplexGenericHideArray.join(',\n');
this.needDOMSurveyor =
selfie.lowlyGenericSID.length !== 0 ||
selfie.lowlyGenericCID.length !== 0 ||
@ -1042,14 +1039,19 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
// indirectly in the mru cache: this is to prevent duplication of the
// string in memory, which I have observed occurs when the string is
// stored directly as a value in a Map.
//
// https://github.com/uBlockOrigin/uBlock-issues/issues/1692
// Wrap generic selectors to prevent matching `html` or `body`
// elements. Content script-side code must unwrap those selectors
// before reporting them in a user interface.
if ( options.noGenericCosmeticFiltering !== true ) {
const exceptionSetHash = out.exceptionFilters.join();
for ( const key in this.highlyGeneric ) {
const entry = this.highlyGeneric[key];
let str = entry.mru.lookup(exceptionSetHash);
if ( str === undefined ) {
str = { s: entry.str, excepted: [] };
let genericSet = entry.dict;
let genericSet = entry.dict;
let cache = entry.mru.lookup(exceptionSetHash);
if ( cache === undefined ) {
cache = { s: '', excepted: [] };
let hit = false;
for ( const exception of exceptionSet ) {
if ( (hit = genericSet.has(exception)) ) { break; }
@ -1058,18 +1060,23 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
genericSet = new Set(entry.dict);
for ( const exception of exceptionSet ) {
if ( genericSet.delete(exception) ) {
str.excepted.push(exception);
cache.excepted.push(exception);
}
}
str.s = Array.from(genericSet).join(',\n');
}
entry.mru.add(exceptionSetHash, str);
let genericArr = Array.from(genericSet);
if ( this.cssIs ) {
genericArr =
genericArr.map(a => `:is(${a}):not(html,body)/*hg*/`);
}
cache.s = genericArr.join(',\n');
entry.mru.add(exceptionSetHash, cache);
}
if ( str.excepted.length !== 0 ) {
out.exceptedFilters.push(...str.excepted);
if ( cache.excepted.length !== 0 ) {
out.exceptedFilters.push(...cache.excepted);
}
if ( str.s.length !== 0 ) {
injectedHideFilters.push(str.s);
if ( cache.s.length !== 0 ) {
injectedHideFilters.push(cache.s);
}
}
}

View File

@ -91,6 +91,7 @@ const processDeclarativeSimple = function(node, out) {
) {
return;
}
const unwrapSelector = vAPI.domFilterer.unwrapSelector;
for ( const selector of simpleDeclarativeSet ) {
if (
(node === document || safeMatchSelector(selector, node) === false) &&
@ -98,7 +99,7 @@ const processDeclarativeSimple = function(node, out) {
) {
continue;
}
out.push(`##${selector}`);
out.push(`##${unwrapSelector(selector)}`);
simpleDeclarativeSet.delete(selector);
simpleDeclarativeStr = undefined;
loggedSelectors.add(selector);
@ -113,9 +114,10 @@ const processDeclarativeComplex = function(out) {
complexDeclarativeStr = safeGroupSelectors(complexDeclarativeSet);
}
if ( document.querySelector(complexDeclarativeStr) === null ) { return; }
const unwrapSelector = vAPI.domFilterer.unwrapSelector;
for ( const selector of complexDeclarativeSet ) {
if ( safeQuerySelector(selector) === null ) { continue; }
out.push(`##${selector}`);
out.push(`##${unwrapSelector(selector)}`);
complexDeclarativeSet.delete(selector);
complexDeclarativeStr = undefined;
loggedSelectors.add(selector);

View File

@ -1,7 +1,7 @@
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2015-2018 Raymond Hill
Copyright (C) 2015-present Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -509,7 +509,7 @@ const cosmeticFilterMapper = (function() {
// Declarative selectors.
for ( const entry of (details.declarative || []) ) {
for ( const selector of entry[0].split(',\n') ) {
let canonical = selector;
let canonical = vAPI.domFilterer.unwrapSelector(selector);
let nodes;
if ( entry[1] !== vAPI.hideStyle ) {
canonical += ':style(' + entry[1] + ')';