This commit is contained in:
gorhill 2014-08-13 20:03:55 -04:00
parent 338946c2e0
commit ef289bc4da
4 changed files with 179 additions and 134 deletions

View file

@ -127,23 +127,6 @@ FilterHostname.prototype.retrieve = function(hostname, out) {
}
};
/******************************************************************************/
// Any selector specific to an entity
// Examples:
// google.*###cnt #center_col > #res > #topstuff > .ts
var FilterEntity = function(s, entity) {
this.s = s;
this.entity = entity;
};
FilterEntity.prototype.retrieve = function(entity, out) {
if ( entity.slice(-this.entity.length) === this.entity ) {
out.push(this.s);
}
};
/******************************************************************************/
/******************************************************************************/
@ -240,6 +223,27 @@ FilterParser.prototype.parse = function(s) {
/******************************************************************************/
/******************************************************************************/
var SelectorCacheEntry = function() {
this.selectors = [];
this.lastAccessTime = Date.now();
};
SelectorCacheEntry.prototype.add = function(selectors) {
this.lastAccessTime = Date.now();
this.selectors.push(selectors);
};
SelectorCacheEntry.prototype.retrieve = function(out) {
this.lastAccessTime = Date.now();
var i = this.selectors.length;
while ( i-- ) {
out.push(this.selectors[i]);
}
};
/******************************************************************************/
/******************************************************************************/
// Two Unicode characters:
// T0HHHHHHH HHHHHHHHH
// | | |
@ -306,7 +310,6 @@ var makeHash = function(unhide, token, mask) {
// High-high generic: everything else
// Specific
// Specfic hostname
// Specific entity
//
// Generic filters can only be enforced once the main document is loaded.
// Specific filers can be enforced before the main document is loaded.
@ -328,6 +331,11 @@ FilterContainer.prototype.reset = function() {
this.domainHashMask = (1 << 10) - 1;
this.genericHashMask = (1 << 15) - 1;
this.selectorCache = {};
this.selectorCacheCount = 0;
this.selectorCacheLowWaterMark = 75;
this.selectorCacheHighWaterMark = 100;
// temporary (at parse time)
this.lowGenericHide = {};
this.lowGenericDonthide = {};
@ -335,8 +343,6 @@ FilterContainer.prototype.reset = function() {
this.highGenericDonthide = {};
this.hostnameHide = {};
this.hostnameDonthide = {};
this.entityHide = {};
this.entityDonthide = {};
// permanent
// [class], [id]
@ -361,7 +367,6 @@ FilterContainer.prototype.reset = function() {
this.highHighGenericDonthideCount = 0;
this.hostnameFilters = {};
this.entityFilters = {};
};
/******************************************************************************/
@ -389,7 +394,7 @@ FilterContainer.prototype.add = function(s) {
if ( hostname.charAt(0) !== '~' ) {
applyGlobally = false;
}
this.addSpecificSelector(hostname, parsed);
this.addHostnameSelector(hostname, parsed);
}
if ( applyGlobally ) {
this.addGenericSelector(parsed);
@ -422,17 +427,6 @@ FilterContainer.prototype.addGenericSelector = function(parsed) {
/******************************************************************************/
FilterContainer.prototype.addSpecificSelector = function(hostname, parsed) {
// rhill 2014-07-13: new filter class: entity.
if ( hostname.slice(-2) === '.*' ) {
this.addEntitySelector(hostname, parsed);
} else {
this.addHostnameSelector(hostname, parsed);
}
};
/******************************************************************************/
FilterContainer.prototype.addHostnameSelector = function(hostname, parsed) {
// https://github.com/gorhill/uBlock/issues/145
var unhide = parsed.unhide;
@ -458,26 +452,6 @@ FilterContainer.prototype.addHostnameSelector = function(hostname, parsed) {
/******************************************************************************/
FilterContainer.prototype.addEntitySelector = function(hostname, parsed) {
var entries = parsed.unhide === 0 ?
this.entityHide :
this.entityDonthide;
var entity = hostname.slice(0, -2);
var entry = entries[entity];
if ( entry === undefined ) {
entry = entries[entity] = {};
entry[parsed.suffix] = true;
this.acceptedCount += 1;
} else if ( entry[parsed.suffix] === undefined ) {
entry[parsed.suffix] = true;
this.acceptedCount += 1;
} else {
this.duplicateCount += 1;
}
};
/******************************************************************************/
FilterContainer.prototype.freezeLowGenerics = function(what, type) {
var selectors = this[what];
var matches, selectorPrefix, f, hash, bucket;
@ -519,7 +493,7 @@ FilterContainer.prototype.freezeHostnameSpecifics = function(what, type) {
if ( entries.hasOwnProperty(hostname) === false ) {
continue;
}
f = new FilterHostname(Object.keys(entries[hostname]).join(','), hostname);
f = new FilterHostname(Object.keys(entries[hostname]).join(',\n'), hostname);
hash = makeHash(type, µburi.domainFromHostname(hostname), this.domainHashMask);
bucket = filters[hash];
if ( bucket === undefined ) {
@ -535,30 +509,6 @@ FilterContainer.prototype.freezeHostnameSpecifics = function(what, type) {
/******************************************************************************/
FilterContainer.prototype.freezeEntitySpecifics = function(what, type) {
var entries = this[what];
var filters = this.entityFilters;
var f, hash, bucket;
for ( var entity in entries ) {
if ( entries.hasOwnProperty(entity) === false ) {
continue;
}
f = new FilterEntity(Object.keys(entries[entity]).join(','), entity);
hash = makeHash(type, entity, this.domainHashMask);
bucket = filters[hash];
if ( bucket === undefined ) {
filters[hash] = f;
} else if ( bucket instanceof FilterBucket ) {
bucket.add(f);
} else {
filters[hash] = new FilterBucket(bucket, f);
}
}
this[what] = {};
};
/******************************************************************************/
FilterContainer.prototype.freezeHighGenerics = function(what) {
var selectors = this['highGeneric' + what];
@ -614,8 +564,6 @@ FilterContainer.prototype.freeze = function() {
this.freezeHighGenerics('Donthide');
this.freezeHostnameSpecifics('hostnameHide', 0);
this.freezeHostnameSpecifics('hostnameDonthide', 1);
this.freezeEntitySpecifics('entityHide', 0);
this.freezeEntitySpecifics('entityDonthide', 1);
this.filterParser.reset();
this.frozen = true;
@ -625,6 +573,51 @@ FilterContainer.prototype.freeze = function() {
/******************************************************************************/
FilterContainer.prototype.addToSelectorCache = function(hostname, selectors) {
if ( typeof hostname !== 'string' || hostname === '' ) {
return;
}
if ( typeof selectors !== 'string' || selectors === '' ) {
return;
}
var entry = this.selectorCache[hostname];
if ( entry === undefined ) {
entry = this.selectorCache[hostname] = new SelectorCacheEntry();
this.selectorCacheCount += 1;
if ( this.selectorCacheCount > this.selectorCacheHighWaterMark ) {
this.pruneSelectorCache();
}
}
entry.add(selectors);
};
/******************************************************************************/
FilterContainer.prototype.retrieveFromSelectorCache = function(hostname, out) {
var entry = this.selectorCache[hostname];
if ( entry === undefined ) {
return;
}
entry.retrieve(out);
};
/******************************************************************************/
FilterContainer.prototype.pruneSelectorCache = function() {
var cache = this.selectorCache;
var hostnames = Object.keys(cache).sort(function(a ,b) {
return cache[b].lastAccessTime - cache[a].lastAccessTime;
});
var toRemove = hostnames.slice(this.selectorCacheLowWaterMark);
var i = toRemove.length;
while ( i-- ) {
delete cache[toRemove[i]];
}
this.selectorCacheCount -= toRemove.length;
};
/******************************************************************************/
FilterContainer.prototype.retrieveGenericSelectors = function(request) {
if ( µb.userSettings.parseAllABPHideFilters !== true ) {
return;
@ -698,11 +691,8 @@ FilterContainer.prototype.retrieveDomainSelectors = function(request) {
//quickProfiler.start('FilterContainer.retrieve()');
var hostname = µb.URI.hostnameFromURI(request.locationURL);
var domain = µb.URI.domainFromHostname(hostname);
var pos = domain.indexOf('.');
var r = {
domain: domain,
entity: pos === -1 ? domain : domain.slice(0, pos - domain.length),
domain: µb.URI.domainFromHostname(hostname),
hide: [],
donthide: []
};
@ -712,15 +702,13 @@ FilterContainer.prototype.retrieveDomainSelectors = function(request) {
if ( bucket = this.hostnameFilters[hash] ) {
bucket.retrieve(hostname, r.hide);
}
hash = makeHash(0, r.entity, this.domainHashMask);
if ( bucket = this.entityFilters[hash] ) {
bucket.retrieve(pos === -1 ? domain : hostname.slice(0, pos - domain.length), r.hide);
}
hash = makeHash(1, r.domain, this.domainHashMask);
if ( bucket = this.hostnameFilters[hash] ) {
bucket.retrieve(hostname, r.donthide);
}
this.retrieveFromSelectorCache(hostname, r.hide);
//quickProfiler.stop();
//console.log(

View file

@ -132,16 +132,25 @@ var uBlockMessaging = (function(name){
var contextNodes = [document];
var domLoaded = function() {
// https://github.com/gorhill/uBlock/issues/14
// Treat any existing domain-specific exception selectors as if they had
// been injected already.
var style = document.getElementById('uBlock1ae7a5f130fc79b4fdb8a4272d9426b5');
var exceptions = style && style.getAttribute('uBlock1ae7a5f130fc79b4fdb8a4272d9426b5');
if ( exceptions ) {
exceptions = JSON.parse(exceptions);
var i = exceptions.length;
var style = document.getElementById('uBlockPreload-1ae7a5f130fc79b4fdb8a4272d9426b5');
if ( style ) {
// https://github.com/gorhill/uBlock/issues/14
// Treat any existing domain-specific exception selectors as if
// they had been injected already.
var selectors, i;
var exceptions = style.getAttribute('uBlockExceptions');
if ( exceptions ) {
selectors = JSON.parse(exceptions);
i = selectors.length;
while ( i-- ) {
injectedSelectors[selectors[i]] = true;
}
}
// Avoid re-injecting already injected CSS rules.
selectors = selectorsFromStyles(style);
i = selectors.length;
while ( i-- ) {
injectedSelectors[exceptions[i]] = true;
injectedSelectors[selectors[i]] = true;
}
}
idsFromNodeList(document.querySelectorAll('[id]'));
@ -149,6 +158,28 @@ var uBlockMessaging = (function(name){
retrieveGenericSelectors();
};
var selectorsFromStyles = function(styleRef) {
var selectors = [];
var styles = typeof styleRef === 'string' ?
document.querySelectorAll(styleRef):
[styleRef];
var i = styles.length;
var style, subset, lastSelector, pos;
while ( i-- ) {
style = styles[i];
subset = style.textContent.split(',\n');
lastSelector = subset.pop();
if ( lastSelector ) {
pos = lastSelector.indexOf('\n');
if ( pos !== -1 ) {
subset.push(lastSelector.slice(0, pos));
}
}
selectors = selectors.concat(subset);
}
return selectors;
};
var retrieveGenericSelectors = function() {
var selectors = classSelectors !== null ? Object.keys(classSelectors) : [];
if ( idSelectors !== null ) {
@ -209,12 +240,19 @@ var uBlockMessaging = (function(name){
if ( hideSelectors.length ) {
applyCSS(hideSelectors, 'display', 'none');
var style = document.createElement('style');
var text = hideSelectors.join(',\n') + ' {display:none !important;}';
style.appendChild(document.createTextNode(text));
style.setAttribute('class', 'uBlockPostload-1ae7a5f130fc79b4fdb8a4272d9426b5');
// The linefeed before the style block is very important: do no remove!
var text = hideSelectors.join(',\n');
style.appendChild(document.createTextNode(text + '\n{display:none !important;}'));
var parent = document.body || document.documentElement;
if ( parent ) {
parent.appendChild(style);
}
messaging.tell({
what: 'injectedGenericCosmeticSelectors',
hostname: window.location.hostname,
selectors: text
});
//console.debug('µBlock> generic cosmetic filters: injecting %d CSS rules:', hideSelectors.length, text);
}
contextNodes.length = 0;
@ -268,7 +306,8 @@ var uBlockMessaging = (function(name){
var processHighLowGenerics = function(generics, out) {
var attrs = ['title', 'alt'];
var attr, attrValue, nodeList, iNode, node, selector;
var attr, attrValue, nodeList, iNode, node;
var selector;
while ( attr = attrs.pop() ) {
nodeList = selectNodes('[' + attr + ']');
iNode = nodeList.length;
@ -277,17 +316,21 @@ var uBlockMessaging = (function(name){
attrValue = node.getAttribute(attr);
if ( !attrValue ) { continue; }
selector = '[' + attr + '="' + attrValue + '"]';
if ( injectedSelectors[selector] === undefined && generics[selector] ) {
injectedSelectors[selector] = true;
if ( out !== undefined ) {
out.push(selector);
if ( generics[selector] ) {
if ( injectedSelectors[selector] === undefined ) {
injectedSelectors[selector] = true;
if ( out !== undefined ) {
out.push(selector);
}
}
}
selector = node.tagName.toLowerCase() + selector;
if ( injectedSelectors[selector] === undefined && generics[selector] ) {
injectedSelectors[selector] = true;
if ( out !== undefined ) {
out.push(selector);
if ( generics[selector] ) {
if ( injectedSelectors[selector] === undefined ) {
injectedSelectors[selector] = true;
if ( out !== undefined ) {
out.push(selector);
}
}
}
}
@ -297,7 +340,7 @@ var uBlockMessaging = (function(name){
var processHighMediumGenerics = function(generics, out) {
var nodeList = selectNodes('a[href^="http"]');
var iNode = nodeList.length;
var node, href, pos, hash, selector;
var node, href, pos, hash, selectors, selector, iSelector;
while ( iNode-- ) {
node = nodeList[iNode];
href = node.getAttribute('href');
@ -305,29 +348,37 @@ var uBlockMessaging = (function(name){
pos = href.indexOf('://');
if ( pos === -1 ) { continue; }
hash = href.slice(pos + 3, pos + 11);
selector = generics[hash];
if ( selector === undefined ) { continue; }
if ( injectedSelectors[selector] !== undefined ) { continue; }
injectedSelectors[selector] = true;
if ( out !== undefined ) {
out.push(selector);
selectors = generics[hash];
if ( selectors === undefined ) { continue; }
selectors = selectors.split(',\n');
iSelector = selectors.length;
while ( iSelector-- ) {
selector = selectors[iSelector];
if ( injectedSelectors[selector] === undefined ) {
injectedSelectors[selector] = true;
if ( out !== undefined ) {
out.push(selector);
}
}
}
}
};
var processHighHighGenerics = function(generics, out) {
if ( injectedSelectors[generics] !== undefined ) { return; }
if ( injectedSelectors['{{highHighGenerics}}'] !== undefined ) { return; }
if ( document.querySelector(generics) === null ) { return; }
injectedSelectors[generics] = true;
if ( out !== undefined ) {
var selectors = generics.split(',\n');
var i = selectors.length;
while ( i-- ) {
if ( injectedSelectors[selectors[i]] !== undefined ) {
selectors.splice(i, 1);
injectedSelectors['{{highHighGenerics}}'] = true;
var selectors = generics.split(',\n');
var iSelector = selectors.length;
var selector;
while ( iSelector-- ) {
selector = selectors[iSelector];
if ( injectedSelectors[selector] === undefined ) {
injectedSelectors[selector] = true;
if ( out !== undefined ) {
out.push(selector);
}
}
out.push(selectors.join(',\n'));
}
};
@ -347,7 +398,6 @@ var uBlockMessaging = (function(name){
if ( node.nodeType !== 1 ) { continue; }
// id
v = nodes[i].id;
// quite unlikely, so no need to be fancy
if ( typeof v !== 'string' ) { continue; }
v = v.trim();
if ( v === '' ) { continue; }
@ -418,7 +468,6 @@ var uBlockMessaging = (function(name){
var mutationObservedHandler = function(mutations) {
var iMutation = mutations.length;
var nodes = [];
var nodeList, iNode, node;
while ( iMutation-- ) {
nodeList = mutations[iMutation].addedNodes;

View file

@ -141,21 +141,21 @@ var domainCosmeticFilteringHandler = function(selectors) {
return;
}
var style = document.createElement('style');
style.setAttribute('id', 'uBlockPreload-1ae7a5f130fc79b4fdb8a4272d9426b5');
var donthide = selectors.donthide;
var hide = selectors.hide;
if ( donthide.length !== 0 ) {
donthide = donthide.length !== 1 ? donthide.join(',') : donthide[0];
donthide = donthide.split(',');
style.setAttribute('id', 'uBlock1ae7a5f130fc79b4fdb8a4272d9426b5');
style.setAttribute('uBlock1ae7a5f130fc79b4fdb8a4272d9426b5', JSON.stringify(donthide));
donthide = donthide.length !== 1 ? donthide.join(',\n') : donthide[0];
donthide = donthide.split(',\n');
style.setAttribute('uBlockExceptions', JSON.stringify(donthide));
// https://github.com/gorhill/uBlock/issues/143
if ( hide.length !== 0 ) {
// I chose to use Array.indexOf() instead of converting the array to
// a map, then deleting whitelisted selectors, and then converting
// back the map into an array, because there are typically very few
// exception filters, if any.
hide = hide.length !== 1 ? hide.join(',') : hide[0];
hide = hide.split(',');
hide = hide.length !== 1 ? hide.join(',\n') : hide[0];
hide = hide.split(',\n');
var i = donthide.length, j;
while ( i-- ) {
j = hide.indexOf(donthide[i]);
@ -166,9 +166,10 @@ var domainCosmeticFilteringHandler = function(selectors) {
}
}
if ( hide.length !== 0 ) {
var text = hide.join(',');
var text = hide.join(',\n');
domainCosmeticFilteringApplyCSS(text, 'display', 'none');
style.appendChild(document.createTextNode(text + ' {display:none !important;}'));
// The linefeed before the style block is very important: do no remove!
style.appendChild(document.createTextNode(text + '\n{display:none !important;}'));
//console.debug('µBlock> "%s" cosmetic filters: injecting %d CSS rules:', selectors.domain, selectors.hide.length, hideStyleText);
}
var parent = document.head || document.documentElement;

View file

@ -166,6 +166,13 @@ var onMessage = function(request, sender, callback) {
}
break;
case 'injectedGenericCosmeticSelectors':
µb.abpHideFilters.addToSelectorCache(
request.hostname,
request.selectors
);
break;
case 'blockedRequests':
response = {
collapse: µb.userSettings.collapseBlocked,