mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-10 09:07:54 +01:00
this fixes the other half of #58: from which list(s) a cosmetic filter originates
This commit is contained in:
parent
daef0bd8c8
commit
9a5404ef07
9 changed files with 281 additions and 56 deletions
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
})();
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue