more work to expand dynamic filtering

This commit is contained in:
gorhill 2014-12-30 16:36:29 -05:00
parent b49b0864d3
commit a0279b1378
7 changed files with 287 additions and 112 deletions

View File

@ -4,7 +4,6 @@ body {
float: left;
font: 13px sans-serif;
margin: 0;
overflow: hidden;
padding: 0;
white-space: nowrap;
}
@ -32,11 +31,19 @@ a {
body > div {
background-color: transparent;
display: inline-block;
position: relative;
vertical-align: top;
}
body > div:nth-of-type(1) {
direction: rtl; /* scroll bar to the left */
overflow-y: hidden;
overflow-x: hidden;
}
body.dynamicFilteringEnabled > div:nth-of-type(1) {
overflow-y: auto;
}
body > div:nth-of-type(2) {
padding: 4px 12px 0 5px;
position: relative;
}
p {
margin: 16px 0;
@ -131,39 +138,51 @@ body.dynamicFilteringEnabled #dynamicFilteringToggler::before {
#dynamicFilteringContainer {
border: 0;
direction: rtl;
font-size: 12px;
margin: 0;
overflow: hidden;
padding: 0;
text-align: right;
width: 7px;
}
body.dynamicFilteringEnabled #dynamicFilteringContainer {
display: block;
width: 200px;
width: 280px;
}
#dynamicFilteringContainer > div {
background-color: transparent;
border: 0;
direction: ltr;
margin: 0;
padding: 0;
width: 200px;
width: 280px;
}
body.dynamicFilteringEnabled #dynamicFilteringContainer > div {
background-color: #e6e6e6;
}
body.dynamicFilteringEnabled #dynamicFilteringContainer > div:hover {
background-color: #f0f0f0;
}
#dynamicFilteringContainer > div#privacyInfo {
background-color: white;
color: gray;
padding: 4px 0;
text-align: center;
}
#dynamicFilteringContainer > div > span {
background-color: transparent;
border: none;
border-bottom: 1px solid white;
box-sizing: border-box;
color: gray;
color: transparent;
display: inline-block;
height: 2em;
line-height: 2em;
height: 24px;
line-height: 28px;
pointer-events: none;
vertical-align: top;
}
body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span {
background-color: #e6e6e6;
color: #000;
overflow: hidden;
pointer-events: auto;
}
#dynamicFilteringContainer > div > span:nth-of-type(1) {
@ -178,9 +197,14 @@ body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span {
#dynamicFilteringContainer > div > span:nth-of-type(3) {
border-left: 1px solid white;
cursor: pointer;
text-align: center;
width: 16%;
}
#dynamicFilteringContainer > div.isDomain > span:nth-of-type(1) {
font-weight: bold;
}
body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span:nth-of-type(3) {
color: #444;
pointer-events: auto;
}
#dynamicFilteringContainer span.blocked[data-src] {
@ -188,7 +212,9 @@ body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span:nth-of-type
}
#dynamicFilteringContainer span.ownFilter[data-src] {
background-color: #bbb;
color: white;
}
#dynamicFilteringContainer span.blocked.ownFilter[data-src] {
background-color: #f66;
color: white;
}

View File

@ -264,6 +264,12 @@ Matrix.prototype.mustBlockOrAllow = function() {
/******************************************************************************/
Matrix.prototype.mustAbort = function() {
return this.r === 3;
};
/******************************************************************************/
Matrix.prototype.toFilterString = function() {
if ( this.type === '' ) {
return '';

View File

@ -103,46 +103,96 @@ var µb = µBlock;
/******************************************************************************/
var getDynamicFilterResults = function(scope) {
var getHostnameDict = function(hostnameToCountMap) {
var r = {};
var dFiltering = µb.dynamicNetFilteringEngine;
r['image'] = dFiltering.evaluateCellZY(scope, '*', 'image').toFilterString();
r['inline-script'] = dFiltering.evaluateCellZY(scope, '*', 'inline-script').toFilterString();
r['1p-script'] = dFiltering.evaluateCellZY(scope, '*', '1p-script').toFilterString();
r['3p-script'] = dFiltering.evaluateCellZY(scope, '*', '3p-script').toFilterString();
r['3p-frame'] = dFiltering.evaluateCellZY(scope, '*', '3p-frame').toFilterString();
var µburi = µb.URI;
var domain, counts;
for ( var hostname in hostnameToCountMap ) {
if ( hostnameToCountMap.hasOwnProperty(hostname) === false ) {
continue;
}
if ( r.hasOwnProperty(hostname) ) {
continue;
}
domain = µburi.domainFromHostname(hostname);
counts = hostnameToCountMap[domain] || 0;
r[domain] = {
domain: domain,
blockCount: counts & 0xFFFF,
allowCount: counts >>> 16 & 0xFFFF
};
if ( hostname === domain ) {
continue;
}
counts = hostnameToCountMap[hostname] || 0;
r[hostname] = {
domain: domain,
blockCount: counts & 0xFFFF,
allowCount: counts >>> 16 & 0xFFFF
};
}
return r;
};
/******************************************************************************/
var getStats = function(tab) {
var getDynamicFilterRules = function(srcHostname, desHostnames) {
var r = {};
var dFiltering = µb.dynamicNetFilteringEngine;
r['/ * image'] = dFiltering.evaluateCellZY('*', '*', 'image').toFilterString();
r['/ * inline-script'] = dFiltering.evaluateCellZY('*', '*', 'inline-script').toFilterString();
r['/ * 1p-script'] = dFiltering.evaluateCellZY('*', '*', '1p-script').toFilterString();
r['/ * 3p-script'] = dFiltering.evaluateCellZY('*', '*', '3p-script').toFilterString();
r['/ * 3p-frame'] = dFiltering.evaluateCellZY('*', '*', '3p-frame').toFilterString();
if ( typeof srcHostname !== 'string' ) {
return r;
}
r['. * image'] = dFiltering.evaluateCellZY(srcHostname, '*', 'image').toFilterString();
r['. * inline-script'] = dFiltering.evaluateCellZY(srcHostname, '*', 'inline-script').toFilterString();
r['. * 1p-script'] = dFiltering.evaluateCellZY(srcHostname, '*', '1p-script').toFilterString();
r['. * 3p-script'] = dFiltering.evaluateCellZY(srcHostname, '*', '3p-script').toFilterString();
r['. * 3p-frame'] = dFiltering.evaluateCellZY(srcHostname, '*', '3p-frame').toFilterString();
for ( var desHostname in desHostnames ) {
if ( desHostnames.hasOwnProperty(desHostname) ) {
r['/ ' + desHostname + ' *'] = dFiltering.evaluateCellZY('*', desHostname, '*').toFilterString();
r['. ' + desHostname + ' *'] = dFiltering.evaluateCellZY(srcHostname, desHostname, '*').toFilterString();
}
}
return r;
};
/******************************************************************************/
var getStats = function(tabId) {
var r = {
appName: vAPI.app.name,
appVersion: vAPI.app.version,
globalBlockedRequestCount: µb.localSettings.blockedRequestCount,
globalAllowedRequestCount: µb.localSettings.allowedRequestCount,
tabId: 0,
tabId: tabId,
pageURL: '',
pageBlockedRequestCount: 0,
pageAllowedRequestCount: 0,
netFilteringSwitch: false,
cosmeticFilteringSwitch: false,
logRequests: µb.userSettings.logRequests,
dynamicFilteringEnabled: µb.userSettings.dynamicFilteringEnabled,
dynamicFilterResults: {
'*': getDynamicFilterResults('*')
}
dynamicFilteringEnabled: µb.userSettings.dynamicFilteringEnabled
};
var pageStore = tab && µb.pageStoreFromTabId(tab.id);
var pageStore = µb.pageStoreFromTabId(tabId);
if ( pageStore ) {
r.tabId = tab.id;
r.pageURL = pageStore.pageURL;
r.pageDomain = pageStore.pageDomain;
r.pageHostname = pageStore.pageHostname;
r.pageBlockedRequestCount = pageStore.perLoadBlockedRequestCount;
r.pageAllowedRequestCount = pageStore.perLoadAllowedRequestCount;
r.netFilteringSwitch = pageStore.getNetFilteringSwitch();
r.dynamicFilterResults['local'] = getDynamicFilterResults(r.pageHostname);
r.hostnameDict = getHostnameDict(pageStore.hostnameToCountMap);
r.dynamicFilterRules = getDynamicFilterRules(pageStore.pageHostname, r.hostnameDict);
} else {
r.hostnameDict = {};
r.dynamicFilterRules = getDynamicFilterRules();
}
return r;
};
@ -153,7 +203,10 @@ var onMessage = function(request, sender, callback) {
// Async
switch ( request.what ) {
case 'activeTabStats':
vAPI.tabs.get(null, function(tab) { callback(getStats(tab)); });
vAPI.tabs.get(null, function(tab) {
var tabId = tab && tab.id;
callback(getStats(tabId));
});
return;
default:
@ -182,10 +235,7 @@ var onMessage = function(request, sender, callback) {
case 'toggleDynamicFilter':
µb.toggleDynamicFilter(request);
response = { '*': getDynamicFilterResults('*') };
if ( request.pageHostname ) {
response['local'] = getDynamicFilterResults(request.pageHostname);
}
response = getStats(request.tabId);
break;
default:

View File

@ -296,16 +296,6 @@ PageStore.factory = function(tabId, pageURL) {
/******************************************************************************/
PageStore.prototype.bitFromResult = {
'': 1,
'sb': 2,
'sa': 4,
'db': 8,
'da': 16
};
/******************************************************************************/
PageStore.prototype.init = function(tabId, pageURL) {
this.tabId = tabId;
this.previousPageURL = '';
@ -321,7 +311,7 @@ PageStore.prototype.init = function(tabId, pageURL) {
// This is part of the filtering evaluation context
this.requestURL = this.requestHostname = this.requestType = '';
this.requestHostnames = {};
this.hostnameToCountMap = {};
this.frames = {};
this.netFiltering = true;
this.netFilteringReadTime = 0;
@ -381,7 +371,7 @@ PageStore.prototype.dispose = function() {
this.pageHostname = this.pageDomain =
this.rootHostname = this.rootDomain =
this.requestURL = this.requestHostname = this.requestType = '';
this.requestHostnames = null;
this.hostnameToCountMap = null;
this.disposeFrameStores();
this.netFilteringCache = this.netFilteringCache.dispose();
if ( pageStoreJunkyard.length < pageStoreJunkyardMax ) {
@ -451,12 +441,15 @@ PageStore.prototype.filterRequest = function(context) {
this.recordResult(context.requestType, requestURL, result);
var requestHostname = context.requestHostname;
if ( this.requestHostnames.hasOwnProperty(requestHostname) ) {
this.requestHostnames[requestHostname] |= this.bitFromResult[result.slice(0, 2)];
} else {
this.requestHostnames[requestHostname] = this.bitFromResult[result.slice(0, 2)];
if ( this.hostnameToCountMap.hasOwnProperty(requestHostname) === false ) {
this.hostnameToCountMap[requestHostname] = 0;
}
var c = result.charAt(1);
if ( c === '' || c === 'a' ) {
this.hostnameToCountMap[requestHostname] += 0x00010000;
} else /* if ( c === 'b' ) */ {
this.hostnameToCountMap[requestHostname] += 0x00000001;
}
return result;
};

View File

@ -30,6 +30,24 @@
/******************************************************************************/
var stats;
var dynaTypes = [
'image',
'inline-script',
'1p-script',
'3p-script',
'3p-frame'
];
var popupHeight;
var reIP = /^\d+(?:\.\d+){1,3}$/;
var reSrcHostnameFromResult = /^d[abn]:([^ ]+) ([^ ]+)/;
var touchedDomains = {};
var scopeToSrcHostnameMap = {
'/': '*',
'.': ''
};
var threePlus = '+++';
var threeMinus = '\u2012\u2012\u2012';
var sixSpace = '\u2007\u2007\u2007\u2007\u2007\u2007';
/******************************************************************************/
@ -37,6 +55,15 @@ var stats;
var messager = vAPI.messaging.channel('popup.js');
/******************************************************************************/
var cachePopupData = function(data) {
if ( data ) {
stats = data;
scopeToSrcHostnameMap['.'] = data.pageHostname || '';
}
return data;
};
/******************************************************************************/
@ -49,61 +76,126 @@ var formatNumber = function(count) {
/******************************************************************************/
var syncDynamicFilter = function(scope, des, type, result) {
var el = uDom('span[data-src="' + scope + '"][data-des="' + des + '"][data-type="' + type + '"]');
var blocked = result.charAt(1) === 'b';
el.toggleClass('blocked', blocked);
// https://github.com/gorhill/uBlock/issues/340
// Use dark shade visual cue if the filter is specific to the page hostname
// or one of the ancestor hostname.
var ownFilter = false;
var matches = /^d[abn]:([^ ]+)/.exec(result);
if ( matches !== null ) {
var thisSrc = scope === 'local' ? stats.pageHostname : '*';
var otherSrc = matches[1];
ownFilter = thisSrc.slice(0 - otherSrc.length) === thisSrc;
if ( ownFilter && thisSrc.length !== otherSrc.length ) {
var c = thisSrc.substr(0 - otherSrc.length - 1, 1);
ownFilter = c === '' || c === '.';
}
var rulekeyCompare = function(a, b) {
var ha = a.slice(2, a.indexOf(' ', 2));
if ( !reIP.test(ha) ) {
ha = ha.split('.').reverse().join('.').replace(reRulekeyCompareNoise, '~');
}
el.toggleClass('ownFilter', ownFilter);
var hb = b.slice(2, b.indexOf(' ', 2));
if ( !reIP.test(hb) ) {
hb = hb.split('.').reverse().join('.').replace(reRulekeyCompareNoise, '~');
}
return ha.localeCompare(hb);
};
var reRulekeyCompareNoise = /[^a-z0-9.]/g;
/******************************************************************************/
var addDynamicFilterRow = function(des) {
var row = uDom('#templates > div:nth-of-type(1)').clone();
row.descendants('[data-des]').attr('data-des', des);
row.descendants('div > span:nth-of-type(1)').text(des);
var hnDetails = stats.hostnameDict[des] || {};
var isDomain = des === hnDetails.domain;
row.toggleClass('isDomain', isDomain);
if ( hnDetails.allowCount !== 0 ) {
touchedDomains[hnDetails.domain] = true;
}
row.appendTo('#dynamicFilteringContainer');
// Hacky? I couldn't figure a CSS recipe for this problem.
// I do not want the left pane -- optional and hidden by defaut -- to
// dictate the height of the popup. The right pane dictates the height
// of the popup, and the left pane will have a scrollbar if ever its
// height is larger than what is available.
if ( popupHeight === undefined ) {
popupHeight = uDom('body > div:nth-of-type(2)').nodeAt(0).offsetHeight;
uDom('body > div:nth-of-type(1)').css('height', popupHeight + 'px');
}
return row;
};
/******************************************************************************/
var syncDynamicFilter = function(scope, des, type, result) {
var selector = '#dynamicFilteringContainer span[data-src="' + scope + '"][data-des="' + des + '"][data-type="' + type + '"]';
var cell = uDom(selector);
// Create the row?
if ( cell.length === 0 ) {
cell = addDynamicFilterRow(des).descendants(selector);
}
var blocked = result.charAt(1) === 'b';
cell.toggleClass('blocked', blocked);
// Use dark shade visual cue if the filter is specific to the cell.
var ownFilter = false;
var matches = reSrcHostnameFromResult.exec(result);
if ( matches !== null ) {
ownFilter = matches[2] === des &&
matches[1] === scopeToSrcHostnameMap[scope];
}
cell.toggleClass('ownFilter', ownFilter);
if ( scope !== '.' || type !== '*' ) {
return;
}
if ( stats.hostnameDict.hasOwnProperty(des) === false ) {
return;
}
var hnDetails = stats.hostnameDict[des];
var aCount = Math.min(Math.ceil(Math.log10(hnDetails.allowCount + 1)), 3);
var bCount = Math.min(Math.ceil(Math.log10(hnDetails.blockCount + 1)), 3);
cell.text(
threePlus.slice(0, aCount) +
sixSpace.slice(aCount + bCount) +
threeMinus.slice(0, bCount)
);
};
/******************************************************************************/
var syncAllDynamicFilters = function() {
var hasBlock = false;
var scopes = ['*', 'local'];
var scope, results, i, result;
while ( scope = scopes.pop() ) {
if ( stats.dynamicFilterResults.hasOwnProperty(scope) === false ) {
var rules = stats.dynamicFilterRules;
var type, result;
var types = dynaTypes;
var i = types.length;
while ( i-- ) {
type = types[i];
syncDynamicFilter('/', '*', type, rules['/ * ' + type] || '');
result = rules['. * ' + type] || '';
if ( result.charAt(1) === 'b' ) {
hasBlock = true;
}
syncDynamicFilter('.', '*', type, result);
}
// Sort hostnames. First-party hostnames must always appear at the top
// of the list.
var keys = Object.keys(rules).sort(rulekeyCompare);
var key;
for ( var i = 0; i < keys.length; i++ ) {
key = keys[i];
// Specific-type rules -- they were processed above
if ( key.slice(-1) !== '*' ) {
continue;
}
results = stats.dynamicFilterResults[scope];
for ( var type in results ) {
if ( results.hasOwnProperty(type) === false ) {
continue;
}
result = results[type];
syncDynamicFilter(scope, '*', type, result);
if ( scope === 'local' && result.charAt(1) === 'b' ) {
hasBlock = true;
}
}
syncDynamicFilter(key.charAt(0), key.slice(2, key.indexOf(' ', 2)), '*', rules[key]);
}
uDom('body').toggleClass('hasDynamicBlock', hasBlock);
uDom('#privacyInfo > b').text(Object.keys(touchedDomains).length);
};
/******************************************************************************/
var renderPopup = function(details) {
if ( details ) {
stats = details;
}
if ( !stats ) {
if ( !cachePopupData(details) ) {
return;
}
@ -246,19 +338,20 @@ var onDynamicFilterClicked = function(ev) {
if ( typeof stats.pageHostname !== 'string' || stats.pageHostname === '' ) {
return;
}
var elFilter = uDom(ev.target);
var scope = elFilter.attr('data-src') === '*' ? '*' : stats.pageHostname;
var cell = uDom(ev.target);
var scope = cell.attr('data-src') === '/' ? '*' : stats.pageHostname;
var onDynamicFilterChanged = function(details) {
stats.dynamicFilterResults = details;
cachePopupData(details);
syncAllDynamicFilters();
};
messager.send({
what: 'toggleDynamicFilter',
tabId: stats.tabId,
pageHostname: stats.pageHostname,
srcHostname: scope,
desHostname: elFilter.attr('data-des'),
requestType: elFilter.attr('data-type'),
block: elFilter.hasClassName('blocked') === false
desHostname: cell.attr('data-des'),
requestType: cell.attr('data-type'),
block: cell.hasClass('blocked') === false
}, onDynamicFilterChanged);
};
@ -270,7 +363,7 @@ var toggleDynamicFiltering = function(ev) {
messager.send({
what: 'userSettings',
name: 'dynamicFilteringEnabled',
value: el.hasClassName('dynamicFilteringEnabled')
value: el.hasClass('dynamicFilteringEnabled')
});
};

View File

@ -366,22 +366,24 @@ var matchWhitelistDirective = function(url, hostname, directive) {
// Dynamic filters:
// 3. specific source, any destination, specific type, allow/block
// 4. any source, any destination, specific type, allow/block
if ( requestType === 'script' ) {
df.evaluateCellZY(rootHostname, requestHostname, this.isFirstParty(rootHostname, requestHostname) ? '1p-script' : '3p-script');
if ( df.mustBlockOrAllow() ) {
return df.toFilterString();
}
} else if ( requestType === 'sub_frame' ) {
if ( this.isFirstParty(rootHostname, requestHostname) === false ) {
df.evaluateCellZY(rootHostname, requestHostname, '3p-frame');
if ( df.mustAbort() === false ) {
if ( requestType === 'script' ) {
df.evaluateCellZY(rootHostname, requestHostname, this.isFirstParty(rootHostname, requestHostname) ? '1p-script' : '3p-script');
if ( df.mustBlockOrAllow() ) {
return df.toFilterString();
}
} else if ( requestType === 'sub_frame' ) {
if ( this.isFirstParty(rootHostname, requestHostname) === false ) {
df.evaluateCellZY(rootHostname, requestHostname, '3p-frame');
if ( df.mustBlockOrAllow() ) {
return df.toFilterString();
}
}
} else {
df.evaluateCellZY(rootHostname, requestHostname, requestType);
if ( df.mustBlockOrAllow() ) {
return df.toFilterString();
}
}
} else {
df.evaluateCellZY(rootHostname, requestHostname, requestType);
if ( df.mustBlockOrAllow() ) {
return df.toFilterString();
}
}

View File

@ -12,11 +12,12 @@
<h4 title="popupTipDashboard">v<span id="version"></span></h4>
<div>
<div id="dynamicFilteringContainer">
<div><span>images</span><span data-src="*" data-des="*" data-type="image"></span><span data-src="local" data-des="*" data-type="image"></span></div>
<div><span>inline scripts</span><span data-src="*" data-des="*" data-type="inline-script"></span><span data-src="local" data-des="*" data-type="inline-script"></span></div>
<div><span>1st-party scripts</span><span data-src="*" data-des="*" data-type="1p-script"></span><span data-src="local" data-des="*" data-type="1p-script"></span></div>
<div><span>3rd-party scripts</span><span data-src="*" data-des="*" data-type="3p-script"></span><span data-src="local" data-des="*" data-type="3p-script"></span></div>
<div><span>3rd-party frames</span><span data-src="*" data-des="*" data-type="3p-frame"></span><span data-src="local" data-des="*" data-type="3p-frame"></span></div>
<div><span>images</span><span data-src="/" data-des="*" data-type="image"></span><span data-src="." data-des="*" data-type="image"></span></div>
<div><span>inline scripts</span><span data-src="/" data-des="*" data-type="inline-script"></span><span data-src="." data-des="*" data-type="inline-script"></span></div>
<div><span>1st-party scripts</span><span data-src="/" data-des="*" data-type="1p-script"></span><span data-src="." data-des="*" data-type="1p-script"></span></div>
<div><span>3rd-party scripts</span><span data-src="/" data-des="*" data-type="3p-script"></span><span data-src="." data-des="*" data-type="3p-script"></span></div>
<div><span>3rd-party frames</span><span data-src="/" data-des="*" data-type="3p-frame"></span><span data-src="." data-des="*" data-type="3p-frame"></span></div>
<div id="privacyInfo"><b>?</b> distinct domains touched</div>
</div>
</div><!-- DO NOT REMOVE --><div>
<p id="switch" data-i18n-tip="popupPowerSwitchInfo"><span class="fa">&#xf011;</span></p>
@ -32,6 +33,10 @@
<p id="total-blocked">?</p>
</div>
<div id="templates" style="display: none">
<div><span></span><span data-src="/" data-des="" data-type="*"></span><span data-src="." data-des="" data-type="*"></span></div>
<div>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script>