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:
parent
aed66e5278
commit
1fe7045b92
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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: {},
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
485
src/js/dynamic-net-filtering.js
Normal file
485
src/js/dynamic-net-filtering.js
Normal 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
|
||||
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
@ -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,
|
||||
|
@ -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';
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -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();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
@ -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 = '\\*';
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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++;
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
})();
|
@ -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);
|
||||
};
|
||||
|
@ -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"></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> 
|
||||
<span id="gotoPick" class="fa tool" data-i18n-tip="popupTipPicker" data-tip-anchor="top"></span> 
|
||||
@ -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"><script></div>
|
||||
<div class="label"><iframe></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"><script></div>
|
||||
<div class="label"><iframe></div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/udom.js"></script>
|
||||
|
Loading…
Reference in New Issue
Block a user