add to #2984: fix regressions, as per feedback and code review

This commit is contained in:
gorhill 2017-10-23 09:01:00 -04:00
parent 14109b33d6
commit 6e18829f02
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
6 changed files with 175 additions and 90 deletions

View File

@ -60,6 +60,7 @@ vAPI.supportsUserStylesheets =
chrome.extensionTypes instanceof Object && chrome.extensionTypes instanceof Object &&
chrome.extensionTypes.CSSOrigin instanceof Object && chrome.extensionTypes.CSSOrigin instanceof Object &&
'USER' in chrome.extensionTypes.CSSOrigin; 'USER' in chrome.extensionTypes.CSSOrigin;
vAPI.insertCSS = chrome.tabs.insertCSS;
var noopFunc = function(){}; var noopFunc = function(){};

View File

@ -119,7 +119,7 @@ vAPI.DOMFilterer = function() {
}; };
vAPI.DOMFilterer.prototype = { vAPI.DOMFilterer.prototype = {
reHideStyle: /^display: none !important;$/, reHideStyle: /^display:none!important;$/,
// https://www.w3.org/community/webed/wiki/CSS/Selectors#Combinators // https://www.w3.org/community/webed/wiki/CSS/Selectors#Combinators
reCSSCombinators: /[ >+~]/, reCSSCombinators: /[ >+~]/,
@ -224,7 +224,7 @@ vAPI.DOMFilterer.prototype = {
selectors; selectors;
if ( selectorsStr.length === 0 ) { return; } if ( selectorsStr.length === 0 ) { return; }
vAPI.userStylesheet.add(selectorsStr + '\n{ ' + declarations + ' }'); vAPI.userStylesheet.add(selectorsStr + '\n{' + declarations + '}');
this.commit(); this.commit();
this.triggerListeners('declarative', selectorsStr); this.triggerListeners('declarative', selectorsStr);
@ -353,9 +353,9 @@ vAPI.DOMFilterer.prototype = {
attr.length !== 0 && attr.length !== 0 &&
attr.charCodeAt(attr.length - 1) !== 0x3B /* ';' */ attr.charCodeAt(attr.length - 1) !== 0x3B /* ';' */
) { ) {
attr += '; '; attr += ';';
} }
node.setAttribute('style', attr + 'display: none !important;'); node.setAttribute('style', attr + 'display:none!important;');
} }
this.hiddenNodesetToProcess.clear(); this.hiddenNodesetToProcess.clear();
}, },
@ -384,7 +384,7 @@ vAPI.DOMFilterer.prototype = {
if ( this.hideNodeStylesheet === false ) { if ( this.hideNodeStylesheet === false ) {
this.hideNodeStylesheet = true; this.hideNodeStylesheet = true;
vAPI.userStylesheet.add( vAPI.userStylesheet.add(
'[' + this.hideNodeAttr + ']\n{ display: none !important; }' '[' + this.hideNodeAttr + ']\n{display:none!important;}'
); );
} }
}, },

View File

@ -85,7 +85,11 @@ vAPI.DOMFilterer.prototype = {
var userStylesheet = vAPI.userStylesheet, var userStylesheet = vAPI.userStylesheet,
addedSelectors = []; addedSelectors = [];
for ( var entry of this.addedCSSRules ) { for ( var entry of this.addedCSSRules ) {
if ( this.disabled === false && entry.lazy ) { if (
this.disabled === false &&
entry.lazy &&
entry.injected === false
) {
userStylesheet.add( userStylesheet.add(
entry.selectors + '\n{' + entry.declarations + '}' entry.selectors + '\n{' + entry.declarations + '}'
); );
@ -114,15 +118,21 @@ vAPI.DOMFilterer.prototype = {
? selectors.join(',\n') ? selectors.join(',\n')
: selectors; : selectors;
if ( selectorsStr.length === 0 ) { return; } if ( selectorsStr.length === 0 ) { return; }
if ( details === undefined ) { details = {}; }
var entry = { var entry = {
selectors: selectorsStr, selectors: selectorsStr,
declarations, declarations,
lazy: details !== undefined && details.lazy === true, lazy: details.lazy === true,
internal: details && details.internal === true internal: details.internal === true,
injected: details.injected === true
}; };
this.addedCSSRules.add(entry); this.addedCSSRules.add(entry);
this.filterset.add(entry); this.filterset.add(entry);
if ( this.disabled === false && entry.lazy !== true ) { if (
this.disabled === false &&
entry.lazy !== true &&
entry.injected !== true
) {
vAPI.userStylesheet.add(selectorsStr + '\n{' + declarations + '}'); vAPI.userStylesheet.add(selectorsStr + '\n{' + declarations + '}');
} }
this.commit(); this.commit();
@ -162,7 +172,7 @@ vAPI.DOMFilterer.prototype = {
this.hideNodeStylesheet = true; this.hideNodeStylesheet = true;
this.addCSSRule( this.addCSSRule(
'[' + this.hideNodeId + ']', '[' + this.hideNodeId + ']',
'display: none !important;', 'display:none!important;',
{ internal: true } { internal: true }
); );
} }

View File

@ -455,6 +455,7 @@ vAPI.DOMFilterer = (function() {
]); ]);
} }
this.raw = o.raw; this.raw = o.raw;
this.cost = 0;
this.selector = o.selector; this.selector = o.selector;
this.tasks = []; this.tasks = [];
var tasks = o.tasks; var tasks = o.tasks;
@ -520,7 +521,7 @@ vAPI.DOMFilterer = (function() {
if ( o.pseudoclass ) { if ( o.pseudoclass ) {
this.domFilterer.addCSSRule( this.domFilterer.addCSSRule(
o.raw, o.raw,
'display: none !important;' 'display:none!important;'
); );
mustCommit = true; mustCommit = true;
continue; continue;
@ -575,16 +576,24 @@ vAPI.DOMFilterer = (function() {
this.addedNodes = this.removedNodes = false; this.addedNodes = this.removedNodes = false;
var afterResultset = new Set(); var afterResultset = new Set(),
t0 = Date.now(), t1, pselector;
for ( entry of this.selectors ) { for ( entry of this.selectors ) {
nodes = entry[1].exec(); pselector = entry[1];
if ( pselector.cost > 100 ) { continue; }
nodes = pselector.exec();
i = nodes.length; i = nodes.length;
while ( i-- ) { while ( i-- ) {
node = nodes[i]; node = nodes[i];
this.domFilterer.hideNode(node); this.domFilterer.hideNode(node);
afterResultset.add(node); afterResultset.add(node);
} }
t1 = Date.now();
pselector.cost += t1 - t0;
t0 = t1;
// TODO: Consider adding logging ability to report disabled
// precedural filter in the logger.
} }
if ( afterResultset.size !== currentResultset.size ) { if ( afterResultset.size !== currentResultset.size ) {
this.addedNodesHandlerMissCount = 0; this.addedNodesHandlerMissCount = 0;
@ -982,7 +991,7 @@ vAPI.domSurveyor = (function() {
if ( Array.isArray(selectors) && selectors.length !== 0 ) { if ( Array.isArray(selectors) && selectors.length !== 0 ) {
domFilterer.addCSSRule( domFilterer.addCSSRule(
selectors, selectors,
'display: none !important;', 'display:none!important;',
{ type: 'simple' } { type: 'simple' }
); );
mustCommit = true; mustCommit = true;
@ -991,7 +1000,7 @@ vAPI.domSurveyor = (function() {
if ( Array.isArray(selectors) && selectors.length !== 0 ) { if ( Array.isArray(selectors) && selectors.length !== 0 ) {
domFilterer.addCSSRule( domFilterer.addCSSRule(
selectors, selectors,
'display: none !important;', 'display:none!important;',
{ type: 'complex' } { type: 'complex' }
); );
mustCommit = true; mustCommit = true;
@ -1265,6 +1274,8 @@ vAPI.domSurveyor = (function() {
return; return;
} }
var injected = cfeDetails.rulesInjected === true;
if ( response.noCosmeticFiltering ) { if ( response.noCosmeticFiltering ) {
vAPI.domFilterer = null; vAPI.domFilterer = null;
vAPI.domSurveyor = null; vAPI.domSurveyor = null;
@ -1276,47 +1287,45 @@ vAPI.domSurveyor = (function() {
domFilterer.exceptions = cfeDetails.exceptionFilters; domFilterer.exceptions = cfeDetails.exceptionFilters;
domFilterer.addCSSRule( domFilterer.addCSSRule(
cfeDetails.declarativeFilters, cfeDetails.declarativeFilters,
'display: none !important;' 'display:none!important;',
{ injected: injected }
); );
domFilterer.addCSSRule( domFilterer.addCSSRule(
cfeDetails.highGenericHideSimple, cfeDetails.highGenericHideSimple,
'display: none !important;', 'display:none!important;',
{ type: 'simple', lazy: true } { type: 'simple', lazy: true, injected: injected }
); );
domFilterer.addCSSRule( domFilterer.addCSSRule(
cfeDetails.highGenericHideComplex, cfeDetails.highGenericHideComplex,
'display: none !important;', 'display:none!important;',
{ type: 'complex', lazy: true } { type: 'complex', lazy: true, injected: injected }
); );
domFilterer.addProceduralSelectors(cfeDetails.proceduralFilters); domFilterer.addProceduralSelectors(cfeDetails.proceduralFilters);
} }
if ( cfeDetails.netFilters.length !== 0 ) { if ( cfeDetails.netFilters.length !== 0 && injected !== true ) {
vAPI.userStylesheet.add( vAPI.userStylesheet.add(
cfeDetails.netFilters + '\n{ display: none !important; }'); cfeDetails.netFilters + '\n{display:none!important;}');
} }
vAPI.userStylesheet.apply(); vAPI.userStylesheet.apply();
var parent = document.head || document.documentElement; // Library of resources is located at:
if ( parent ) { // https://github.com/gorhill/uBlock/blob/master/assets/ublock/resources.txt
// Library of resources is located at: if ( cfeDetails.scripts ) {
// https://github.com/gorhill/uBlock/blob/master/assets/ublock/resources.txt // Have the injected script tag remove itself when execution completes:
if ( cfeDetails.scripts ) { // to keep DOM as clean as possible.
// Have the injected script tag remove itself when execution completes: var text = cfeDetails.scripts +
// to keep DOM as clean as possible. "\n" +
var text = cfeDetails.scripts + "(function() {\n" +
"\n" + " var c = document.currentScript,\n" +
"(function() {\n" + " p = c && c.parentNode;\n" +
" var c = document.currentScript,\n" + " if ( p ) {\n" +
" p = c && c.parentNode;\n" + " p.removeChild(c);\n" +
" if ( p ) {\n" + " }\n" +
" p.removeChild(c);\n" + "})();";
" }\n" + vAPI.injectScriptlet(document, text);
"})();"; vAPI.injectedScripts = text;
vAPI.injectScriptlet(document, text);
vAPI.injectedScripts = text;
}
} }
// https://github.com/chrisaljoudi/uBlock/issues/587 // https://github.com/chrisaljoudi/uBlock/issues/587

View File

@ -625,6 +625,8 @@ var FilterContainer = function() {
this.netSelectorCacheCountMax = netSelectorCacheHighWaterMark; this.netSelectorCacheCountMax = netSelectorCacheHighWaterMark;
this.selectorCacheTimer = null; this.selectorCacheTimer = null;
this.supportsUserStylesheets = vAPI.supportsUserStylesheets;
// generic exception filters // generic exception filters
this.genericDonthideSet = new Set(); this.genericDonthideSet = new Set();
@ -1255,8 +1257,8 @@ FilterContainer.prototype.compileGenericUnhideSelector = function(parsed, writer
if ( compiled === undefined ) { return; } if ( compiled === undefined ) { return; }
// https://github.com/chrisaljoudi/uBlock/issues/497 // https://github.com/chrisaljoudi/uBlock/issues/497
// All generic exception filters are put in the same bucket: they are // All generic exception filters are put in the same bucket: they are
// expected to be very rare. // expected to be very rare.
writer.push([ 7 /* g1 */, compiled ]); writer.push([ 7 /* g1 */, compiled ]);
}; };
@ -1964,6 +1966,26 @@ FilterContainer.prototype.retrieveGenericSelectors = function(request) {
/******************************************************************************/ /******************************************************************************/
FilterContainer.prototype.injectHideRules = function(
tabId,
frameId,
selectors,
runAt
) {
var details = {
code: '',
cssOrigin: 'user',
frameId: frameId,
runAt: runAt
};
for ( var selector of selectors ) {
details.code = selector + '\n{display:none!important;}';
vAPI.insertCSS(tabId, details);
}
};
/******************************************************************************/
FilterContainer.prototype.retrieveDomainSelectors = function( FilterContainer.prototype.retrieveDomainSelectors = function(
request, request,
sender, sender,
@ -1973,6 +1995,9 @@ FilterContainer.prototype.retrieveDomainSelectors = function(
console.time('cosmeticFilteringEngine.retrieveDomainSelectors'); console.time('cosmeticFilteringEngine.retrieveDomainSelectors');
// TODO: consider using MRUCache to quickly lookup all the previously
// looked-up data.
var hostname = this.µburi.hostnameFromURI(request.locationURL), var hostname = this.µburi.hostnameFromURI(request.locationURL),
domain = this.µburi.domainFromHostname(hostname) || hostname, domain = this.µburi.domainFromHostname(hostname) || hostname,
pos = domain.indexOf('.'), pos = domain.indexOf('.'),
@ -1998,7 +2023,7 @@ FilterContainer.prototype.retrieveDomainSelectors = function(
netFilters: '', netFilters: '',
proceduralFilters: [], proceduralFilters: [],
scripts: undefined, scripts: undefined,
cssRulesInjected: false rulesInjected: false
}; };
if ( options.noCosmeticFiltering !== true ) { if ( options.noCosmeticFiltering !== true ) {
@ -2027,6 +2052,22 @@ FilterContainer.prototype.retrieveDomainSelectors = function(
if ( (bucket = this.specificFilters.get('!' + this.noDomainHash)) ) { if ( (bucket = this.specificFilters.get('!' + this.noDomainHash)) ) {
bucket.retrieve(hostname, exceptionSet); bucket.retrieve(hostname, exceptionSet);
} }
// Specific exception procedural cosmetic filters.
if ( (bucket = this.proceduralFilters.get('!' + domainHash)) ) {
bucket.retrieve(hostname, exceptionSet);
}
// Specific entity-based exception procedural cosmetic filters.
if ( entityHash !== undefined ) {
if ( (bucket = this.proceduralFilters.get('!' + entityHash)) ) {
bucket.retrieve(entity, exceptionSet);
}
}
// Special bucket for those filters without a valid
// domain name as per PSL.
if ( (bucket = this.proceduralFilters.get('!' + this.noDomainHash)) ) {
bucket.retrieve(hostname, exceptionSet);
}
if ( exceptionSet.size !== 0 ) { if ( exceptionSet.size !== 0 ) {
r.exceptionFilters = µb.arrayFrom(exceptionSet); r.exceptionFilters = µb.arrayFrom(exceptionSet);
} }
@ -2126,6 +2167,27 @@ FilterContainer.prototype.retrieveDomainSelectors = function(
r.netFilters = netFilters.join(',\n'); r.netFilters = netFilters.join(',\n');
} }
if (
this.supportsUserStylesheets &&
sender instanceof Object &&
sender.tab instanceof Object &&
typeof sender.frameId === 'number'
) {
this.injectHideRules(
sender.tab.id,
sender.frameId,
[ r.declarativeFilters.join(',\n'), r.netFilters ],
'document_start'
);
this.injectHideRules(
sender.tab.id,
sender.frameId,
[ r.highGenericHideSimple, r.highGenericHideComplex ],
'document_end'
);
r.rulesInjected = true;
}
console.timeEnd('cosmeticFilteringEngine.retrieveDomainSelectors'); console.timeEnd('cosmeticFilteringEngine.retrieveDomainSelectors');
return r; return r;

View File

@ -98,34 +98,13 @@ var fromNetFilter = function(details) {
var fromCosmeticFilter = function(details) { var fromCosmeticFilter = function(details) {
var match = /^#@?#/.exec(details.rawFilter), var match = /^#@?#/.exec(details.rawFilter),
prefix = match[0], prefix = match[0],
needle = details.rawFilter.slice(prefix.length), selector = details.rawFilter.slice(prefix.length);
selector = needle;
// With low generic simple cosmetic filters, the class or id prefix // The longer the needle, the lower the number of false positives.
// character is not part of the compiled data. So we must be ready to var needles = selector.match(/\w+/g).sort(function(a, b) {
// look-up version of the selector without the prefix character. return b.length - a.length;
var idOrClassPrefix = needle.charAt(0), });
cssPrefixMatcher; var reNeedle = new RegExp(needles[0], 'g');
if ( idOrClassPrefix === '#' ) {
cssPrefixMatcher = '#?';
needle = needle.slice(1);
} else if ( idOrClassPrefix === '.' ) {
cssPrefixMatcher = '\\.?';
needle = needle.slice(1);
} else {
idOrClassPrefix = '';
cssPrefixMatcher = '';
}
// https://github.com/gorhill/uBlock/issues/3101
// Use `m` flag for efficient regex execution.
var reFilter = new RegExp(
'^\\[\\d,[^\\n]*\\\\*"' +
cssPrefixMatcher +
reEscapeCosmetic(needle) +
'\\\\*"[^\\n]*\\]$',
'gm'
);
var reHostname = new RegExp( var reHostname = new RegExp(
'^' + '^' +
@ -159,7 +138,7 @@ var fromCosmeticFilter = function(details) {
} }
var response = Object.create(null), var response = Object.create(null),
assetKey, entry, content, found, fargs; assetKey, entry, content, found, beg, end, fargs;
for ( assetKey in listEntries ) { for ( assetKey in listEntries ) {
entry = listEntries[assetKey]; entry = listEntries[assetKey];
@ -169,28 +148,61 @@ var fromCosmeticFilter = function(details) {
filterClassSeparator.length filterClassSeparator.length
); );
found = undefined; found = undefined;
while ( (match = reFilter.exec(content)) !== null ) { while ( (match = reNeedle.exec(content)) !== null ) {
fargs = JSON.parse(match[0]); beg = content.lastIndexOf('\n', match.index);
if ( beg === -1 ) { beg = 0; }
end = content.indexOf('\n', reNeedle.lastIndex);
if ( end === -1 ) { end = content.length; }
fargs = JSON.parse(content.slice(beg, end));
switch ( fargs[0] ) { switch ( fargs[0] ) {
case 0: // id-based case 0: // id-based
if (
fargs[1] === selector.slice(1) &&
selector.charAt(0) === '#'
) {
found = prefix + selector;
}
break;
case 1: // id-based
if (
fargs[2] === selector.slice(1) &&
selector.charAt(0) === '#'
) {
found = prefix + selector;
}
break;
case 2: // class-based case 2: // class-based
found = prefix + selector; if (
fargs[1] === selector.slice(1) &&
selector.charAt(0) === '.'
) {
found = prefix + selector;
}
break;
case 3:
if (
fargs[2] === selector.slice(1) &&
selector.charAt(0) === '.'
) {
found = prefix + selector;
}
break; break;
case 4: case 4:
case 5: case 5:
case 7: case 7:
found = prefix + selector; if ( fargs[1] === selector ) {
break;
case 1:
case 3:
if ( fargs[2] === selector ) {
found = prefix + selector; found = prefix + selector;
} }
break; break;
case 6: case 6:
case 8: case 8:
case 9: case 9:
if ( fargs[3] !== selector ) { break; } if (
fargs[0] === 8 && fargs[3] !== selector ||
fargs[0] === 9 && JSON.parse(fargs[3]).raw !== selector
) {
break;
}
if ( if (
fargs[2] === '' || fargs[2] === '' ||
reHostname.test(fargs[2]) === true || reHostname.test(fargs[2]) === true ||
@ -219,15 +231,6 @@ var fromCosmeticFilter = function(details) {
}); });
}; };
// https://github.com/gorhill/uBlock/issues/2666
// Raw filters in compiled filter lists may have been JSON-stringified one or
// multiple times.
var reEscapeCosmetic = function(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
.replace(/"/g, '\\\\*"');
};
/******************************************************************************/ /******************************************************************************/
onmessage = function(e) { // jshint ignore:line onmessage = function(e) { // jshint ignore:line