this fixes the other half of #58: from which list(s) a cosmetic filter originates

This commit is contained in:
gorhill 2015-06-13 11:21:55 -04:00
parent daef0bd8c8
commit 9a5404ef07
9 changed files with 281 additions and 56 deletions

View File

@ -189,7 +189,7 @@ body:not(.popupOn) #content tr.canMtx td:nth-of-type(2) {
body:not(.popupOn) #content tr.canMtx td:nth-of-type(2):hover { body:not(.popupOn) #content tr.canMtx td:nth-of-type(2):hover {
background: #ccc; background: #ccc;
} }
#content tr.cat_net[data-filter] td:nth-of-type(3) { #content tr.canLookup td:nth-of-type(3) {
cursor: zoom-in; cursor: zoom-in;
} }
#content tr.cat_net td:nth-of-type(4), #content tr.cat_net td:nth-of-type(4),

View File

@ -117,7 +117,7 @@ var FilterPlainMore = function(s) {
}; };
FilterPlainMore.prototype.retrieve = function(s, out) { FilterPlainMore.prototype.retrieve = function(s, out) {
if ( s === this.s.slice(0, s.length) ) { if ( this.s.lastIndexOf(s, 0) === 0 ) {
out.push(this.s); out.push(this.s);
} }
}; };

View File

@ -44,7 +44,18 @@ var details = {};
(function() { (function() {
var onReponseReady = function(response) { var onReponseReady = function(response) {
var lists = response.matches; if ( typeof response !== 'object' ) {
return;
}
var lists;
for ( var rawFilter in response ) {
if ( response.hasOwnProperty(rawFilter) === false ) {
continue;
}
lists = response[rawFilter];
break;
}
if ( Array.isArray(lists) === false || lists.length === 0 ) { if ( Array.isArray(lists) === false || lists.length === 0 ) {
return; return;
} }
@ -72,8 +83,9 @@ var details = {};
}; };
messager.send({ messager.send({
what: 'reverseLookupFilter', what: 'listsFromNetFilter',
filter: details.fc compiledFilter: details.fc,
rawFilter: details.fs
}, onReponseReady); }, onReponseReady);
})(); })();

View File

@ -389,10 +389,7 @@ var createHiddenTextNode = function(text) {
var createGap = function(tabId, url) { var createGap = function(tabId, url) {
var tr = createRow('1'); var tr = createRow('1');
tr.classList.add('tab'); tr.classList.add('tab', 'canMtx', 'tab_' + tabId, 'maindoc');
tr.classList.add('canMtx');
tr.classList.add('tab_' + tabId);
tr.classList.add('maindoc');
tr.cells[firstVarDataCol].textContent = url; tr.cells[firstVarDataCol].textContent = url;
tbody.insertBefore(tr, tbody.firstChild); tbody.insertBefore(tr, tbody.firstChild);
}; };
@ -400,12 +397,13 @@ var createGap = function(tabId, url) {
/******************************************************************************/ /******************************************************************************/
var renderNetLogEntry = function(tr, entry) { var renderNetLogEntry = function(tr, entry) {
var trcl = tr.classList;
var filter = entry.d0; var filter = entry.d0;
var type = entry.d1; var type = entry.d1;
var url = entry.d2; var url = entry.d2;
var td; var td;
tr.classList.add('canMtx'); trcl.add('canMtx');
// If the request is that of a root frame, insert a gap in the table // If the request is that of a root frame, insert a gap in the table
// in order to visually separate entries for different documents. // in order to visually separate entries for different documents.
@ -423,7 +421,7 @@ var renderNetLogEntry = function(tr, entry) {
var filterCat = filter.slice(0, 3); var filterCat = filter.slice(0, 3);
if ( filterCat.charAt(2) === ':' ) { if ( filterCat.charAt(2) === ':' ) {
tr.classList.add(filterCat.slice(0, 2)); trcl.add(filterCat.slice(0, 2));
} }
var filteringType = filterCat.charAt(0); var filteringType = filterCat.charAt(0);
@ -432,7 +430,11 @@ var renderNetLogEntry = function(tr, entry) {
filter = filter.slice(3); filter = filter.slice(3);
if ( filteringType === 's' ) { if ( filteringType === 's' ) {
td.textContent = filterDecompiler.toString(filter); td.textContent = filterDecompiler.toString(filter);
trcl.add('canLookup');
tr.setAttribute('data-filter', filter); tr.setAttribute('data-filter', filter);
} else if ( filteringType === 'c' ) {
td.textContent = filter;
trcl.add('canLookup');
} else { } else {
td.textContent = filter; td.textContent = filter;
} }
@ -441,13 +443,13 @@ var renderNetLogEntry = function(tr, entry) {
td = tr.cells[3]; td = tr.cells[3];
var filteringOp = filterCat.charAt(1); var filteringOp = filterCat.charAt(1);
if ( filteringOp === 'b' ) { if ( filteringOp === 'b' ) {
tr.classList.add('blocked'); trcl.add('blocked');
td.textContent = '--'; td.textContent = '--';
} else if ( filteringOp === 'a' ) { } else if ( filteringOp === 'a' ) {
tr.classList.add('allowed'); trcl.add('allowed');
td.textContent = '++'; td.textContent = '++';
} else if ( filteringOp === 'n' ) { } else if ( filteringOp === 'n' ) {
tr.classList.add('nooped'); trcl.add('nooped');
td.textContent = '**'; td.textContent = '**';
} else { } else {
td.textContent = ''; td.textContent = '';
@ -1262,7 +1264,6 @@ var netFilteringManager = (function() {
/******************************************************************************/ /******************************************************************************/
var reverseLookupManager = (function() { var reverseLookupManager = (function() {
var rawFilter = '';
var reSentence1 = /\{\{filter\}\}/g; var reSentence1 = /\{\{filter\}\}/g;
var sentence1Template = vAPI.i18n('loggerStaticFilteringFinderSentence1'); var sentence1Template = vAPI.i18n('loggerStaticFilteringFinderSentence1');
@ -1284,16 +1285,12 @@ var reverseLookupManager = (function() {
ev.stopPropagation(); ev.stopPropagation();
}; };
var reverseLookupDone = function(response) { var nodeFromFilter = function(filter, lists) {
var lists = response.matches; if ( Array.isArray(lists) === false || lists.length === 0 ) {
if ( Array.isArray(lists) === false ) { return null;
return;
} }
var dialog = filterFinderDialog.querySelector('.dialog');
var p = dialog.querySelector('p');
removeAllChildren(p);
var node; var node;
var p = document.createElement('p');
reSentence1.lastIndex = 0; reSentence1.lastIndex = 0;
var matches = reSentence1.exec(sentence1Template); var matches = reSentence1.exec(sentence1Template);
@ -1302,13 +1299,12 @@ var reverseLookupManager = (function() {
} else { } else {
node = uDom.nodeFromSelector('#filterFinderDialogSentence1 > span').cloneNode(true); node = uDom.nodeFromSelector('#filterFinderDialogSentence1 > span').cloneNode(true);
node.childNodes[0].textContent = sentence1Template.slice(0, matches.index); node.childNodes[0].textContent = sentence1Template.slice(0, matches.index);
node.childNodes[1].textContent = rawFilter; node.childNodes[1].textContent = filter;
node.childNodes[2].textContent = sentence1Template.slice(reSentence1.lastIndex); node.childNodes[2].textContent = sentence1Template.slice(reSentence1.lastIndex);
} }
p.appendChild(node); p.appendChild(node);
var ul = dialog.querySelector('ul'); var ul = document.createElement('ul');
removeAllChildren(ul);
var list, li; var list, li;
for ( var i = 0; i < lists.length; i++ ) { for ( var i = 0; i < lists.length; i++ ) {
list = lists[i]; list = lists[i];
@ -1324,6 +1320,26 @@ var reverseLookupManager = (function() {
li.appendChild(node); li.appendChild(node);
ul.appendChild(li); ul.appendChild(li);
} }
p.appendChild(ul);
return p;
};
var reverseLookupDone = function(response) {
if ( typeof response !== 'object' ) {
return;
}
var dialog = filterFinderDialog.querySelector('.dialog');
removeAllChildren(dialog);
for ( var filter in response ) {
var p = nodeFromFilter(filter, response[filter]);
if ( p === null ) {
continue;
}
dialog.appendChild(p);
}
document.body.appendChild(filterFinderDialog); document.body.appendChild(filterFinderDialog);
filterFinderDialog.addEventListener('click', onClick, true); filterFinderDialog.addEventListener('click', onClick, true);
@ -1331,15 +1347,24 @@ var reverseLookupManager = (function() {
var toggleOn = function(ev) { var toggleOn = function(ev) {
var row = ev.target.parentElement; var row = ev.target.parentElement;
var filter = row.getAttribute('data-filter') || ''; var rawFilter = row.cells[2].textContent;
if ( filter === '' ) { if ( rawFilter === '' ) {
return; return;
} }
rawFilter = row.cells[2].textContent;
messager.send({ if ( row.classList.contains('cat_net') ) {
what: 'reverseLookupFilter', messager.send({
filter: filter what: 'listsFromNetFilter',
}, reverseLookupDone); compiledFilter: row.getAttribute('data-filter') || '',
rawFilter: rawFilter
}, reverseLookupDone);
} else if ( row.classList.contains('cat_cosmetic') ) {
messager.send({
what: 'listsFromCosmeticFilter',
hostname: row.getAttribute('data-hn-frame') || '',
rawFilter: rawFilter,
}, reverseLookupDone);
}
}; };
var toggleOff = function() { var toggleOff = function() {
@ -1656,7 +1681,7 @@ uDom.onLoad(function() {
uDom('#maxEntries').on('change', onMaxEntriesChanged); uDom('#maxEntries').on('change', onMaxEntriesChanged);
uDom('#content table').on('click', 'tr.canMtx > td:nth-of-type(2)', popupManager.toggleOn); uDom('#content table').on('click', 'tr.canMtx > td:nth-of-type(2)', popupManager.toggleOn);
uDom('#content').on('click', 'tr.cat_net > td:nth-of-type(4)', netFilteringManager.toggleOn); uDom('#content').on('click', 'tr.cat_net > td:nth-of-type(4)', netFilteringManager.toggleOn);
uDom('#content').on('click', 'tr[data-filter] > td:nth-of-type(3)', reverseLookupManager.toggleOn); uDom('#content').on('click', 'tr.canLookup > td:nth-of-type(3)', reverseLookupManager.toggleOn);
}); });
/******************************************************************************/ /******************************************************************************/

View File

@ -66,8 +66,20 @@ var onMessage = function(request, sender, callback) {
µb.reloadAllFilters(callback); µb.reloadAllFilters(callback);
return; return;
case 'reverseLookupFilter': case 'listsFromNetFilter':
µb.staticFilteringReverseLookup.lookup(request.filter, callback); µb.staticFilteringReverseLookup.fromNetFilter(
request.compiledFilter,
request.rawFilter,
callback
);
return;
case 'listsFromCosmeticFilter':
µb.staticFilteringReverseLookup.fromCosmeticFilter(
request.hostname,
request.rawFilter,
callback
);
return; return;
default: default:
@ -1351,7 +1363,9 @@ var logCosmeticFilters = function(tabId, details) {
'cosmetic', 'cosmetic',
'cb:##' + selectors[i], 'cb:##' + selectors[i],
'dom', 'dom',
details.pageURL details.frameURL,
null,
details.frameHostname
); );
} }
}; };

View File

@ -29,35 +29,164 @@ var listEntries = Object.create(null);
/******************************************************************************/ /******************************************************************************/
var lookup = function(details) { // Helpers
var matches = [];
var rescape = function(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
};
/******************************************************************************/
var fromNetFilter = function(details) {
var lists = [];
var entry, pos; var entry, pos;
for ( var path in listEntries ) { for ( var path in listEntries ) {
entry = listEntries[path]; entry = listEntries[path];
if ( entry === undefined ) { if ( entry === undefined ) {
continue; continue;
} }
pos = entry.content.indexOf(details.filter); pos = entry.content.indexOf(details.compiledFilter);
if ( pos === -1 ) { if ( pos === -1 ) {
continue; continue;
} }
matches.push({ lists.push({
title: entry.title, title: entry.title,
supportURL: entry.supportURL supportURL: entry.supportURL
}); });
} }
var response = {};
response[details.rawFilter] = lists;
postMessage({ postMessage({
id: details.id, id: details.id,
response: { response: response
filter: details.filter,
matches: matches
}
}); });
}; };
/******************************************************************************/ /******************************************************************************/
// Looking up filter lists from a cosmetic filter is a bit more complicated
// than with network filters:
//
// The filter is its raw representation, not its compiled version. This is
// because the cosmetic filtering engine can't translate a live cosmetic
// filter into its compiled version. Reason is I do not want to burden
// cosmetic filtering with the resource overhead of being able to re-compile
// live cosmetic filters. I want the cosmetic filtering code to be left
// completely unaffected by reverse lookup requirements.
//
// Mainly, given a CSS selector and a hostname as context, we will derive
// various versions of compiled filters and see if there are matches. This way
// the whole CPU cost is incurred by the reverse lookup code -- in a worker
// thread, and the cosmetic filtering engine incurred zero cost.
//
// For this though, the reverse lookup code here needs some knowledge of
// the inners of the cosmetic filtering engine.
// FilterContainer.fromCompiledContent() is our reference code to create
// the various compiled versions.
var fromCosmeticFilter = function(details) {
var filter = details.rawFilter;
var exception = filter.lastIndexOf('#@#', 0) === 0;
filter = exception ? filter.slice(3) : filter.slice(2);
var candidates = Object.create(null);
var response = Object.create(null);
// First step: assuming the filter is generic, find out its compiled
// representation.
// Reference: FilterContainer.compileGenericSelector().
var reStr = '';
var matches = rePlainSelector.exec(filter);
if ( matches ) {
if ( matches[0] === filter ) { // simple CSS selector
reStr = rescape('c\vlg\v') + '\\w+' + rescape('\v' + filter);
} else { // complex CSS selector
reStr = rescape('c\vlg+\v') + '\\w+' + rescape('\v' + filter);
}
} else if ( reHighLow.test(filter) ) { // [alt] or [title]
reStr = rescape('c\vhlg0\v' + filter) + '(?:\\n|$)';
} else if ( reHighMedium.test(filter) ) { // [href^="..."]
reStr = rescape('c\vhmg0\v') + '\\w+' + rescape('\v' + filter);
} else { // all else
reStr = rescape('c\vhhg0\v' + filter);
}
candidates[details.rawFilter] = new RegExp(reStr + '(?:\\n|$)');
var pos;
var domain = details.domain;
var hostname = details.hostname;
if ( hostname !== '' ) {
for ( ;; ) {
candidates[hostname + '##' + filter] = new RegExp(
rescape('c\vh\v') +
'\\w+' +
rescape('\v' + hostname + '\v' + filter) +
'(?:\\n|$)'
);
// If there is no valid domain, there won't be any other
// version of this hostname-based filter.
if ( domain === '' ) {
break;
}
if ( hostname === domain ) {
break;
}
pos = hostname.indexOf('.');
if ( pos === -1 ) {
break;
}
hostname = hostname.slice(pos + 1);
}
}
// Entity-based
pos = domain.indexOf('.');
if ( pos !== -1 ) {
var entity = domain.slice(0, pos);
candidates[entity + '.*##' + filter] = new RegExp(
rescape('c\ve\v' + entity + '\v' + filter) +
'(?:\\n|$)'
);
}
var re, path, entry;
for ( var candidate in candidates ) {
re = candidates[candidate];
for ( path in listEntries ) {
entry = listEntries[path];
if ( entry === undefined ) {
continue;
}
if ( re.test(entry.content) === false ) {
continue;
}
if ( response[candidate] === undefined ) {
response[candidate] = [];
}
response[candidate].push({
title: entry.title,
supportURL: entry.supportURL
});
}
}
postMessage({
id: details.id,
response: response
});
};
var rePlainSelector = /^([#.][\w-]+)/;
var reHighLow = /^[a-z]*\[(?:alt|title)="[^"]+"\]$/;
var reHighMedium = /^\[href\^="https?:\/\/([^"]{8})[^"]*"\]$/;
/******************************************************************************/
onmessage = function(e) { onmessage = function(e) {
var msg = e.data; var msg = e.data;
@ -70,8 +199,12 @@ onmessage = function(e) {
listEntries[msg.details.path] = msg.details; listEntries[msg.details.path] = msg.details;
break; break;
case 'reverseLookup': case 'fromNetFilter':
lookup(msg); fromNetFilter(msg);
break;
case 'fromCosmeticFilter':
fromCosmeticFilter(msg);
break; break;
} }
}; };

View File

@ -123,11 +123,16 @@ var initWorker = function(callback) {
/******************************************************************************/ /******************************************************************************/
var lookup = function(compiledFilter, callback) { var fromNetFilter = function(compiledFilter, rawFilter, callback) {
if ( typeof callback !== 'function' ) { if ( typeof callback !== 'function' ) {
return; return;
} }
if ( compiledFilter === '' || rawFilter === '' ) {
callback();
return;
}
if ( workerTTLTimer !== null ) { if ( workerTTLTimer !== null ) {
clearTimeout(workerTTLTimer); clearTimeout(workerTTLTimer);
workerTTLTimer = null; workerTTLTimer = null;
@ -136,9 +141,46 @@ var lookup = function(compiledFilter, callback) {
var onWorkerReady = function() { var onWorkerReady = function() {
var id = messageId++; var id = messageId++;
var message = { var message = {
what: 'reverseLookup', what: 'fromNetFilter',
id: id, id: id,
filter: compiledFilter compiledFilter: compiledFilter,
rawFilter: rawFilter
};
pendingResponses[id] = callback;
worker.postMessage(message);
// The worker will be shutdown after n minutes without being used.
workerTTLTimer = vAPI.setTimeout(stopWorker, workerTTL);
};
initWorker(onWorkerReady);
};
/******************************************************************************/
var fromCosmeticFilter = function(hostname, rawFilter, callback) {
if ( typeof callback !== 'function' ) {
return;
}
if ( rawFilter === '' ) {
callback();
return;
}
if ( workerTTLTimer !== null ) {
clearTimeout(workerTTLTimer);
workerTTLTimer = null;
}
var onWorkerReady = function() {
var id = messageId++;
var message = {
what: 'fromCosmeticFilter',
id: id,
domain: µBlock.URI.domainFromHostname(hostname),
hostname: hostname,
rawFilter: rawFilter
}; };
pendingResponses[id] = callback; pendingResponses[id] = callback;
worker.postMessage(message); worker.postMessage(message);
@ -165,7 +207,8 @@ var resetLists = function() {
/******************************************************************************/ /******************************************************************************/
return { return {
lookup: lookup, fromNetFilter: fromNetFilter,
fromCosmeticFilter: fromCosmeticFilter,
resetLists: resetLists, resetLists: resetLists,
shutdown: stopWorker shutdown: stopWorker
}; };

View File

@ -83,7 +83,8 @@ var localMessager = vAPI.messaging.channel('scriptlets');
localMessager.send({ localMessager.send({
what: 'logCosmeticFilteringData', what: 'logCosmeticFilteringData',
pageURL: window.location.href, frameURL: window.location.href,
frameHostname: window.location.hostname,
matchedSelectors: matchedSelectors matchedSelectors: matchedSelectors
}, function() { }, function() {
localMessager.close(); localMessager.close();

View File

@ -80,10 +80,7 @@
</div> </div>
</div> </div>
<div id="filterFinderDialog" class="modalDialog"> <div id="filterFinderDialog" class="modalDialog">
<div class="dialog"> <div class="dialog"></div>
<p></p>
<ul></ul>
</div>
</div> </div>
<div id="filterFinderDialogSentence1"><span><span></span><code></code><span></span></span></div> <div id="filterFinderDialogSentence1"><span><span></span><code></code><span></span></span></div>
</div> </div>