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 {
background: #ccc;
}
#content tr.cat_net[data-filter] td:nth-of-type(3) {
#content tr.canLookup td:nth-of-type(3) {
cursor: zoom-in;
}
#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) {
if ( s === this.s.slice(0, s.length) ) {
if ( this.s.lastIndexOf(s, 0) === 0 ) {
out.push(this.s);
}
};

View file

@ -44,7 +44,18 @@ var details = {};
(function() {
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 ) {
return;
}
@ -72,8 +83,9 @@ var details = {};
};
messager.send({
what: 'reverseLookupFilter',
filter: details.fc
what: 'listsFromNetFilter',
compiledFilter: details.fc,
rawFilter: details.fs
}, onReponseReady);
})();

View file

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

View file

@ -29,35 +29,164 @@ var listEntries = Object.create(null);
/******************************************************************************/
var lookup = function(details) {
var matches = [];
// Helpers
var rescape = function(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
};
/******************************************************************************/
var fromNetFilter = function(details) {
var lists = [];
var entry, pos;
for ( var path in listEntries ) {
entry = listEntries[path];
if ( entry === undefined ) {
continue;
}
pos = entry.content.indexOf(details.filter);
pos = entry.content.indexOf(details.compiledFilter);
if ( pos === -1 ) {
continue;
}
matches.push({
lists.push({
title: entry.title,
supportURL: entry.supportURL
});
}
var response = {};
response[details.rawFilter] = lists;
postMessage({
id: details.id,
response: {
filter: details.filter,
matches: matches
}
response: response
});
};
/******************************************************************************/
// 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) {
var msg = e.data;
@ -70,8 +199,12 @@ onmessage = function(e) {
listEntries[msg.details.path] = msg.details;
break;
case 'reverseLookup':
lookup(msg);
case 'fromNetFilter':
fromNetFilter(msg);
break;
case 'fromCosmeticFilter':
fromCosmeticFilter(msg);
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' ) {
return;
}
if ( compiledFilter === '' || rawFilter === '' ) {
callback();
return;
}
if ( workerTTLTimer !== null ) {
clearTimeout(workerTTLTimer);
workerTTLTimer = null;
@ -136,9 +141,46 @@ var lookup = function(compiledFilter, callback) {
var onWorkerReady = function() {
var id = messageId++;
var message = {
what: 'reverseLookup',
what: 'fromNetFilter',
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;
worker.postMessage(message);
@ -165,7 +207,8 @@ var resetLists = function() {
/******************************************************************************/
return {
lookup: lookup,
fromNetFilter: fromNetFilter,
fromCosmeticFilter: fromCosmeticFilter,
resetLists: resetLists,
shutdown: stopWorker
};

View file

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

View file

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