1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-09-04 10:09:38 +02:00

too many changes for #433: branching so that I can commit and keep working on it

This commit is contained in:
gorhill 2014-12-28 10:07:43 -05:00
parent aed66e5278
commit 1fe7045b92
16 changed files with 931 additions and 633 deletions

View File

@ -16,7 +16,8 @@
<script src="js/liquid-dict.js"></script>
<script src="js/utils.js"></script>
<script src="js/assets.js"></script>
<script src="js/net-filtering.js"></script>
<script src="js/dynamic-net-filtering.js"></script>
<script src="js/static-net-filtering.js"></script>
<script src="js/cosmetic-filtering.js"></script>
<script src="js/ublock.js"></script>
<script src="js/messaging.js"></script>

View File

@ -1,12 +1,12 @@
body {
margin: 0;
border: 0;
padding: 0;
font: 13px sans-serif;
background-color: white;
min-width: 180px;
overflow: hidden;
border: 0;
float: left;
font: 13px sans-serif;
margin: 0;
overflow: hidden;
padding: 0;
white-space: nowrap;
}
h1,h2,h3,h4 {
margin: 0;
@ -30,7 +30,13 @@ a {
font-size: 11px;
}
body > div {
padding: 4px 12px 0 12px;
background-color: transparent;
display: inline-block;
vertical-align: top;
}
body > div:nth-of-type(2) {
padding: 4px 12px 0 5px;
position: relative;
}
p {
margin: 16px 0;
@ -53,6 +59,9 @@ p {
font-size: 11px;
color: #888;
}
[data-i18n="popupBlockedRequestPrompt"] {
font-size: 16px;
}
#page-blocked {
margin-top: 4px;
font-size: 20px;
@ -80,6 +89,20 @@ p {
color: #444;
}
#dynamicFilteringToggler {
pointer-events: none;
}
#dynamicFilteringToggler::before {
color: gray;
content: '+\202F';
cursor: pointer;
font-size: 13px;
pointer-events: auto;
}
body.dynamicFilteringEnabled #dynamicFilteringToggler::before {
content: '\2212\202F';
}
.dynamicFiltering div > .tip {
background-color: #fffff4;
border: 1px solid #888;
@ -105,157 +128,67 @@ p {
font: normal monospace;
padding: 1px 0;
}
#dynamicFilteringToggler {
margin: 0;
border: 0;
padding: 0;
width: 100%;
text-align: center;
cursor: pointer;
position: relative;
}
#dynamicFilteringToggler.hasBlock:not(.on) {
background-color: #fbb;
}
#dynamicFilteringToggler > div {
font-size: 9px;
line-height: 100%;
color: #888;
position: absolute;
bottom: 0;
width: 25%;
display: none;
pointer-events: none;
}
#dynamicFilteringToggler.on:hover > div {
display: initial;
}
#dynamicFilteringToggler > div:nth-of-type(1) {
left: 0;
}
#dynamicFilteringToggler > div:nth-of-type(2) {
left: 25%;
}
#dynamicFilteringToggler > div:nth-of-type(3) {
left: 50%;
}
#dynamicFilteringToggler > div:nth-of-type(4) {
left: 75%;
}
#dynamicFilteringToggler > a {
padding: 0 2px 0 8px;
position: absolute;
right: 0;
color: #666;
visibility: hidden;
}
body[dir=rtl] #dynamicFilteringToggler > a {
right: auto;
left: 0;
}
#dynamicFilteringToggler a:hover {
color: #444;
}
#dynamicFilteringToggler.on:hover a {
visibility: visible;
}
#dynamicFilteringToggler::before {
content: '\f107';
}
#dynamicFilteringToggler.on::before {
content: '\f106';
}
#dynamicFilteringContainer {
margin: 0;
border: 0;
padding: 0;
display: none;
}
#dynamicFilteringToggler.on + #dynamicFilteringContainer {
display: initial;
}
.dynamicFiltering {
direction: rtl;
font-size: 12px;
margin: 0;
overflow: hidden;
padding: 0;
text-align: right;
width: 7px;
}
body.dynamicFilteringEnabled #dynamicFilteringContainer {
display: block;
width: 200px;
}
#dynamicFilteringContainer > div {
border: 0;
padding: 0;
height: 2.75em;
position: relative;
}
.dynamicFiltering.local {
border-bottom: 1px solid white;
}
.dynamicFiltering.global {
height: 1.25em;
}
.dynamicFiltering > div {
direction: ltr;
margin: 0;
border: 1px solid #e6e6e6;
padding: 0;
box-sizing: border-box;
background-color: #e6e6e6;
position: absolute;
cursor: pointer;
width: 200px;
}
.dynamicFiltering > div:not(:first-child) {
border-left: 1px solid white !important;
}
.dynamicFiltering > div:nth-of-type(1) {
left: 0;
width: 7%;
height: 100%;
}
.dynamicFiltering > div:nth-of-type(2) {
left: 7%;
width: 18%;
height: 100%;
}
.dynamicFiltering > div:nth-of-type(3) {
left: 25%;
width: 25%;
height: 100%;
}
.dynamicFiltering > div:nth-of-type(4) {
left: 50%;
width: 25%;
height: 100%;
}
.dynamicFiltering > div:nth-of-type(5) {
left: 75%;
width: 25%;
height: 100%;
}
.dynamicFiltering > div:nth-of-type(6) {
left: 0;
width: 50%;
}
.dynamicFiltering > div:nth-of-type(7) {
left: 50%;
width: 50%;
}
.dynamicFiltering > div.label {
margin: 0;
border: 0;
padding: 0;
pointer-events: none;
color: black;
opacity: 0.4;
font: 12px monospace;
text-align: center;
top: 50%;
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
#dynamicFilteringContainer > div > span {
background-color: transparent;
border: none;
border-bottom: 1px solid white;
box-sizing: border-box;
color: gray;
display: inline-block;
height: 2em;
line-height: 2em;
pointer-events: none;
vertical-align: top;
}
.dynamicFiltering > div.blocked {
border-color: #fbb;
body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span {
background-color: #e6e6e6;
pointer-events: auto;
}
#dynamicFilteringContainer > div > span:nth-of-type(1) {
border-right: 1px solid white;
padding-right: 4px;
width: 75%;
}
#dynamicFilteringContainer > div > span:nth-of-type(2) {
cursor: pointer;
width: 9%;
}
#dynamicFilteringContainer > div > span:nth-of-type(3) {
border-left: 1px solid white;
cursor: pointer;
width: 16%;
}
body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span:nth-of-type(3) {
pointer-events: auto;
}
#dynamicFilteringContainer span.blocked[data-src] {
background-color: #fbb;
}
.dynamicFiltering > div.ownFilter {
border-color: #bbb;
#dynamicFilteringContainer span.ownFilter[data-src] {
background-color: #bbb;
}
.dynamicFiltering > div.blocked.ownFilter {
border-color: #f66;
#dynamicFilteringContainer span.blocked.ownFilter[data-src] {
background-color: #f66;
}
}

View File

@ -55,6 +55,7 @@ return {
autoUpdate: true,
collapseBlocked: true,
contextMenuEnabled: true,
dynamicFilteringString: '',
dynamicFilteringSelfie: '',
dynamicFilteringEnabled: false,
experimentalEnabled: false,
@ -106,7 +107,7 @@ return {
firstUpdateAfter: 5 * oneMinute,
nextUpdateAfter: 7 * oneHour,
selfieMagic: 'odyxfmbsqllh',
selfieMagic: 'qidcglrwobsm',
selfieAfter: 7 * oneMinute,
pageStores: {},

View File

@ -216,7 +216,7 @@ var messager = vAPI.messaging.channel('contentscript-end.js');
var selector;
while ( i-- ) {
selector = generics[i];
if ( injectedSelectors[selector] !== undefined ) {
if ( injectedSelectors.hasOwnProperty(selector) ) {
continue;
}
injectedSelectors[selector] = true;
@ -241,14 +241,14 @@ var messager = vAPI.messaging.channel('contentscript-end.js');
if ( !attrValue ) { continue; }
selector = '[' + attr + '="' + attrValue + '"]';
if ( generics[selector] ) {
if ( injectedSelectors[selector] === undefined ) {
if ( injectedSelectors.hasOwnProperty(selector) === false ) {
injectedSelectors[selector] = true;
out.push(selector);
}
}
selector = node.tagName.toLowerCase() + selector;
if ( generics[selector] ) {
if ( injectedSelectors[selector] === undefined ) {
if ( injectedSelectors.hasOwnProperty(selector) === false ) {
injectedSelectors[selector] = true;
out.push(selector);
}
@ -277,7 +277,7 @@ var messager = vAPI.messaging.channel('contentscript-end.js');
iSelector = selectors.length;
while ( iSelector-- ) {
selector = selectors[iSelector];
if ( injectedSelectors[selector] === undefined ) {
if ( injectedSelectors.hasOwnProperty(selector) === false ) {
injectedSelectors[selector] = true;
out.push(selector);
}
@ -293,7 +293,7 @@ var messager = vAPI.messaging.channel('contentscript-end.js');
var processHighHighGenerics = function() {
processHighHighGenericsTimer = null;
if ( injectedSelectors['{{highHighGenerics}}'] !== undefined ) { return; }
if ( injectedSelectors.hasOwnProperty('{{highHighGenerics}}') ) { return; }
if ( document.querySelector(highGenerics.hideHigh) === null ) { return; }
injectedSelectors['{{highHighGenerics}}'] = true;
// We need to filter out possible exception cosmetic filters from
@ -443,10 +443,10 @@ var messager = vAPI.messaging.channel('contentscript-end.js');
}
}
if ( addedNodeListsTimer === null ) {
// I arbitrarily chose 50 ms for now:
// I arbitrarily chose 100 ms for now:
// I have to compromise between the overhead of processing too few
// nodes too often and the delay of many nodes less often.
addedNodeListsTimer = setTimeout(mutationObservedHandler, 75);
addedNodeListsTimer = setTimeout(mutationObservedHandler, 100);
}
};

View File

@ -0,0 +1,485 @@
/*******************************************************************************
µBlock - a Chromium browser extension to black/white list requests.
Copyright (C) 2014 Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
*/
/* global punycode, µBlock */
/* jshint bitwise: false */
/******************************************************************************/
µBlock.dynamicNetFilteringEngine = (function() {
/******************************************************************************/
var magicId = 'chmdgxwtetgu';
/******************************************************************************/
var Matrix = function() {
this.reset();
};
/******************************************************************************/
var typeBitOffsets = {
'*': 0,
'inline-script': 2,
'1p-script': 4,
'3p-script': 6,
'3p-frame': 8,
'image': 10
};
var stateToNameMap = {
'1': 'block',
'2': 'allow',
'3': 'noop'
};
var nameToStateMap = {
'block': 1,
'allow': 2,
'noop': 3
};
/******************************************************************************/
// For performance purpose, as simple tests as possible
var reHostnameVeryCoarse = /[g-z_-]/;
var reIPv4VeryCoarse = /\.\d+$/;
// http://tools.ietf.org/html/rfc5952
// 4.3: "MUST be represented in lowercase"
// Also: http://en.wikipedia.org/wiki/IPv6_address#Literal_IPv6_addresses_in_network_resource_identifiers
var isIPAddress = function(hostname) {
if ( reHostnameVeryCoarse.test(hostname) ) {
return false;
}
if ( reIPv4VeryCoarse.test(hostname) ) {
return true;
}
return hostname.charAt(0) === '[';
};
/******************************************************************************/
var toBroaderHostname = function(hostname) {
if ( hostname === '*' ) {
return '';
}
if ( isIPAddress(hostname) ) {
return '*';
}
var pos = hostname.indexOf('.');
if ( pos === -1 ) {
return '*';
}
return hostname.slice(pos + 1);
};
Matrix.toBroaderHostname = toBroaderHostname;
/******************************************************************************/
Matrix.prototype.reset = function() {
this.r = 0;
this.type = '';
this.y = '';
this.z = '';
this.rules = {};
};
/******************************************************************************/
Matrix.prototype.setCell = function(srcHostname, desHostname, type, state) {
var bitOffset = typeBitOffsets[type];
var k = srcHostname + ' ' + desHostname;
var oldBitmap = this.rules[k];
if ( oldBitmap === undefined ) {
oldBitmap = 0;
}
var newBitmap = oldBitmap & ~(3 << bitOffset) | (state << bitOffset);
if ( newBitmap === oldBitmap ) {
return false;
}
if ( newBitmap === 0 ) {
delete this.rules[k];
} else {
this.rules[k] = newBitmap;
}
return true;
};
/******************************************************************************/
Matrix.prototype.blockCell = function(srcHostname, desHostname, type) {
this.evaluateCellZY(srcHostname, desHostname, type);
if ( this.r === 1 ) {
return false;
}
this.setCell(srcHostname, desHostname, type, 0);
this.evaluateCellZY(srcHostname, desHostname, type);
if ( this.r === 1 ) {
return true;
}
this.setCell(srcHostname, desHostname, type, 1);
return true;
};
// https://www.youtube.com/watch?v=Csewb_eIStY
/******************************************************************************/
Matrix.prototype.allowCell = function(srcHostname, desHostname, type) {
this.evaluateCellZY(srcHostname, desHostname, type);
if ( this.r === 2 ) {
return false;
}
this.setCell(srcHostname, desHostname, type, 0);
this.evaluateCellZY(srcHostname, desHostname, type);
if ( this.r === 2 ) {
return true;
}
this.setCell(srcHostname, desHostname, type, 2);
return true;
};
/******************************************************************************/
Matrix.prototype.unsetCell = function(srcHostname, desHostname, type) {
this.evaluateCellZY(srcHostname, desHostname, type);
if ( this.r === 0 ) {
return false;
}
this.setCell(srcHostname, desHostname, type, 0);
this.evaluateCellZY(srcHostname, desHostname, type);
if ( this.r === 0 || this.r === 3 ) {
return true;
}
this.setCell(srcHostname, desHostname, type, 3);
return true;
};
/******************************************************************************/
Matrix.prototype.evaluateCell = function(srcHostname, desHostname, type) {
var key = srcHostname + ' ' + desHostname;
var bitmap = this.rules[key];
if ( bitmap === undefined ) {
return 0;
}
return bitmap >> typeBitOffsets[type] & 3;
};
/******************************************************************************/
Matrix.prototype.clearRegisters = function() {
this.r = 0;
this.type = '';
this.y = '';
this.z = '';
};
/******************************************************************************/
Matrix.prototype.evaluateCellZ = function(srcHostname, desHostname, type) {
var bitOffset = typeBitOffsets[type];
var s = srcHostname;
var v;
for (;;) {
this.z = s;
v = this.rules[s + ' ' + desHostname];
if ( v !== undefined ) {
v = v >> bitOffset & 3;
if ( v !== 0 ) {
return v;
}
}
s = toBroaderHostname(s);
if ( s === '' ) {
break;
}
}
// srcHostname is '*' at this point
return 0;
};
/******************************************************************************/
Matrix.prototype.evaluateCellZY = function(srcHostname, desHostname, type) {
if ( typeBitOffsets.hasOwnProperty(type) === false ) {
this.type = '';
this.r = 0;
return this;
}
this.type = type;
// Specific-hostname specific-type cell
this.y = desHostname;
this.r = this.evaluateCellZ(srcHostname, desHostname, type);
if ( this.r !== 0 ) { return this; }
var d = desHostname;
for (;;) {
d = toBroaderHostname(d);
if ( d === '*' ) {
break;
}
// specific-hostname specific-type cell
this.y = d;
this.r = this.evaluateCellZ(srcHostname, d, type);
if ( this.r !== 0 ) { return this; }
}
// Any-hostname specific-type cells
this.y = '*';
this.r = this.evaluateCellZ(srcHostname, '*', type);
return this;
};
// http://youtu.be/gSGk1bQ9rcU?t=25m6s
/******************************************************************************/
Matrix.prototype.mustBlockOrAllow = function() {
return this.r === 1 || this.r === 2;
};
/******************************************************************************/
Matrix.prototype.toFilterString = function() {
if ( this.type === '' ) {
return '';
}
if ( this.r === 1 ) {
return 'db:' + this.z + ' ' + this.y + ' ' + this.type + ' block';
}
if ( this.r === 2 ) {
return 'da:' + this.z + ' ' + this.y + ' ' + this.type + ' allow';
}
if ( this.r === 3 ) {
return 'dn:' + this.z + ' ' + this.y + ' ' + this.type + ' noop';
}
return '';
};
/******************************************************************************/
Matrix.prototype.mustBlock = function(srcHostname, desHostname, type) {
this.evaluateCellZY(srcHostname, desHostname, type);
return this.r === 1;
};
/******************************************************************************/
Matrix.prototype.srcHostnameFromRule = function(rule) {
return rule.slice(0, rule.indexOf(' '));
};
/******************************************************************************/
Matrix.prototype.desHostnameFromRule = function(rule) {
return rule.slice(rule.indexOf(' ') + 1);
};
/******************************************************************************/
Matrix.prototype.toString = function() {
var out = [];
var rule, type, val;
var srcHostname, desHostname;
for ( rule in this.rules ) {
if ( this.rules.hasOwnProperty(rule) === false ) {
continue;
}
srcHostname = this.srcHostnameFromRule(rule);
desHostname = this.desHostnameFromRule(rule);
for ( type in typeBitOffsets ) {
if ( typeBitOffsets.hasOwnProperty(type) === false ) {
continue;
}
val = this.evaluateCell(srcHostname, desHostname, type);
if ( val === 0 ) {
continue;
}
out.push(
punycode.toUnicode(srcHostname) + ' ' +
punycode.toUnicode(desHostname) + ' ' +
type + ' ' +
stateToNameMap[val]
);
}
}
return out.join('\n');
};
/******************************************************************************/
Matrix.prototype.fromString = function(text, append) {
var textEnd = text.length;
var lineBeg = 0, lineEnd;
var line, pos;
var fields, fieldVal;
var srcHostname = '';
var desHostname = '';
var type, state;
while ( lineBeg < textEnd ) {
lineEnd = text.indexOf('\n', lineBeg);
if ( lineEnd < 0 ) {
lineEnd = text.indexOf('\r', lineBeg);
if ( lineEnd < 0 ) {
lineEnd = textEnd;
}
}
line = text.slice(lineBeg, lineEnd).trim();
lineBeg = lineEnd + 1;
pos = line.indexOf('# ');
if ( pos !== -1 ) {
line = line.slice(0, pos).trim();
}
if ( line === '' ) {
continue;
}
fields = line.split(/\s+/);
// Less than 2 fields makes no sense
if ( fields.length < 2 ) {
continue;
}
fieldVal = fields[0];
// Valid rule syntax:
// srcHostname desHostname [type [state]]
// type = a valid request type
// state = [`block`, `allow`, `inherit`]
// srcHostname desHostname type
// type = a valid request type
// state = `allow`
// srcHostname desHostname
// type = `*`
// state = `allow`
// Lines with invalid syntax silently ignored
srcHostname = punycode.toASCII(fields[0]);
desHostname = punycode.toASCII(fields[1]);
fieldVal = fields[2];
if ( fieldVal !== undefined ) {
type = fieldVal;
// Unknown type: reject
if ( typeBitOffsets.hasOwnProperty(type) === false ) {
continue;
}
} else {
type = '*';
}
fieldVal = fields[3];
if ( fieldVal !== undefined ) {
// Unknown state: reject
if ( nameToStateMap.hasOwnProperty(fieldVal) === false ) {
continue;
}
state = nameToStateMap[fieldVal];
} else {
state = 2;
}
this.setCell(srcHostname, desHostname, type, state);
}
};
/******************************************************************************/
Matrix.prototype.fromObsoleteSelfie = function(selfie) {
if ( selfie === '' ) {
return '';
}
var bin = JSON.parse(selfie);
var filters = bin.filters;
var bits, val;
for ( var hostname in filters ) {
if ( filters.hasOwnProperty(hostname) === false ) {
continue;
}
bits = filters[hostname];
val = bits & 3;
if ( val === 1 ) {
this.setCell(hostname, '*', 'inline-script', 1);
} else if ( val === 2 ) {
this.setCell(hostname, '*', 'inline-script', 3);
}
val = (bits >> 2) & 3;
if ( val === 1 ) {
this.setCell(hostname, '*', '1p-script', 1);
} else if ( val === 2 ) {
this.setCell(hostname, '*', '1p-script', 3);
}
val = (bits >> 4) & 3;
if ( val === 1 ) {
this.setCell(hostname, '*', '3p-script', 1);
} else if ( val === 2 ) {
this.setCell(hostname, '*', '3p-script', 3);
}
val = (bits >> 8) & 3;
if ( val === 1 ) {
this.setCell(hostname, '*', '3p-frame', 1);
} else if ( val === 2 ) {
this.setCell(hostname, '*', '3p-frame', 3);
}
}
};
/******************************************************************************/
Matrix.prototype.toSelfie = function() {
return {
magicId: magicId,
rules: this.rules
};
};
/******************************************************************************/
Matrix.prototype.fromSelfie = function(selfie) {
this.rules = selfie.rules;
};
/******************************************************************************/
return new Matrix;
/******************************************************************************/
// http://youtu.be/5-K8R1hDG9E?t=31m1s
})();
/******************************************************************************/

View File

@ -42,10 +42,6 @@ var onMessage = function(request, sender, callback) {
µb.assets.get(request.url, callback);
return;
case 'loadUbiquitousAllowRules':
µb.loadUbiquitousWhitelists();
return;
default:
break;
}
@ -108,13 +104,14 @@ var µb = µBlock;
/******************************************************************************/
var getDynamicFilterResults = function(scope) {
return [
µb.netFilteringEngine.matchDynamicFilters(scope, 'inline-script', true),
µb.netFilteringEngine.matchDynamicFilters(scope, 'script', true),
µb.netFilteringEngine.matchDynamicFilters(scope, 'script', false),
µb.netFilteringEngine.matchDynamicFilters(scope, 'sub_frame', true),
µb.netFilteringEngine.matchDynamicFilters(scope, 'sub_frame', false)
];
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();
return r;
};
/******************************************************************************/
@ -134,7 +131,7 @@ var getStats = function(tab) {
logRequests: µb.userSettings.logRequests,
dynamicFilteringEnabled: µb.userSettings.dynamicFilteringEnabled,
dynamicFilterResults: {
'/': getDynamicFilterResults('*')
'*': getDynamicFilterResults('*')
}
};
var pageStore = tab && µb.pageStoreFromTabId(tab.id);
@ -145,7 +142,7 @@ var getStats = function(tab) {
r.pageBlockedRequestCount = pageStore.perLoadBlockedRequestCount;
r.pageAllowedRequestCount = pageStore.perLoadAllowedRequestCount;
r.netFilteringSwitch = pageStore.getNetFilteringSwitch();
r.dynamicFilterResults['.'] = getDynamicFilterResults(r.pageHostname);
r.dynamicFilterResults['local'] = getDynamicFilterResults(r.pageHostname);
}
return r;
};
@ -185,9 +182,9 @@ var onMessage = function(request, sender, callback) {
case 'toggleDynamicFilter':
µb.toggleDynamicFilter(request);
response = { '/': getDynamicFilterResults('*') };
response = { '*': getDynamicFilterResults('*') };
if ( request.pageHostname ) {
response['.'] = getDynamicFilterResults(request.pageHostname);
response['local'] = getDynamicFilterResults(request.pageHostname);
}
break;
@ -281,9 +278,13 @@ var tagNameToRequestTypeMap = {
// Evaluate many requests
var filterRequests = function(pageStore, details) {
details.pageDomain = µb.URI.domainFromHostname(details.pageHostname);
var µburi = µb.URI;
// Create evaluation context
details.pageDomain = µburi.domainFromHostname(details.pageHostname);
details.rootHostname = pageStore.rootHostname;
details.rootDomain = pageStore.rootDomain;
details.requestHostname = '';
var inRequests = details.requests;
var outRequests = [];
@ -294,11 +295,10 @@ var filterRequests = function(pageStore, details) {
if ( tagNameToRequestTypeMap.hasOwnProperty(request.tagName) === false ) {
continue;
}
result = pageStore.filterRequest(
details,
tagNameToRequestTypeMap[request.tagName],
request.url
);
details.requestURL = request.url;
details.requestHostname = µburi.hostnameFromURI(request.url);
details.requestType = tagNameToRequestTypeMap[request.tagName];
result = pageStore.filterRequest(details);
if ( pageStore.boolFromResult(result) ) {
outRequests.push(request);
}
@ -317,14 +317,13 @@ var filterRequest = function(pageStore, details) {
if ( tagNameToRequestTypeMap.hasOwnProperty(details.tagName) === false ) {
return;
}
details.pageDomain = µb.URI.domainFromHostname(details.pageHostname);
var µburi = µb.URI;
details.pageDomain = µburi.domainFromHostname(details.pageHostname);
details.rootHostname = pageStore.rootHostname;
details.rootDomain = pageStore.rootDomain;
var result = pageStore.filterRequest(
details,
tagNameToRequestTypeMap[details.tagName],
details.requestURL
);
details.requestHostname = µburi.hostnameFromURI(details.requestURL);
details.requestType = tagNameToRequestTypeMap[details.tagName];
var result = pageStore.filterRequest(details);
if ( pageStore.boolFromResult(result) ) {
return { collapse: µb.userSettings.collapseBlocked };
}
@ -485,7 +484,7 @@ var getLists = function(callback) {
available: null,
current: µb.remoteBlacklists,
cosmetic: µb.userSettings.parseAllABPHideFilters,
netFilterCount: µb.netFilteringEngine.getFilterCount(),
netFilterCount: µb.staticNetFilteringEngine.getFilterCount(),
cosmeticFilterCount: µb.cosmeticFilteringEngine.getFilterCount(),
autoUpdate: µb.userSettings.autoUpdate,
userFiltersPath: µb.userFiltersPath,

View File

@ -245,6 +245,9 @@ FrameStore.prototype.init = function(rootHostname, frameURL) {
this.pageDomain = µburi.domainFromHostname(this.pageHostname) || this.pageHostname;
this.rootHostname = rootHostname;
this.rootDomain = µburi.domainFromHostname(rootHostname) || rootHostname;
// This is part of the filtering evaluation context
this.requestURL = this.requestHostname = this.requestType = '';
return this;
};
@ -252,7 +255,8 @@ FrameStore.prototype.init = function(rootHostname, frameURL) {
FrameStore.prototype.dispose = function() {
this.pageHostname = this.pageDomain =
this.rootHostname = this.rootDomain = '';
this.rootHostname = this.rootDomain =
this.requestURL = this.requestHostname = this.requestType = '';
if ( frameStoreJunkyard.length < frameStoreJunkyardMax ) {
frameStoreJunkyard.push(this);
}
@ -292,6 +296,16 @@ PageStore.factory = function(tabId, pageURL) {
/******************************************************************************/
PageStore.prototype.bitFromRequestType = {
'': 1,
'sb': 2,
'sa': 4,
'db': 8,
'da': 16
};
/******************************************************************************/
PageStore.prototype.init = function(tabId, pageURL) {
this.tabId = tabId;
this.previousPageURL = '';
@ -304,6 +318,10 @@ PageStore.prototype.init = function(tabId, pageURL) {
this.rootHostname = this.pageHostname;
this.rootDomain = this.pageDomain;
// This is part of the filtering evaluation context
this.requestURL = this.requestHostname = this.requestType = '';
this.requestHostnames = {};
this.frames = {};
this.netFiltering = true;
this.netFilteringReadTime = 0;
@ -361,7 +379,9 @@ PageStore.prototype.dispose = function() {
// used as a key).
this.pageURL = this.previousPageURL =
this.pageHostname = this.pageDomain =
this.rootHostname = this.rootDomain = '';
this.rootHostname = this.rootDomain =
this.requestURL = this.requestHostname = this.requestType = '';
this.requestHostnames = null;
this.disposeFrameStores();
this.netFilteringCache = this.netFilteringCache.dispose();
if ( pageStoreJunkyard.length < pageStoreJunkyardMax ) {
@ -374,11 +394,8 @@ PageStore.prototype.dispose = function() {
PageStore.prototype.disposeFrameStores = function() {
var frames = this.frames;
if ( typeof frames === 'object' ) {
for ( var k in frames ) {
if ( frames.hasOwnProperty(k) === false ) {
continue;
}
for ( var k in frames ) {
if ( frames.hasOwnProperty(k) ) {
frames[k].dispose();
}
}
@ -414,20 +431,32 @@ PageStore.prototype.getNetFilteringSwitch = function() {
/******************************************************************************/
PageStore.prototype.filterRequest = function(context, requestType, requestURL) {
var result = '';
if ( this.getNetFilteringSwitch() ) {
var entry = this.netFilteringCache.lookup(requestURL);
if ( entry !== undefined ) {
//console.debug(' cache HIT: PageStore.filterRequest("%s")', requestURL);
return entry.result;
}
//console.debug('cache MISS: PageStore.filterRequest("%s")', requestURL);
result = µb.netFilteringEngine.matchString(context, requestURL, requestType);
PageStore.prototype.filterRequest = function(context) {
var requestURL = context.requestURL;
if ( this.getNetFilteringSwitch() === false ) {
this.recordResult(context.requestType, requestURL, '');
return '';
}
if ( collapsibleRequestTypes.indexOf(requestType) !== -1 || µb.userSettings.logRequests ) {
this.netFilteringCache.add(requestURL, result, requestType, 0);
var entry = this.netFilteringCache.lookup(requestURL);
if ( entry !== undefined ) {
//console.debug('cache HIT: PageStore.filterRequest("%s")', requestURL);
return entry.result;
}
var result = µb.filterRequest(context);
//console.debug('cache MISS: PageStore.filterRequest("%s")', requestURL);
this.recordResult(context.requestType, requestURL, result);
var requestHostname = context.requestHostname;
if ( this.requestHostnames.hasOwnProperty(requestHostname) ) {
this.requestHostnames[requestHostname] |= this.bitFromRequestType[result.slice(0, 2)];
} else {
this.requestHostnames[requestHostname] = this.bitFromRequestType[result.slice(0, 2)];
}
return result;
};
@ -454,7 +483,7 @@ PageStore.prototype.recordResult = function(requestType, requestURL, result) {
// true: blocked
PageStore.prototype.boolFromResult = function(result) {
return typeof result === 'string' && result !== '' && result.slice(0, 2) !== '@@';
return typeof result === 'string' && result.charAt(1) === 'b';
};
/******************************************************************************/

View File

@ -30,7 +30,6 @@
/******************************************************************************/
var stats;
var reResultParser = /^(@@)?(\*|\|\|([^$^]+)\^)\$(.+)$/;
/******************************************************************************/
@ -50,23 +49,23 @@ var formatNumber = function(count) {
/******************************************************************************/
var syncDynamicFilter = function(scope, i, result) {
var el = uDom('[data-scope="' + scope + '"] > div:nth-of-type(' + i + ')');
var matches = reResultParser.exec(result) || [];
var blocked = matches.length !== 0 && matches[1] !== '@@';
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;
// There might be no page hostname on pages where uBlock can't be active,
// like on browser's built-in pages, etc.
if ( stats.pageHostname ) {
var filterHostname = matches[3] || '*';
if ( stats.pageHostname.slice(0 - filterHostname.length) === filterHostname ) {
ownFilter = (stats.pageHostname.length === filterHostname.length) ||
(stats.pageHostname.substr(0 - filterHostname.length - 1, 1) === '.');
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 === '.';
}
}
el.toggleClass('ownFilter', ownFilter);
@ -76,23 +75,25 @@ var syncDynamicFilter = function(scope, i, result) {
var syncAllDynamicFilters = function() {
var hasBlock = false;
var scopes = ['.', '/'];
var scopes = ['*', 'local'];
var scope, results, i, result;
while ( scope = scopes.pop() ) {
if ( stats.dynamicFilterResults.hasOwnProperty(scope) === false ) {
continue;
}
results = stats.dynamicFilterResults[scope];
i = 5;
while ( i-- ) {
result = results[i];
syncDynamicFilter(scope, i + 1, result);
if ( scope === '.' && result.length !== 0 && result.slice(0, 2) !== '@@' ) {
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;
}
}
}
uDom('#dynamicFilteringToggler').toggleClass('hasBlock', hasBlock);
uDom('body').toggleClass('hasDynamicBlock', hasBlock);
};
/******************************************************************************/
@ -165,7 +166,7 @@ var renderPopup = function(details) {
uDom('#total-blocked').html(html.join(''));
uDom('#switch .fa').toggleClass('off', stats.pageURL === '' || !stats.netFilteringSwitch);
uDom('#dynamicFilteringToggler').toggleClass('on', stats.dynamicFilteringEnabled);
uDom('body').toggleClass('dynamicFilteringEnabled', stats.dynamicFilteringEnabled);
};
/******************************************************************************/
@ -174,12 +175,11 @@ var toggleNetFilteringSwitch = function(ev) {
if ( !stats || !stats.pageURL ) {
return;
}
var off = uDom(this).toggleClass('off').hasClassName('off');
messager.send({
what: 'toggleNetFiltering',
url: stats.pageURL,
scope: ev.ctrlKey || ev.metaKey ? 'page' : '',
state: !off,
state: !uDom(this).toggleClass('off').hasClass('off'),
tabId: stats.tabId
});
};
@ -242,37 +242,35 @@ var gotoLink = function(ev) {
/******************************************************************************/
var onDynamicFilterClicked = function(ev) {
var elScope = uDom(ev.currentTarget);
var scope = elScope.attr('data-scope') === '/' ? '*' : stats.pageHostname;
// This can happen on pages where uBlock does not work
if ( typeof stats.pageHostname !== 'string' || stats.pageHostname === '' ) {
return;
}
var elFilter = uDom(ev.target);
var scope = elFilter.attr('data-src') === '*' ? '*' : stats.pageHostname;
var onDynamicFilterChanged = function(details) {
stats.dynamicFilterResults = details;
syncAllDynamicFilters();
};
messager.send({
what: 'toggleDynamicFilter',
hostname: scope,
pageHostname: stats.pageHostname,
srcHostname: scope,
desHostname: elFilter.attr('data-des'),
requestType: elFilter.attr('data-type'),
firstParty: elFilter.attr('data-first-party') !== null,
block: elFilter.hasClassName('blocked') === false,
pageHostname: stats.pageHostname
block: elFilter.hasClassName('blocked') === false
}, onDynamicFilterChanged);
};
/******************************************************************************/
var toggleDynamicFiltering = function(ev) {
// Discard events destined to child elements.
if ( ev !== undefined && ev.target !== this ) {
return;
}
var el = uDom('#dynamicFilteringToggler');
el.toggleClass('on');
var el = uDom('body');
el.toggleClass('dynamicFilteringEnabled');
messager.send({
what: 'userSettings',
name: 'dynamicFilteringEnabled',
value: el.hasClassName('on')
value: el.hasClassName('dynamicFilteringEnabled')
});
};
@ -285,7 +283,7 @@ var installEventHandlers = function() {
uDom('#gotoPick').on('click', gotoPick);
uDom('a[href^=http]').on('click', gotoLink);
uDom('#dynamicFilteringToggler').on('click', toggleDynamicFiltering);
uDom('.dynamicFiltering').on('click', 'div', onDynamicFilterClicked);
uDom('#dynamicFilteringContainer').on('click', 'span[data-type]', onDynamicFilterClicked);
};
/******************************************************************************/

View File

@ -19,7 +19,7 @@
Home: https://github.com/gorhill/uBlock
*/
/* jshint bitwise: false */
/* jshint bitwise: false, esnext: true */
/* global µBlock */
// Older Safari throws an exception for const when it's used with 'use strict'.
@ -27,7 +27,7 @@
/******************************************************************************/
µBlock.netFilteringEngine = (function(){
µBlock.staticNetFilteringEngine = (function(){
/******************************************************************************/
@ -1284,7 +1284,6 @@ var FilterContainer = function() {
this.buckets = new Array(4);
this.blockedAnyPartyHostnames = new µb.LiquidDict();
this.blocked3rdPartyHostnames = new µb.LiquidDict();
this.dynamicFilters = {};
this.filterParser = new FilterParser();
this.reset();
};
@ -1642,193 +1641,6 @@ FilterContainer.prototype.addToCategory = function(category, tokenKey, filter) {
/******************************************************************************/
// Dynamic filters
// Bits:
// 0: inline script blocked
// 1: inline script allowed
// 2: first-party script blocked
// 3: first-party script allowed
// 4: third-party script blocked
// 5: third-party script allowed
// 6: first-party frame blocked
// 7: first-party frame allowed
// 8: third-party frame blocked
// 9: third-party frame allowed
//
// I chose separate bits for blocked/unblocked as I want to have an "undefined"
// state, which will be used to inherit from wider-scoped filters.
//
// undefined: 0x00
// blocked: 0x01
// allowed: 0x02
// unused: 0x03
FilterContainer.prototype.dynamicFilterBitOffsets = {};
FilterContainer.prototype.dynamicFilterBitOffsets[FirstParty | typeNameToTypeValue['inline-script']] = 0;
FilterContainer.prototype.dynamicFilterBitOffsets[FirstParty | typeNameToTypeValue.script] = 2;
FilterContainer.prototype.dynamicFilterBitOffsets[ThirdParty | typeNameToTypeValue.script] = 4;
FilterContainer.prototype.dynamicFilterBitOffsets[FirstParty | typeNameToTypeValue.sub_frame] = 6;
FilterContainer.prototype.dynamicFilterBitOffsets[ThirdParty | typeNameToTypeValue.sub_frame] = 8;
FilterContainer.prototype.dynamicFiltersMagicId = 'numrebvoacir';
/******************************************************************************/
FilterContainer.prototype.dynamicFilterSet = function(hostname, requestType, firstParty, value) {
if ( typeNameToTypeValue.hasOwnProperty(requestType) === false ) {
return false;
}
var party = firstParty ? FirstParty : ThirdParty;
var categoryKey = party | typeNameToTypeValue[requestType];
if ( this.dynamicFilterBitOffsets.hasOwnProperty(categoryKey) === false ) {
return false;
}
var bitOffset = this.dynamicFilterBitOffsets[categoryKey];
var oldFilter = this.dynamicFilters[hostname] || 0;
var newFilter = (oldFilter & ~(0x0003 << bitOffset)) | (value << bitOffset);
if ( newFilter === oldFilter ) {
return false;
}
if ( newFilter === 0 ) {
delete this.dynamicFilters[hostname];
} else {
this.dynamicFilters[hostname] = newFilter;
}
return true;
};
/******************************************************************************/
FilterContainer.prototype.dynamicFilterBlock = function(hostname, requestType, firstParty) {
if ( typeof hostname !== 'string' || hostname === '' ) {
return false;
}
var result = this.matchDynamicFilters(hostname, requestType, firstParty);
if ( result !== '' && result.slice(0, 2) !== '@@' ) {
return false;
}
this.dynamicFilterSet(hostname, requestType, firstParty, 0x00);
result = this.matchDynamicFilters(hostname, requestType, firstParty);
if ( result !== '' && result.slice(0, 2) !== '@@' ) {
return true;
}
this.dynamicFilterSet(hostname, requestType, firstParty, 0x01);
return true;
};
/******************************************************************************/
FilterContainer.prototype.dynamicFilterUnblock = function(hostname, requestType, firstParty) {
if ( typeof hostname !== 'string' || hostname === '' ) {
return false;
}
var result = this.matchDynamicFilters(hostname, requestType, firstParty);
if ( result === '' || result.slice(0, 2) === '@@' ) {
return false;
}
this.dynamicFilterSet(hostname, requestType, firstParty, 0x00);
result = this.matchDynamicFilters(hostname, requestType, firstParty);
if ( result === '' || result.slice(0, 2) === '@@' ) {
return true;
}
this.dynamicFilterSet(hostname, requestType, firstParty, 0x02);
return true;
};
/******************************************************************************/
FilterContainer.prototype.dynamicFilterStateToType = {
0x0001: '',
0x0002: '@@',
0x0004: '',
0x0008: '@@',
0x0010: '',
0x0020: '@@',
0x0040: '',
0x0080: '@@',
0x0100: '',
0x0200: '@@'
};
FilterContainer.prototype.dynamicFilterStateToOption = {
0x0001: '$inline-script,important',
0x0002: '$inline-script',
0x0004: '$~third-party,script,important',
0x0008: '$~third-party,script',
0x0010: '$third-party,script,important',
0x0020: '$third-party,script',
0x0040: '$~third-party,subdocument,important',
0x0080: '$~third-party,subdocument',
0x0100: '$third-party,subdocument,important',
0x0200: '$third-party,subdocument'
};
FilterContainer.prototype.matchDynamicFilters = function(hostname, requestType, firstParty) {
if ( typeof hostname !== 'string' || hostname === '' ) {
return '';
}
var party = firstParty ? FirstParty : ThirdParty;
if ( typeNameToTypeValue.hasOwnProperty(requestType) === false ) {
return '';
}
var categoryKey = party | typeNameToTypeValue[requestType];
if ( this.dynamicFilterBitOffsets.hasOwnProperty(categoryKey) === false ) {
return '';
}
var bitOffset = this.dynamicFilterBitOffsets[categoryKey];
var bitMask = 0x0003 << bitOffset;
var bitState, pos;
for ( ;; ) {
if ( this.dynamicFilters.hasOwnProperty(hostname) !== false ) {
bitState = this.dynamicFilters[hostname] & bitMask;
if ( bitState !== 0 ) {
if ( hostname !== '*' ) {
hostname = '||' + hostname + '^';
}
return this.dynamicFilterStateToType[bitState] +
hostname +
this.dynamicFilterStateToOption[bitState];
}
}
pos = hostname.indexOf('.');
if ( pos === -1 ) {
if ( hostname === '*' ) {
return '';
}
hostname = '*';
} else {
hostname = hostname.slice(pos + 1);
}
}
// unreachable
};
/******************************************************************************/
FilterContainer.prototype.selfieFromDynamicFilters = function() {
var bin = {
magicId: this.dynamicFiltersMagicId,
filters: this.dynamicFilters
};
return JSON.stringify(bin);
};
/******************************************************************************/
FilterContainer.prototype.dynamicFiltersFromSelfie = function(selfie) {
if ( selfie === '' ) {
return;
}
var bin = JSON.parse(selfie);
if ( bin.magicId !== this.dynamicFiltersMagicId ) {
return;
}
this.dynamicFilters = bin.filters;
};
/******************************************************************************/
// Since the addition of the `important` evaluation, this means it is now
// likely that the url will have to be scanned more than once. So this is
// to ensure we do it once only, and reuse results.
@ -1858,15 +1670,9 @@ FilterContainer.prototype.tokenize = function(url) {
/******************************************************************************/
FilterContainer.prototype.matchTokens = function(url) {
var buckets = this.buckets;
var bucket0 = buckets[0];
var bucket1 = buckets[1];
var bucket2 = buckets[2];
var bucket3 = buckets[3];
FilterContainer.prototype.matchTokens = function(bucket, url) {
var tokens = this.tokens;
var tokenEntry, beg, token, f;
var tokenEntry, token, f;
var i = 0;
for (;;) {
tokenEntry = tokens[i++];
@ -1874,30 +1680,9 @@ FilterContainer.prototype.matchTokens = function(url) {
if ( token === '' ) {
break;
}
beg = tokenEntry.beg;
if ( bucket0 !== undefined ) {
f = bucket0[token];
if ( f !== undefined && f.match(url, beg) !== false ) {
return f;
}
}
if ( bucket1 !== undefined ) {
f = bucket1[token];
if ( f !== undefined && f.match(url, beg) !== false ) {
return f;
}
}
if ( bucket2 !== undefined ) {
f = bucket2[token];
if ( f !== undefined && f.match(url, beg) !== false ) {
return f;
}
}
if ( bucket3 !== undefined ) {
f = bucket3[token];
if ( f !== undefined && f.match(url, beg) !== false ) {
return f;
}
f = bucket[token];
if ( f !== undefined && f.match(url, tokenEntry.beg) !== false ) {
return f;
}
}
return false;
@ -1955,73 +1740,77 @@ FilterContainer.prototype.match3rdPartyHostname = function(requestHostname) {
// Some type of requests are exceptional, they need custom handling,
// not the generic handling.
FilterContainer.prototype.matchStringExactType = function(pageDetails, requestURL, requestType) {
FilterContainer.prototype.matchStringExactType = function(context, requestURL, requestType) {
var url = requestURL.toLowerCase();
var requestHostname = µb.URI.hostnameFromURI(requestURL);
// Evaluate dynamic filters first. "Block" dynamic filters are always
// "important", they override everything else.
var bf = this.matchDynamicFilters(
pageDetails.rootHostname,
requestType,
isFirstParty(pageDetails.rootDomain, requestHostname)
);
if ( bf !== '' && bf.slice(0, 2) !== '@@' ) {
return bf;
}
var party = isFirstParty(pageDetails.pageDomain, requestHostname) ? FirstParty : ThirdParty;
var party = isFirstParty(context.pageDomain, requestHostname) ? FirstParty : ThirdParty;
// This will be used by hostname-based filters
pageHostname = pageDetails.pageHostname || '';
pageHostname = context.pageHostname || '';
var type = typeNameToTypeValue[requestType];
var categories = this.categories;
var buckets = this.buckets;
var bucket;
// Tokenize only once
this.tokenize(url);
// We are testing for a specific type, skip "any type" buckets
buckets[0] = buckets[1] = undefined;
// https://github.com/gorhill/uBlock/issues/139
// Test against important block filters
buckets[2] = categories[this.makeCategoryKey(BlockAnyParty | Important | type)];
buckets[3] = categories[this.makeCategoryKey(BlockAction | Important | type | party)];
bf = this.matchTokens(url);
if ( bf !== false ) {
return bf.toString();
if ( bucket = categories[this.makeCategoryKey(BlockAnyParty | Important | type)] ) {
bf = this.matchTokens(bucket, url);
if ( bf !== false ) {
return 'sb:' + bf.toString();
}
}
if ( bucket = categories[this.makeCategoryKey(BlockAction | Important | type | party)] ) {
bf = this.matchTokens(bucket, url);
if ( bf !== false ) {
return 'sb:' + bf.toString();
}
}
// Test against block filters
bf = false;
if ( bucket = categories[this.makeCategoryKey(BlockAnyParty | type)] ) {
bf = this.matchTokens(bucket, url);
}
if ( bf === false ) {
if ( bucket = categories[this.makeCategoryKey(BlockAction | type | party)] ) {
bf = this.matchTokens(bucket, url);
}
}
// If there is no block filter, no need to test against allow filters
buckets[2] = categories[this.makeCategoryKey(BlockAnyParty | type)];
buckets[3] = categories[this.makeCategoryKey(BlockAction | type | party)];
bf = this.matchTokens(url);
if ( bf === false ) {
return '';
}
// Test against allow filters
buckets[2] = categories[this.makeCategoryKey(AllowAnyParty | type)];
buckets[3] = categories[this.makeCategoryKey(AllowAction | type | party)];
var af = this.matchTokens(url);
if ( af !== false ) {
return '@@' + af.toString();
var af;
if ( bucket = categories[this.makeCategoryKey(AllowAnyParty | type)] ) {
af = this.matchTokens(bucket, url);
if ( af !== false ) {
return 'sa:' + af.toString();
}
}
if ( bucket = categories[this.makeCategoryKey(AllowAction | type | party)] ) {
af = this.matchTokens(bucket, url);
if ( af !== false ) {
return 'sa:' + af.toString();
}
}
return bf.toString();
return 'sb:' + bf.toString();
};
/******************************************************************************/
FilterContainer.prototype.matchString = function(pageDetails, requestURL, requestType) {
FilterContainer.prototype.matchString = function(context) {
// https://github.com/gorhill/httpswitchboard/issues/239
// Convert url to lower case:
// `match-case` option not supported, but then, I saw only one
// occurrence of it in all the supported lists (bulgaria list).
var url = requestURL.toLowerCase();
var url = context.requestURL.toLowerCase();
// The logic here is simple:
//
@ -2044,27 +1833,15 @@ FilterContainer.prototype.matchString = function(pageDetails, requestURL, reques
// filters are tested *only* if there is a (unlikely) hit on a block
// filter.
var requestHostname = µb.URI.hostnameFromURI(requestURL);
// Evaluate dynamic filters first. "Block" dynamic filters are always
// "important", they override everything else.
var bf = this.matchDynamicFilters(
pageDetails.rootHostname,
requestType,
isFirstParty(pageDetails.rootDomain, requestHostname)
);
if ( bf !== '' && bf.slice(0, 2) !== '@@' ) {
return bf;
}
var party = isFirstParty(pageDetails.pageDomain, requestHostname) ? FirstParty : ThirdParty ;
var requestHostname = context.requestHostname;
var party = isFirstParty(context.pageDomain, requestHostname) ? FirstParty : ThirdParty;
// This will be used by hostname-based filters
pageHostname = pageDetails.pageHostname || '';
pageHostname = context.pageHostname || '';
var type = typeNameToTypeValue[requestType];
var type = typeNameToTypeValue[context.requestType];
var categories = this.categories;
var buckets = this.buckets;
var bucket;
// Tokenize only once
this.tokenize(url);
@ -2074,13 +1851,29 @@ FilterContainer.prototype.matchString = function(pageDetails, requestURL, reques
// The purpose of the `important` option is to reverse the order of
// evaluation. Normally, it is "evaluate block then evaluate allow", with
// the `important` property it is "evaluate allow then evaluate block".
buckets[0] = categories[this.makeCategoryKey(BlockAnyTypeAnyParty | Important)];
buckets[1] = categories[this.makeCategoryKey(BlockAnyType | Important | party)];
buckets[2] = categories[this.makeCategoryKey(BlockAnyParty | Important | type)];
buckets[3] = categories[this.makeCategoryKey(BlockAction | Important | type | party)];
bf = this.matchTokens(url);
if ( bf !== false ) {
return bf.toString() + '$important';
if ( bucket = categories[this.makeCategoryKey(BlockAnyTypeAnyParty | Important)] ) {
bf = this.matchTokens(bucket, url);
if ( bf !== false ) {
return 'sb:' + bf.toString() + '$important';
}
}
if ( bucket = categories[this.makeCategoryKey(BlockAnyType | Important | party)] ) {
bf = this.matchTokens(bucket, url);
if ( bf !== false ) {
return 'sb:' + bf.toString() + '$important';
}
}
if ( bucket = categories[this.makeCategoryKey(BlockAnyParty | Important | type)] ) {
bf = this.matchTokens(bucket, url);
if ( bf !== false ) {
return 'sb:' + bf.toString() + '$important';
}
}
if ( bucket = categories[this.makeCategoryKey(BlockAction | Important | type | party)] ) {
bf = this.matchTokens(bucket, url);
if ( bf !== false ) {
return 'sb:' + bf.toString() + '$important';
}
}
// Test hostname-based block filters
@ -2091,11 +1884,24 @@ FilterContainer.prototype.matchString = function(pageDetails, requestURL, reques
// Test against block filters
if ( bf === false ) {
buckets[0] = categories[this.makeCategoryKey(BlockAnyTypeAnyParty)];
buckets[1] = categories[this.makeCategoryKey(BlockAnyType | party)];
buckets[2] = categories[this.makeCategoryKey(BlockAnyParty | type)];
buckets[3] = categories[this.makeCategoryKey(BlockAction | type | party)];
bf = this.matchTokens(url);
if ( bucket = categories[this.makeCategoryKey(BlockAnyTypeAnyParty)] ) {
bf = this.matchTokens(bucket, url);
}
}
if ( bf === false ) {
if ( bucket = categories[this.makeCategoryKey(BlockAnyType | party)] ) {
bf = this.matchTokens(bucket, url);
}
}
if ( bf === false ) {
if ( bucket = categories[this.makeCategoryKey(BlockAnyParty | type)] ) {
bf = this.matchTokens(bucket, url);
}
}
if ( bf === false ) {
if ( bucket = categories[this.makeCategoryKey(BlockAction | type | party)] ) {
bf = this.matchTokens(bucket, url);
}
}
// If there is no block filter, no need to test against allow filters
@ -2104,16 +1910,33 @@ FilterContainer.prototype.matchString = function(pageDetails, requestURL, reques
}
// Test against allow filters
buckets[0] = categories[this.makeCategoryKey(AllowAnyTypeAnyParty)];
buckets[1] = categories[this.makeCategoryKey(AllowAnyType | party)];
buckets[2] = categories[this.makeCategoryKey(AllowAnyParty | type)];
buckets[3] = categories[this.makeCategoryKey(AllowAction | type | party)];
var af = this.matchTokens(url);
if ( af !== false ) {
return '@@' + af.toString();
var af;
if ( bucket = categories[this.makeCategoryKey(AllowAnyTypeAnyParty)] ) {
af = this.matchTokens(bucket, url);
if ( af !== false ) {
return 'sa:' + af.toString();
}
}
if ( bucket = categories[this.makeCategoryKey(AllowAnyType | party)] ) {
af = this.matchTokens(bucket, url);
if ( af !== false ) {
return 'sa:' + af.toString();
}
}
if ( bucket = categories[this.makeCategoryKey(AllowAnyParty | type)] ) {
af = this.matchTokens(bucket, url);
if ( af !== false ) {
return 'sa:' + af.toString();
}
}
if ( bucket = categories[this.makeCategoryKey(AllowAction | type | party)] ) {
af = this.matchTokens(bucket, url);
if ( af !== false ) {
return 'sa:' + af.toString();
}
}
return bf.toString();
return 'sb:' + bf.toString();
};
/******************************************************************************/

View File

@ -21,12 +21,13 @@
/* jshint bitwise: false */
/* global uDom */
'use strict';
/******************************************************************************/
(function() {
'use strict';
/******************************************************************************/
var messager = vAPI.messaging.channel('stats.js');
@ -77,8 +78,8 @@ var renderURL = function(url, filter) {
if ( pos > 0 ) {
reText = reText.slice(0, pos);
}
if ( reText.slice(0, 2) === '@@' ) {
reText = reText.slice(2);
if ( reText.charAt(0) === 's' ) {
reText = reText.slice(3);
}
if ( reText === '*' ) {
reText = '\\*';

View File

@ -149,7 +149,7 @@
return;
}
µb.mergeFilterText(content);
µb.netFilteringEngine.freeze();
µb.staticNetFilteringEngine.freeze();
µb.cosmeticFilteringEngine.freeze();
µb.destroySelfie();
µb.toSelfieAsync();
@ -278,7 +278,7 @@
}
var loadBlacklistsEnd = function() {
µb.netFilteringEngine.freeze();
µb.staticNetFilteringEngine.freeze();
µb.cosmeticFilteringEngine.freeze();
vAPI.storage.set({ 'remoteBlacklists': µb.remoteBlacklists });
vAPI.messaging.broadcast({ what: 'loadUbiquitousBlacklistCompleted' });
@ -296,7 +296,7 @@
var loadBlacklistsStart = function(lists) {
µb.remoteBlacklists = lists;
µb.netFilteringEngine.reset();
µb.staticNetFilteringEngine.reset();
µb.cosmeticFilteringEngine.reset();
µb.destroySelfie();
var locations = Object.keys(lists);
@ -328,17 +328,17 @@
µBlock.mergeFilterList = function(details) {
// console.log('µBlock > mergeFilterList from "%s": "%s..."', details.path, details.content.slice(0, 40));
var netFilteringEngine = this.netFilteringEngine;
var staticNetFilteringEngine = this.staticNetFilteringEngine;
var cosmeticFilteringEngine = this.cosmeticFilteringEngine;
var duplicateCount = netFilteringEngine.duplicateCount + cosmeticFilteringEngine.duplicateCount;
var acceptedCount = netFilteringEngine.acceptedCount + cosmeticFilteringEngine.acceptedCount;
var duplicateCount = staticNetFilteringEngine.duplicateCount + cosmeticFilteringEngine.duplicateCount;
var acceptedCount = staticNetFilteringEngine.acceptedCount + cosmeticFilteringEngine.acceptedCount;
this.mergeFilterText(details.content);
// For convenience, store the number of entries for this
// blacklist, user might be happy to know this information.
duplicateCount = netFilteringEngine.duplicateCount + cosmeticFilteringEngine.duplicateCount - duplicateCount;
acceptedCount = netFilteringEngine.acceptedCount + cosmeticFilteringEngine.acceptedCount - acceptedCount;
duplicateCount = staticNetFilteringEngine.duplicateCount + cosmeticFilteringEngine.duplicateCount - duplicateCount;
acceptedCount = staticNetFilteringEngine.acceptedCount + cosmeticFilteringEngine.acceptedCount - acceptedCount;
var filterListMeta = this.remoteBlacklists[details.path];
@ -363,7 +363,7 @@
// Useful references:
// https://adblockplus.org/en/filter-cheatsheet
// https://adblockplus.org/en/filters
var netFilteringEngine = this.netFilteringEngine;
var staticNetFilteringEngine = this.staticNetFilteringEngine;
var cosmeticFilteringEngine = this.cosmeticFilteringEngine;
var parseCosmeticFilters = this.userSettings.parseAllABPHideFilters;
@ -433,7 +433,7 @@
continue;
}
netFilteringEngine.add(matches[0]);
staticNetFilteringEngine.add(matches[0]);
}
};
@ -520,7 +520,7 @@
magic: this.selfieMagic,
publicSuffixList: publicSuffixList.toSelfie(),
filterLists: this.remoteBlacklists,
netFilteringEngine: this.netFilteringEngine.toSelfie(),
staticNetFilteringEngine: this.staticNetFilteringEngine.toSelfie(),
cosmeticFilteringEngine: this.cosmeticFilteringEngine.toSelfie()
};
vAPI.storage.set({ selfie: selfie });
@ -565,7 +565,7 @@
}
// console.log('µBlock.fromSelfie> selfie looks good');
µb.remoteBlacklists = selfie.filterLists;
µb.netFilteringEngine.fromSelfie(selfie.netFilteringEngine);
µb.staticNetFilteringEngine.fromSelfie(selfie.staticNetFilteringEngine);
µb.cosmeticFilteringEngine.fromSelfie(selfie.cosmeticFilteringEngine);
callback(true);
};
@ -649,12 +649,21 @@
// Important: block remote fetching for when loading assets at launch
// time.
µb.assets.allowRemoteFetch = false;
µb.assets.autoUpdate = settings.autoUpdate;
µb.netFilteringEngine.dynamicFiltersFromSelfie(settings.dynamicFilteringSelfie);
µb.fromSelfie(onSelfieReady);
µb.mirrors.toggle(settings.experimentalEnabled);
µb.contextMenu.toggle(settings.contextMenuEnabled);
if ( typeof settings.dynamicFilteringSelfie === 'string' ) {
if ( settings.dynamicFilteringString === '' && settings.dynamicFilteringSelfie !== '' ) {
µb.dynamicNetFilteringEngine.fromObsoleteSelfie(settings.dynamicFilteringSelfie);
µb.userSettings.dynamicFilteringString = µb.dynamicNetFilteringEngine.toString();
µb.XAL.keyvalSetOne('dynamicFilteringString', µb.userSettings.dynamicFilteringString);
}
delete µb.userSettings.dynamicFilteringSelfie;
µb.XAL.keyvalRemoveOne('dynamicFilteringSelfie');
}
µb.dynamicNetFilteringEngine.fromString(µb.userSettings.dynamicFilteringString);
};
this.loadUserSettings(onUserSettingsReady);

View File

@ -55,6 +55,8 @@ vAPI.tabs.onClosed = function(tabId) {
// https://github.com/gorhill/uBlock/issues/297
vAPI.tabs.onPopup = function(details) {
//console.debug('vAPI.tabs.onPopup: url="%s"', details.url);
var pageStore = µBlock.pageStoreFromTabId(details.sourceTabId);
if ( !pageStore ) {
return;
@ -65,7 +67,7 @@ vAPI.tabs.onPopup = function(details) {
// https://github.com/gorhill/uBlock/issues/323
// If popup URL is whitelisted, do not block it
if ( µBlock.getNetFilteringSwitch(requestURL) ) {
result = µBlock.netFilteringEngine.matchStringExactType(pageStore, requestURL, 'popup');
result = µBlock.staticNetFilteringEngine.matchStringExactType(pageStore, requestURL, 'popup');
}
// https://github.com/gorhill/uBlock/issues/91

View File

@ -80,7 +80,6 @@ var onBeforeRequest = function(details) {
// https://github.com/gorhill/uBlock/issues/114
var requestContext = pageStore;
var frameStore;
var frameId = details.frameId;
if ( frameId > 0 ) {
@ -89,7 +88,12 @@ var onBeforeRequest = function(details) {
}
}
var result = pageStore.filterRequest(requestContext, requestType, requestURL);
// Setup context and evaluate
requestContext.requestURL = requestURL;
requestContext.requestHostname = µb.URI.hostnameFromURI(requestURL);
requestContext.requestType = requestType;
var result = pageStore.filterRequest(requestContext);
// Not blocked
if ( pageStore.boolFromResult(result) === false ) {
@ -208,11 +212,12 @@ var onBeforeSendHeaders = function(details) {
var pageDetails = {
pageHostname: referrerHostname,
pageDomain: µburi.domainFromHostname(referrerHostname),
firstParty: false
};
pageDetails.rootHostname = pageDetails.pageHostname;
pageDetails.rootDomain = pageDetails.pageDomain;
//console.debug('Referrer="%s"', referrer);
var result = µb.netFilteringEngine.matchStringExactType(pageDetails, requestURL, 'popup');
var result = µb.staticNetFilteringEngine.matchStringExactType(pageDetails, requestURL, 'popup');
// Not blocked?
if ( result === '' || result.slice(0, 2) === '@@' ) {
@ -251,22 +256,14 @@ var onHeadersReceived = function(details) {
// https://github.com/gorhill/uBlock/issues/384
pageStore.skipLocalMirroring = headerValue(details.responseHeaders, 'content-security-policy');
var result = '';
if ( pageStore.getNetFilteringSwitch() ) {
result = µb.netFilteringEngine.matchStringExactType(pageStore, details.url, 'inline-script');
}
// Not blocked?
if ( result === '' || result.slice(0, 2) === '@@' ) {
pageStore.requestURL = details.url;
pageStore.requestHostname = µb.URI.hostnameFromURI(details.url);
pageStore.requestType = 'inline-script';
var result = pageStore.filterRequest(pageStore);
if ( result === '' ) {
return;
}
// Record request
if ( result !== '' ) {
pageStore.recordResult('script', details.url, result);
}
// Blocked
pageStore.perLoadBlockedRequestCount++;
µb.localSettings.blockedRequestCount++;

View File

@ -309,23 +309,88 @@ var matchWhitelistDirective = function(url, hostname, directive) {
µBlock.toggleDynamicFilter = function(details) {
var changed = false;
if ( details.block ) {
changed = this.netFilteringEngine.dynamicFilterBlock(details.hostname, details.requestType, details.firstParty);
changed = this.dynamicNetFilteringEngine.blockCell(details.srcHostname, details.desHostname, details.requestType);
} else {
changed = this.netFilteringEngine.dynamicFilterUnblock(details.hostname, details.requestType, details.firstParty);
changed = this.dynamicNetFilteringEngine.unsetCell(details.srcHostname, details.desHostname, details.requestType);
}
if ( !changed ) {
return;
}
this.userSettings.dynamicFilteringSelfie = this.netFilteringEngine.selfieFromDynamicFilters();
this.XAL.keyvalSetOne('dynamicFilteringSelfie', this.userSettings.dynamicFilteringSelfie);
this.userSettings.dynamicFilteringString = this.dynamicNetFilteringEngine.toString();
this.XAL.keyvalSetOne('dynamicFilteringString', this.userSettings.dynamicFilteringString);
// https://github.com/gorhill/uBlock/issues/420
if ( details.requestType === 'sub_frame' && !details.block ) {
if ( details.requestType === '3p-frame' && !details.block ) {
this.cosmeticFilteringEngine.removeFromSelectorCache(details.hostname, 'net');
}
};
/******************************************************************************/
µBlock.isFirstParty = function(firstPartyDomain, hostname) {
if ( hostname.slice(0 - firstPartyDomain.length) !== firstPartyDomain ) {
return false;
}
// Be sure to not confuse 'example.com' with 'anotherexample.com'
var c = hostname.charAt(hostname.length - firstPartyDomain.length - 1);
return c === '.' || c === '';
};
/******************************************************************************/
// The core logic to evaluate requests through dynamic/static filtering
// is here.
µBlock.filterRequest = function(context) {
// Given that:
// - Dynamic filtering override static filtering
// - Evaluating dynamic filtering is much faster than static filtering
// We evaluate dynamic filtering first, and hopefully we can skip
// evaluation of static filtering.
// Dynamic filtering evaluation is ordered from most-specific to least-
// specific.
var df = this.dynamicNetFilteringEngine;
var rootHostname = context.rootHostname;
var requestHostname = context.requestHostname;
var requestType = context.requestType;
// Dynamic filters:
// 1. specific source, specific destination, any type, allow/block
// 2. any source, specific destination, any type, allow/block
df.evaluateCellZY(rootHostname, requestHostname, '*');
if ( df.mustBlockOrAllow() ) {
return df.toFilterString();
}
// 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();
}
}
if ( requestType === 'sub_frame' ) {
if ( this.isFirstParty(rootHostname, requestHostname) === false ) {
df.evaluateCellZY(rootHostname, requestHostname, '3p-frame');
if ( df.mustBlockOrAllow() ) {
return df.toFilterString();
}
}
}
df.evaluateCellZY(rootHostname, requestHostname, requestType);
if ( df.mustBlockOrAllow() ) {
return df.toFilterString();
}
// 5. Static filtering never override dynamic filtering
return this.staticNetFilteringEngine.matchString(context);
};
/******************************************************************************/
})();

View File

@ -48,6 +48,12 @@ exports.keyvalSetMany = function(dict, callback) {
/******************************************************************************/
exports.keyvalRemoveOne = function(key, callback) {
vAPI.storage.remove(key, callback || noopFunc);
};
/******************************************************************************/
exports.keyvalRemoveAll = function(callback) {
vAPI.storage.clear(callback || noopFunc);
};

View File

@ -11,9 +11,17 @@
<body>
<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>
</div><!-- DO NOT REMOVE --><div>
<p id="switch" data-i18n-tip="popupPowerSwitchInfo"><span class="fa">&#xf011;</span></p>
<p id="switch-hint"></p>
<p style="font-size: 16px;" data-i18n="popupBlockedRequestPrompt"></p>
<p id="dynamicFilteringToggler" data-i18n="popupBlockedRequestPrompt"></p>
<p id="stats">
<span data-i18n="popupBlockedOnThisPagePrompt"></span>&ensp;
<span id="gotoPick" class="fa tool" data-i18n-tip="popupTipPicker" data-tip-anchor="top">&#xf1fb;</span>&ensp;
@ -24,65 +32,6 @@
<p id="total-blocked">?</p>
</div>
<div id="dynamicFilteringToggler" class="fa">
<div></div>
<div></div>
<div></div>
<div></div>
<a href="https://github.com/gorhill/uBlock/wiki/Dynamic-filtering" target="_blank">?</a>
</div>
<div id="dynamicFilteringContainer">
<div class="dynamicFiltering local" data-scope=".">
<div data-first-party data-type="inline-script">
<div class="tip" data-i18n="popupSiteInlineScriptEnabled"></div>
<div class="tip" data-i18n="popupSiteInlineScriptDisabled"></div>
</div>
<div data-first-party data-type="script">
<div class="tip" data-i18n="popupSite1pScriptEnabled"></div>
<div class="tip" data-i18n="popupSite1pScriptDisabled"></div>
</div>
<div data-type="script">
<div class="tip" data-i18n="popupSite3pScriptEnabled"></div>
<div class="tip" data-i18n="popupSite3pScriptDisabled"></div>
</div>
<div data-first-party data-type="sub_frame">
<div class="tip" data-i18n="popupSite1pFrameEnabled"></div>
<div class="tip" data-i18n="popupSite1pFrameDisabled"></div>
</div>
<div data-type="sub_frame">
<div class="tip" data-i18n="popupSite3pFrameEnabled"></div>
<div class="tip" data-i18n="popupSite3pFrameDisabled"></div>
</div>
<div class="label">&lt;script&gt;</div>
<div class="label">&lt;iframe&gt;</div>
</div>
<div class="dynamicFiltering global" data-scope="/">
<div data-first-party data-type="inline-script">
<div class="tip" data-i18n="popupDefaultInlineScriptEnabled"></div>
<div class="tip" data-i18n="popupDefaultInlineScriptDisabled"></div>
</div>
<div data-first-party data-type="script">
<div class="tip" data-i18n="popupDefault1pScriptEnabled"></div>
<div class="tip" data-i18n="popupDefault1pScriptDisabled"></div>
</div>
<div data-type="script">
<div class="tip" data-i18n="popupDefault3pScriptEnabled"></div>
<div class="tip" data-i18n="popupDefault3pScriptDisabled"></div>
</div>
<div data-first-party data-type="sub_frame">
<div class="tip" data-i18n="popupDefault1pFrameEnabled"></div>
<div class="tip" data-i18n="popupDefault1pFrameDisabled"></div>
</div>
<div data-type="sub_frame">
<div class="tip" data-i18n="popupDefault3pFrameEnabled"></div>
<div class="tip" data-i18n="popupDefault3pFrameDisabled"></div>
</div>
<div class="label">&lt;script&gt;</div>
<div class="label">&lt;iframe&gt;</div>
</div>
</div>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script>