mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-06 19:02:30 +01:00
Count allowed/blocked requests for 3rd-party scripts/frames
Related issue: - https://github.com/uBlockOrigin/uBlock-issues/issues/210 Additionally, a small (experimental) widget has been added to emphasize/de-emphasize rows which have 3rd-party scripts/frames, so as to more easily identify which rows are "affected" by 3rd-party scripts and/or frames. Tooltip localization for the new widget is not available yet as I want wait for the feature to be fully settled.
This commit is contained in:
parent
e2b988aed9
commit
435c91636f
@ -262,7 +262,6 @@ body[data-more=""] #lessButton {
|
||||
min-width: var(--popup-firewall-min-width);
|
||||
padding: 0;
|
||||
overflow-y: auto;
|
||||
text-align: right;
|
||||
}
|
||||
:root.desktop body.vMin #firewall {
|
||||
max-height: 100vh;
|
||||
@ -271,7 +270,6 @@ body[data-more=""] #lessButton {
|
||||
border: 0;
|
||||
direction: ltr;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin: 0;
|
||||
margin-top: 1px;
|
||||
padding: 0;
|
||||
@ -305,10 +303,45 @@ body[data-more=""] #lessButton {
|
||||
flex-grow: 1;
|
||||
justify-content: flex-end;
|
||||
padding-right: 2px;
|
||||
text-align: right;
|
||||
white-space: normal;
|
||||
width: calc(100% - var(--popup-rule-cell-width));
|
||||
word-break: break-word;
|
||||
}
|
||||
#firewall > div[data-des="*"] > span:first-of-type {
|
||||
flex-direction: row;
|
||||
}
|
||||
#firewall > div[data-des="*"] > span:first-of-type > span.filter {
|
||||
flex-grow: 1;
|
||||
padding-inline-start: 2px;
|
||||
-webkit-padding-start: 2px;
|
||||
text-align: left;
|
||||
}
|
||||
#firewall:not(.has3pScript) > [data-type="3p-script"] .filter,
|
||||
#firewall:not(.has3pFrame) > [data-type="3p-frame"] .filter {
|
||||
display: none;
|
||||
}
|
||||
#firewall > [data-des="*"] .filter::after {
|
||||
content: '\22EF';
|
||||
}
|
||||
#firewall.show3pScript > [data-type="3p-script"] .filter::after,
|
||||
#firewall.show3pFrame > [data-type="3p-frame"] .filter::after {
|
||||
content: '\2191';
|
||||
}
|
||||
#firewall.hide3pScript > [data-type="3p-script"] .filter::after,
|
||||
#firewall.hide3pFrame > [data-type="3p-frame"] .filter::after {
|
||||
content: '\2193';
|
||||
}
|
||||
#firewall.show3pScript > div:not([data-des="*"]):not(.hasScript),
|
||||
#firewall.show3pScript > div:not([data-des="*"]):not(.is3p),
|
||||
#firewall.hide3pScript > div:not([data-des="*"]).is3p.hasScript,
|
||||
#firewall.show3pFrame > div:not([data-des="*"]):not(.hasFrame),
|
||||
#firewall.show3pFrame > div:not([data-des="*"]):not(.is3p),
|
||||
#firewall.hide3pFrame > div:not([data-des="*"]).is3p.hasFrame,
|
||||
#firewall.show3pScript.show3pFrame > div:not([data-des="*"]).hasScript:not(.hasFrame),
|
||||
#firewall.show3pScript.show3pFrame > div:not([data-des="*"]).hasFrame:not(.hasScript) {
|
||||
opacity: 0.5;
|
||||
}
|
||||
#firewall > div.isCname > span:first-of-type {
|
||||
color: var(--fg-popup-cell-cname);
|
||||
}
|
||||
@ -431,33 +464,33 @@ body.advancedUser #firewall > div > span:first-of-type ~ span {
|
||||
width: 7px;
|
||||
}
|
||||
#firewall > div.isRootContext > span:first-of-type::before {
|
||||
background-color: var(--fg-0-50);
|
||||
background: var(--fg-0-50);
|
||||
width: 14px !important;
|
||||
}
|
||||
#firewall > div.allowed > span:first-of-type::before,
|
||||
#firewall > div.isDomain.totalAllowed > span:first-of-type::before {
|
||||
background-color: var(--bg-popup-cell-allow-own);
|
||||
background: var(--bg-popup-cell-allow-own);
|
||||
}
|
||||
#firewall > div.blocked > span:first-of-type::before,
|
||||
#firewall > div.isDomain.totalBlocked > span:first-of-type::before {
|
||||
background-color: var(--bg-popup-cell-block-own);
|
||||
background: var(--bg-popup-cell-block-own);
|
||||
}
|
||||
#firewall > div.allowed.blocked > span:first-of-type::before,
|
||||
#firewall > div.isDomain.totalAllowed.totalBlocked > span:first-of-type::before {
|
||||
background-color: var(--bg-popup-cell-label-mixed);
|
||||
background: var(--bg-popup-cell-label-mixed);
|
||||
}
|
||||
/* Rule cells */
|
||||
body.advancedUser #firewall > div > span.allowRule,
|
||||
#actionSelector > #dynaAllow {
|
||||
background-color: var(--bg-popup-cell-allow);
|
||||
background: var(--bg-popup-cell-allow);
|
||||
}
|
||||
body.advancedUser #firewall > div > span.blockRule,
|
||||
#actionSelector > #dynaBlock {
|
||||
background-color: var(--bg-popup-cell-block);
|
||||
background: var(--bg-popup-cell-block);
|
||||
}
|
||||
body.advancedUser #firewall > div > span.noopRule,
|
||||
#actionSelector > #dynaNoop {
|
||||
background-color: var(--bg-popup-cell-noop);
|
||||
background: var(--bg-popup-cell-noop);
|
||||
}
|
||||
body.advancedUser #firewall > div > span.ownRule,
|
||||
#firewall > div > span.ownRule {
|
||||
@ -465,15 +498,15 @@ body.advancedUser #firewall > div > span.ownRule,
|
||||
}
|
||||
body.advancedUser #firewall > div > span.allowRule.ownRule,
|
||||
:root:not(.mobile) #actionSelector > #dynaAllow:hover {
|
||||
background-color: var(--bg-popup-cell-allow-own);
|
||||
background: var(--bg-popup-cell-allow-own);
|
||||
}
|
||||
body.advancedUser #firewall > div > span.blockRule.ownRule,
|
||||
:root:not(.mobile) #actionSelector > #dynaBlock:hover {
|
||||
background-color: var(--bg-popup-cell-block-own);
|
||||
background: var(--bg-popup-cell-block-own);
|
||||
}
|
||||
body.advancedUser #firewall > div > span.noopRule.ownRule,
|
||||
:root:not(.mobile) #actionSelector > #dynaNoop:hover {
|
||||
background-color: var(--bg-popup-cell-noop-own);
|
||||
background: var(--bg-popup-cell-noop-own);
|
||||
}
|
||||
|
||||
#actionSelector {
|
||||
|
@ -26,17 +26,12 @@
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
µBlock.Firewall = (function() {
|
||||
{
|
||||
// >>>>> start of local scope
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var Matrix = function() {
|
||||
this.reset();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var supportedDynamicTypes = {
|
||||
const supportedDynamicTypes = {
|
||||
'3p': true,
|
||||
'image': true,
|
||||
'inline-script': true,
|
||||
@ -45,7 +40,7 @@ var supportedDynamicTypes = {
|
||||
'3p-frame': true
|
||||
};
|
||||
|
||||
var typeBitOffsets = {
|
||||
const typeBitOffsets = {
|
||||
'*': 0,
|
||||
'inline-script': 2,
|
||||
'1p-script': 4,
|
||||
@ -55,13 +50,13 @@ var typeBitOffsets = {
|
||||
'3p': 12
|
||||
};
|
||||
|
||||
var actionToNameMap = {
|
||||
const actionToNameMap = {
|
||||
'1': 'block',
|
||||
'2': 'allow',
|
||||
'3': 'noop'
|
||||
};
|
||||
|
||||
var nameToActionMap = {
|
||||
const nameToActionMap = {
|
||||
'block': 1,
|
||||
'allow': 2,
|
||||
'noop': 3
|
||||
@ -70,12 +65,42 @@ var nameToActionMap = {
|
||||
/******************************************************************************/
|
||||
|
||||
// For performance purpose, as simple tests as possible
|
||||
var reBadHostname = /[^0-9a-z_.\[\]:%-]/;
|
||||
var reNotASCII = /[^\x20-\x7F]/;
|
||||
const reBadHostname = /[^0-9a-z_.\[\]:%-]/;
|
||||
const reNotASCII = /[^\x20-\x7F]/;
|
||||
|
||||
const is3rdParty = function(srcHostname, desHostname) {
|
||||
// If at least one is party-less, the relation can't be labelled
|
||||
// "3rd-party"
|
||||
if ( desHostname === '*' || srcHostname === '*' || srcHostname === '' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// No domain can very well occurs, for examples:
|
||||
// - localhost
|
||||
// - file-scheme
|
||||
// etc.
|
||||
const srcDomain = domainFromHostname(srcHostname) || srcHostname;
|
||||
|
||||
if ( desHostname.endsWith(srcDomain) === false ) {
|
||||
return true;
|
||||
}
|
||||
// Do not confuse 'example.com' with 'anotherexample.com'
|
||||
return desHostname.length !== srcDomain.length &&
|
||||
desHostname.charAt(desHostname.length - srcDomain.length - 1) !== '.';
|
||||
};
|
||||
|
||||
const domainFromHostname = µBlock.URI.domainFromHostname;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.reset = function() {
|
||||
const Matrix = class {
|
||||
|
||||
constructor() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
|
||||
reset() {
|
||||
this.r = 0;
|
||||
this.type = '';
|
||||
this.y = '';
|
||||
@ -84,30 +109,28 @@ Matrix.prototype.reset = function() {
|
||||
this.changed = false;
|
||||
this.decomposedSource = [];
|
||||
this.decomposedDestination = [];
|
||||
};
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.assign = function(other) {
|
||||
assign(other) {
|
||||
// Remove rules not in other
|
||||
for ( var k of this.rules.keys() ) {
|
||||
for ( const k of this.rules.keys() ) {
|
||||
if ( other.rules.has(k) === false ) {
|
||||
this.rules.delete(k);
|
||||
this.changed = true;
|
||||
}
|
||||
}
|
||||
// Add/change rules in other
|
||||
for ( var entry of other.rules ) {
|
||||
for ( const entry of other.rules ) {
|
||||
if ( this.rules.get(entry[0]) !== entry[1] ) {
|
||||
this.rules.set(entry[0], entry[1]);
|
||||
this.changed = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.copyRules = function(from, srcHostname, desHostnames) {
|
||||
copyRules(from, srcHostname, desHostnames) {
|
||||
// Specific types
|
||||
let thisBits = this.rules.get('* *');
|
||||
let fromBits = from.rules.get('* *');
|
||||
@ -133,8 +156,10 @@ Matrix.prototype.copyRules = function(from, srcHostname, desHostnames) {
|
||||
}
|
||||
|
||||
// Specific destinations
|
||||
for ( let desHostname in desHostnames ) {
|
||||
if ( desHostnames.hasOwnProperty(desHostname) === false ) { continue; }
|
||||
for ( const desHostname in desHostnames ) {
|
||||
if ( desHostnames.hasOwnProperty(desHostname) === false ) {
|
||||
continue;
|
||||
}
|
||||
key = '* ' + desHostname;
|
||||
thisBits = this.rules.get(key);
|
||||
fromBits = from.rules.get(key);
|
||||
@ -160,19 +185,17 @@ Matrix.prototype.copyRules = function(from, srcHostname, desHostnames) {
|
||||
}
|
||||
|
||||
return this.changed;
|
||||
};
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// - * * type
|
||||
// - from * type
|
||||
// - * to *
|
||||
// - from to *
|
||||
|
||||
Matrix.prototype.hasSameRules = function(other, srcHostname, desHostnames) {
|
||||
|
||||
hasSameRules(other, srcHostname, desHostnames) {
|
||||
// Specific types
|
||||
var key = '* *';
|
||||
let key = '* *';
|
||||
if ( this.rules.get(key) !== other.rules.get(key) ) {
|
||||
return false;
|
||||
}
|
||||
@ -180,9 +203,8 @@ Matrix.prototype.hasSameRules = function(other, srcHostname, desHostnames) {
|
||||
if ( this.rules.get(key) !== other.rules.get(key) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Specific destinations
|
||||
for ( var desHostname in desHostnames ) {
|
||||
for ( const desHostname in desHostnames ) {
|
||||
key = '* ' + desHostname;
|
||||
if ( this.rules.get(key) !== other.rules.get(key) ) {
|
||||
return false;
|
||||
@ -192,17 +214,15 @@ Matrix.prototype.hasSameRules = function(other, srcHostname, desHostnames) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.setCell = function(srcHostname, desHostname, type, state) {
|
||||
var bitOffset = typeBitOffsets[type];
|
||||
var k = srcHostname + ' ' + desHostname;
|
||||
var oldBitmap = this.rules.get(k) || 0;
|
||||
var newBitmap = oldBitmap & ~(3 << bitOffset) | (state << bitOffset);
|
||||
setCell(srcHostname, desHostname, type, state) {
|
||||
const bitOffset = typeBitOffsets[type];
|
||||
const k = srcHostname + ' ' + desHostname;
|
||||
const oldBitmap = this.rules.get(k) || 0;
|
||||
const newBitmap = oldBitmap & ~(3 << bitOffset) | (state << bitOffset);
|
||||
if ( newBitmap === oldBitmap ) {
|
||||
return false;
|
||||
}
|
||||
@ -213,11 +233,10 @@ Matrix.prototype.setCell = function(srcHostname, desHostname, type, state) {
|
||||
}
|
||||
this.changed = true;
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.unsetCell = function(srcHostname, desHostname, type) {
|
||||
unsetCell(srcHostname, desHostname, type) {
|
||||
this.evaluateCellZY(srcHostname, desHostname, type);
|
||||
if ( this.r === 0 ) {
|
||||
return false;
|
||||
@ -225,61 +244,29 @@ Matrix.prototype.unsetCell = function(srcHostname, desHostname, type) {
|
||||
this.setCell(srcHostname, desHostname, type, 0);
|
||||
this.changed = true;
|
||||
return true;
|
||||
};
|
||||
|
||||
// https://www.youtube.com/watch?v=Csewb_eIStY
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.evaluateCell = function(srcHostname, desHostname, type) {
|
||||
var key = srcHostname + ' ' + desHostname;
|
||||
var bitmap = this.rules.get(key);
|
||||
if ( bitmap === undefined ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
evaluateCell(srcHostname, desHostname, type) {
|
||||
const key = srcHostname + ' ' + desHostname;
|
||||
const bitmap = this.rules.get(key);
|
||||
if ( bitmap === undefined ) { return 0; }
|
||||
return bitmap >> typeBitOffsets[type] & 3;
|
||||
};
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.clearRegisters = function() {
|
||||
clearRegisters() {
|
||||
this.r = 0;
|
||||
this.type = this.y = this.z = '';
|
||||
return this;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var is3rdParty = function(srcHostname, desHostname) {
|
||||
// If at least one is party-less, the relation can't be labelled
|
||||
// "3rd-party"
|
||||
if ( desHostname === '*' || srcHostname === '*' || srcHostname === '' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// No domain can very well occurs, for examples:
|
||||
// - localhost
|
||||
// - file-scheme
|
||||
// etc.
|
||||
var srcDomain = domainFromHostname(srcHostname) || srcHostname;
|
||||
|
||||
if ( desHostname.endsWith(srcDomain) === false ) {
|
||||
return true;
|
||||
}
|
||||
// Do not confuse 'example.com' with 'anotherexample.com'
|
||||
return desHostname.length !== srcDomain.length &&
|
||||
desHostname.charAt(desHostname.length - srcDomain.length - 1) !== '.';
|
||||
};
|
||||
|
||||
var domainFromHostname = µBlock.URI.domainFromHostname;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.evaluateCellZ = function(srcHostname, desHostname, type) {
|
||||
evaluateCellZ(srcHostname, desHostname, type) {
|
||||
µBlock.decomposeHostname(srcHostname, this.decomposedSource);
|
||||
this.type = type;
|
||||
let bitOffset = typeBitOffsets[type];
|
||||
for ( let shn of this.decomposedSource ) {
|
||||
const bitOffset = typeBitOffsets[type];
|
||||
for ( const shn of this.decomposedSource ) {
|
||||
this.z = shn;
|
||||
let v = this.rules.get(shn + ' ' + desHostname);
|
||||
if ( v !== undefined ) {
|
||||
@ -293,11 +280,10 @@ Matrix.prototype.evaluateCellZ = function(srcHostname, desHostname, type) {
|
||||
// srcHostname is '*' at this point
|
||||
this.r = 0;
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.evaluateCellZY = function(srcHostname, desHostname, type) {
|
||||
evaluateCellZY(srcHostname, desHostname, type) {
|
||||
// Pathological cases.
|
||||
if ( desHostname === '' ) {
|
||||
this.r = 0;
|
||||
@ -308,7 +294,7 @@ Matrix.prototype.evaluateCellZY = function(srcHostname, desHostname, type) {
|
||||
|
||||
// Specific-destination, any party, any type
|
||||
µBlock.decomposeHostname(desHostname, this.decomposedDestination);
|
||||
for ( let dhn of this.decomposedDestination ) {
|
||||
for ( const dhn of this.decomposedDestination ) {
|
||||
if ( dhn === '*' ) { break; }
|
||||
this.y = dhn;
|
||||
if ( this.evaluateCellZ(srcHostname, dhn, '*') !== 0 ) {
|
||||
@ -316,12 +302,13 @@ Matrix.prototype.evaluateCellZY = function(srcHostname, desHostname, type) {
|
||||
}
|
||||
}
|
||||
|
||||
let thirdParty = is3rdParty(srcHostname, desHostname);
|
||||
const thirdParty = is3rdParty(srcHostname, desHostname);
|
||||
|
||||
// Any destination
|
||||
this.y = '*';
|
||||
|
||||
// Specific party
|
||||
// TODO: equate `object` as `sub_frame`
|
||||
if ( thirdParty ) {
|
||||
// 3rd-party, specific type
|
||||
if ( type === 'script' ) {
|
||||
@ -358,89 +345,65 @@ Matrix.prototype.evaluateCellZY = function(srcHostname, desHostname, type) {
|
||||
|
||||
this.type = '';
|
||||
return 0;
|
||||
};
|
||||
|
||||
// http://youtu.be/gSGk1bQ9rcU?t=25m6s
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.mustAllowCellZY = function(srcHostname, desHostname, type) {
|
||||
return this.evaluateCellZY(srcHostname, desHostname, type) === 2;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.mustBlockOrAllow = function() {
|
||||
return this.r === 1 || this.r === 2;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.mustBlock = function() {
|
||||
return this.r === 1;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.mustAbort = function() {
|
||||
return this.r === 3;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.lookupRuleData = function(src, des, type) {
|
||||
var r = this.evaluateCellZY(src, des, type);
|
||||
if ( r === 0 ) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
src: this.z,
|
||||
des: this.y,
|
||||
type: this.type,
|
||||
action: r === 1 ? 'block' : (r === 2 ? 'allow' : 'noop')
|
||||
};
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.toLogData = function() {
|
||||
mustAllowCellZY(srcHostname, desHostname, type) {
|
||||
return this.evaluateCellZY(srcHostname, desHostname, type) === 2;
|
||||
}
|
||||
|
||||
|
||||
mustBlockOrAllow() {
|
||||
return this.r === 1 || this.r === 2;
|
||||
}
|
||||
|
||||
|
||||
mustBlock() {
|
||||
return this.r === 1;
|
||||
}
|
||||
|
||||
|
||||
mustAbort() {
|
||||
return this.r === 3;
|
||||
}
|
||||
|
||||
|
||||
lookupRuleData(src, des, type) {
|
||||
const r = this.evaluateCellZY(src, des, type);
|
||||
if ( r === 0 ) { return; }
|
||||
return `${this.z} ${this.y} ${this.type} ${r}`;
|
||||
}
|
||||
|
||||
|
||||
toLogData() {
|
||||
if ( this.r === 0 || this.type === '' ) { return; }
|
||||
return {
|
||||
source: 'dynamicHost',
|
||||
result: this.r,
|
||||
raw: `${this.z} ${this.y} ${this.type} ${this.intToActionMap.get(this.r)}`
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Matrix.prototype.intToActionMap = new Map([
|
||||
[ 1, 'block' ],
|
||||
[ 2, 'allow' ],
|
||||
[ 3, 'noop' ]
|
||||
]);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.srcHostnameFromRule = function(rule) {
|
||||
srcHostnameFromRule(rule) {
|
||||
return rule.slice(0, rule.indexOf(' '));
|
||||
};
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.desHostnameFromRule = function(rule) {
|
||||
desHostnameFromRule(rule) {
|
||||
return rule.slice(rule.indexOf(' ') + 1);
|
||||
};
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.toArray = function() {
|
||||
var out = [],
|
||||
toArray() {
|
||||
const out = [],
|
||||
toUnicode = punycode.toUnicode;
|
||||
for ( var key of this.rules.keys() ) {
|
||||
var srcHostname = this.srcHostnameFromRule(key);
|
||||
var desHostname = this.desHostnameFromRule(key);
|
||||
for ( var type in typeBitOffsets ) {
|
||||
for ( const key of this.rules.keys() ) {
|
||||
let srcHostname = this.srcHostnameFromRule(key);
|
||||
let desHostname = this.desHostnameFromRule(key);
|
||||
for ( const type in typeBitOffsets ) {
|
||||
if ( typeBitOffsets.hasOwnProperty(type) === false ) { continue; }
|
||||
var val = this.evaluateCell(srcHostname, desHostname, type);
|
||||
const val = this.evaluateCell(srcHostname, desHostname, type);
|
||||
if ( val === 0 ) { continue; }
|
||||
if ( srcHostname.indexOf('xn--') !== -1 ) {
|
||||
srcHostname = toUnicode(srcHostname);
|
||||
@ -457,25 +420,24 @@ Matrix.prototype.toArray = function() {
|
||||
}
|
||||
}
|
||||
return out;
|
||||
};
|
||||
}
|
||||
|
||||
Matrix.prototype.toString = function() {
|
||||
|
||||
toString() {
|
||||
return this.toArray().join('\n');
|
||||
};
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.fromString = function(text, append) {
|
||||
var lineIter = new µBlock.LineIterator(text);
|
||||
fromString(text, append) {
|
||||
const lineIter = new µBlock.LineIterator(text);
|
||||
if ( append !== true ) { this.reset(); }
|
||||
while ( lineIter.eot() === false ) {
|
||||
this.addFromRuleParts(lineIter.next().trim().split(/\s+/));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.validateRuleParts = function(parts) {
|
||||
validateRuleParts(parts) {
|
||||
if ( parts.length < 4 ) { return; }
|
||||
|
||||
// Ignore hostname-based switch rules
|
||||
@ -506,47 +468,44 @@ Matrix.prototype.validateRuleParts = function(parts) {
|
||||
}
|
||||
|
||||
return parts;
|
||||
};
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.addFromRuleParts = function(parts) {
|
||||
addFromRuleParts(parts) {
|
||||
if ( this.validateRuleParts(parts) !== undefined ) {
|
||||
this.setCell(parts[0], parts[1], parts[2], nameToActionMap[parts[3]]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
Matrix.prototype.removeFromRuleParts = function(parts) {
|
||||
|
||||
removeFromRuleParts(parts) {
|
||||
if ( this.validateRuleParts(parts) !== undefined ) {
|
||||
this.setCell(parts[0], parts[1], parts[2], 0);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const magicId = 1;
|
||||
|
||||
Matrix.prototype.toSelfie = function() {
|
||||
toSelfie() {
|
||||
return {
|
||||
magicId: magicId,
|
||||
magicId: this.magicId,
|
||||
rules: Array.from(this.rules)
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Matrix.prototype.fromSelfie = function(selfie) {
|
||||
if ( selfie.magicId !== magicId ) { return false; }
|
||||
|
||||
fromSelfie(selfie) {
|
||||
if ( selfie.magicId !== this.magicId ) { return false; }
|
||||
this.rules = new Map(selfie.rules);
|
||||
this.changed = true;
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
Matrix.prototype.benchmark = async function() {
|
||||
async benchmark() {
|
||||
const requests = await µBlock.loadBenchmarkDataset();
|
||||
if ( Array.isArray(requests) === false || requests.length === 0 ) {
|
||||
log.print('No requests found to benchmark');
|
||||
@ -569,17 +528,23 @@ Matrix.prototype.benchmark = async function() {
|
||||
const dur = t1 - t0;
|
||||
log.print(`Evaluated ${requests.length} requests in ${dur.toFixed(0)} ms`);
|
||||
log.print(`\tAverage: ${(dur / requests.length).toFixed(3)} ms per request`);
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
Matrix.prototype.intToActionMap = new Map([
|
||||
[ 1, 'block' ],
|
||||
[ 2, 'allow' ],
|
||||
[ 3, 'noop' ]
|
||||
]);
|
||||
|
||||
return Matrix;
|
||||
Matrix.prototype.magicId = 1;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// http://youtu.be/5-K8R1hDG9E?t=31m1s
|
||||
µBlock.Firewall = Matrix;
|
||||
|
||||
})();
|
||||
// <<<<< end of local scope
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
@ -216,78 +216,78 @@ vAPI.messaging.setup(onMessage);
|
||||
|
||||
const µb = µBlock;
|
||||
|
||||
const getHostnameDict = function(hostnameToCountMap, out) {
|
||||
const createCounts = ( ) => {
|
||||
return {
|
||||
blocked: { any: 0, frame: 0, script: 0 },
|
||||
allowed: { any: 0, frame: 0, script: 0 },
|
||||
};
|
||||
};
|
||||
|
||||
const getHostnameDict = function(hostnameDetailsMap, out) {
|
||||
const hnDict = Object.create(null);
|
||||
const cnMap = [];
|
||||
for ( const [ hostname, hnCounts ] of hostnameToCountMap ) {
|
||||
if ( hnDict[hostname] !== undefined ) { continue; }
|
||||
const domain = vAPI.domainFromHostname(hostname) || hostname;
|
||||
const dnCounts = hostnameToCountMap.get(domain) || 0;
|
||||
let blockCount = dnCounts & 0xFFFF;
|
||||
let allowCount = dnCounts >>> 16 & 0xFFFF;
|
||||
if ( hnDict[domain] === undefined ) {
|
||||
hnDict[domain] = {
|
||||
domain,
|
||||
blockCount,
|
||||
allowCount,
|
||||
totalBlockCount: blockCount,
|
||||
totalAllowCount: allowCount,
|
||||
};
|
||||
const cname = vAPI.net.canonicalNameFromHostname(domain);
|
||||
if ( cname !== undefined ) {
|
||||
cnMap.push([ cname, domain ]);
|
||||
}
|
||||
}
|
||||
const domainEntry = hnDict[domain];
|
||||
blockCount = hnCounts & 0xFFFF;
|
||||
allowCount = hnCounts >>> 16 & 0xFFFF;
|
||||
domainEntry.totalBlockCount += blockCount;
|
||||
domainEntry.totalAllowCount += allowCount;
|
||||
if ( hostname === domain ) { continue; }
|
||||
hnDict[hostname] = {
|
||||
domain,
|
||||
blockCount,
|
||||
allowCount,
|
||||
totalBlockCount: 0,
|
||||
totalAllowCount: 0,
|
||||
};
|
||||
|
||||
const createDictEntry = (domain, hostname, details) => {
|
||||
const cname = vAPI.net.canonicalNameFromHostname(hostname);
|
||||
if ( cname !== undefined ) {
|
||||
cnMap.push([ cname, hostname ]);
|
||||
}
|
||||
hnDict[hostname] = { domain, counts: details.counts };
|
||||
};
|
||||
|
||||
for ( const hnDetails of hostnameDetailsMap.values() ) {
|
||||
const hostname = hnDetails.hostname;
|
||||
if ( hnDict[hostname] !== undefined ) { continue; }
|
||||
const domain = vAPI.domainFromHostname(hostname) || hostname;
|
||||
const dnDetails =
|
||||
hostnameDetailsMap.get(domain) || { counts: createCounts() };
|
||||
if ( hnDict[domain] === undefined ) {
|
||||
createDictEntry(domain, domain, dnDetails);
|
||||
}
|
||||
if ( hostname === domain ) { continue; }
|
||||
createDictEntry(domain, hostname, hnDetails);
|
||||
}
|
||||
|
||||
out.hostnameDict = hnDict;
|
||||
out.cnameMap = cnMap;
|
||||
};
|
||||
|
||||
const getFirewallRules = function(srcHostname, desHostnames) {
|
||||
const out = {};
|
||||
const df = µb.sessionFirewall;
|
||||
out['/ * *'] = df.lookupRuleData('*', '*', '*');
|
||||
out['/ * image'] = df.lookupRuleData('*', '*', 'image');
|
||||
out['/ * 3p'] = df.lookupRuleData('*', '*', '3p');
|
||||
out['/ * inline-script'] = df.lookupRuleData('*', '*', 'inline-script');
|
||||
out['/ * 1p-script'] = df.lookupRuleData('*', '*', '1p-script');
|
||||
out['/ * 3p-script'] = df.lookupRuleData('*', '*', '3p-script');
|
||||
out['/ * 3p-frame'] = df.lookupRuleData('*', '*', '3p-frame');
|
||||
if ( typeof srcHostname !== 'string' ) { return out; }
|
||||
|
||||
out['. * *'] = df.lookupRuleData(srcHostname, '*', '*');
|
||||
out['. * image'] = df.lookupRuleData(srcHostname, '*', 'image');
|
||||
out['. * 3p'] = df.lookupRuleData(srcHostname, '*', '3p');
|
||||
out['. * inline-script'] = df.lookupRuleData(srcHostname,
|
||||
const firewallRuleTypes = [
|
||||
'*',
|
||||
'inline-script'
|
||||
);
|
||||
out['. * 1p-script'] = df.lookupRuleData(srcHostname, '*', '1p-script');
|
||||
out['. * 3p-script'] = df.lookupRuleData(srcHostname, '*', '3p-script');
|
||||
out['. * 3p-frame'] = df.lookupRuleData(srcHostname, '*', '3p-frame');
|
||||
'image',
|
||||
'3p',
|
||||
'inline-script',
|
||||
'1p-script',
|
||||
'3p-script',
|
||||
'3p-frame',
|
||||
];
|
||||
|
||||
for ( const desHostname in desHostnames ) {
|
||||
out[`/ ${desHostname} *`] = df.lookupRuleData('*', desHostname, '*');
|
||||
out[`. ${desHostname} *`] = df.lookupRuleData(srcHostname, desHostname, '*');
|
||||
const getFirewallRules = function(src, out) {
|
||||
const { hostnameDict } = out;
|
||||
const ruleset = {};
|
||||
const df = µb.sessionFirewall;
|
||||
|
||||
for ( const type of firewallRuleTypes ) {
|
||||
let r = df.lookupRuleData('*', '*', type);
|
||||
if ( r === undefined ) { continue; }
|
||||
ruleset[`/ * ${type}`] = r;
|
||||
}
|
||||
return out;
|
||||
if ( typeof src !== 'string' ) { return out; }
|
||||
|
||||
for ( const type of firewallRuleTypes ) {
|
||||
let r = df.lookupRuleData(src, '*', type);
|
||||
if ( r === undefined ) { continue; }
|
||||
ruleset[`. * ${type}`] = r;
|
||||
}
|
||||
|
||||
for ( const des in hostnameDict ) {
|
||||
let r = df.lookupRuleData('*', des, '*');
|
||||
if ( r !== undefined ) { ruleset[`/ ${des} *`] = r; }
|
||||
r = df.lookupRuleData(src, des, '*');
|
||||
if ( r !== undefined ) { ruleset[`. ${des} *`] = r; }
|
||||
}
|
||||
|
||||
out.firewallRules = ruleset;
|
||||
};
|
||||
|
||||
const popupDataFromTabId = function(tabId, tabTitle) {
|
||||
@ -311,8 +311,6 @@ const popupDataFromTabId = function(tabId, tabTitle) {
|
||||
pageURL: tabContext.normalURL,
|
||||
pageHostname: rootHostname,
|
||||
pageDomain: tabContext.rootDomain,
|
||||
pageAllowedRequestCount: 0,
|
||||
pageBlockedRequestCount: 0,
|
||||
popupBlockedCount: 0,
|
||||
popupPanelSections: µbus.popupPanelSections,
|
||||
popupPanelDisabledSections: µbhs.popupPanelDisabledSections,
|
||||
@ -329,23 +327,11 @@ const popupDataFromTabId = function(tabId, tabTitle) {
|
||||
|
||||
const pageStore = µb.pageStoreFromTabId(tabId);
|
||||
if ( pageStore ) {
|
||||
// https://github.com/gorhill/uBlock/issues/2105
|
||||
// Be sure to always include the current page's hostname -- it
|
||||
// might not be present when the page itself is pulled from the
|
||||
// browser's short-term memory cache. This needs to be done
|
||||
// before calling getHostnameDict().
|
||||
if (
|
||||
pageStore.hostnameToCountMap.has(rootHostname) === false &&
|
||||
µb.URI.isNetworkURI(tabContext.rawURL)
|
||||
) {
|
||||
pageStore.hostnameToCountMap.set(rootHostname, 0);
|
||||
}
|
||||
r.pageBlockedRequestCount = pageStore.perLoadBlockedRequestCount;
|
||||
r.pageAllowedRequestCount = pageStore.perLoadAllowedRequestCount;
|
||||
r.pageCounts = pageStore.counts;
|
||||
r.netFilteringSwitch = pageStore.getNetFilteringSwitch();
|
||||
getHostnameDict(pageStore.hostnameToCountMap, r);
|
||||
getHostnameDict(pageStore.getAllHostnameDetails(), r);
|
||||
r.contentLastModified = pageStore.contentLastModified;
|
||||
r.firewallRules = getFirewallRules(rootHostname, r.hostnameDict);
|
||||
getFirewallRules(rootHostname, r);
|
||||
r.canElementPicker = µb.URI.isNetworkURI(r.rawURL);
|
||||
r.noPopups = µb.sessionSwitches.evaluateZ(
|
||||
'no-popups',
|
||||
|
@ -176,10 +176,6 @@ NetFilteringResultCache.prototype.extensionOriginURL = vAPI.getURL('/');
|
||||
|
||||
// Frame stores are used solely to associate a URL with a frame id.
|
||||
|
||||
// To mitigate memory churning
|
||||
const frameStoreJunkyard = [];
|
||||
const frameStoreJunkyardMax = 50;
|
||||
|
||||
const FrameStore = class {
|
||||
constructor(frameURL, parentId) {
|
||||
this.init(frameURL, parentId);
|
||||
@ -201,14 +197,14 @@ const FrameStore = class {
|
||||
|
||||
dispose() {
|
||||
this.rawURL = this.hostname = this.domain = '';
|
||||
if ( frameStoreJunkyard.length < frameStoreJunkyardMax ) {
|
||||
frameStoreJunkyard.push(this);
|
||||
if ( FrameStore.junkyard.length < FrameStore.junkyardMax ) {
|
||||
FrameStore.junkyard.push(this);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static factory(frameURL, parentId = -1) {
|
||||
const entry = frameStoreJunkyard.pop();
|
||||
const entry = FrameStore.junkyard.pop();
|
||||
if ( entry === undefined ) {
|
||||
return new FrameStore(frameURL, parentId);
|
||||
}
|
||||
@ -216,11 +212,62 @@ const FrameStore = class {
|
||||
}
|
||||
};
|
||||
|
||||
// To mitigate memory churning
|
||||
FrameStore.junkyard = [];
|
||||
FrameStore.junkyardMax = 50;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// To mitigate memory churning
|
||||
const pageStoreJunkyard = [];
|
||||
const pageStoreJunkyardMax = 10;
|
||||
const CountDetails = class {
|
||||
constructor() {
|
||||
this.allowed = { any: 0, frame: 0, script: 0 };
|
||||
this.blocked = { any: 0, frame: 0, script: 0 };
|
||||
}
|
||||
reset() {
|
||||
const { allowed, blocked } = this;
|
||||
blocked.any = blocked.frame = blocked.script =
|
||||
allowed.any = allowed.frame = allowed.script = 0;
|
||||
}
|
||||
inc(blocked, type = undefined) {
|
||||
const stat = blocked ? this.blocked : this.allowed;
|
||||
if ( type !== undefined ) { stat[type] += 1; }
|
||||
stat.any += 1;
|
||||
}
|
||||
};
|
||||
|
||||
const HostnameDetails = class {
|
||||
constructor(hostname) {
|
||||
this.counts = new CountDetails();
|
||||
this.init(hostname);
|
||||
}
|
||||
init(hostname) {
|
||||
this.hostname = hostname;
|
||||
this.counts.reset();
|
||||
}
|
||||
dispose() {
|
||||
this.hostname = '';
|
||||
if ( HostnameDetails.junkyard.length < HostnameDetails.junkyardMax ) {
|
||||
HostnameDetails.junkyard.push(this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
HostnameDetails.junkyard = [];
|
||||
HostnameDetails.junkyardMax = 100;
|
||||
|
||||
const HostnameDetailsMap = class extends Map {
|
||||
reset() {
|
||||
this.clear();
|
||||
}
|
||||
dispose() {
|
||||
for ( const item of this.values() ) {
|
||||
item.dispose();
|
||||
}
|
||||
this.reset();
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const PageStore = class {
|
||||
constructor(tabId, context) {
|
||||
@ -230,11 +277,13 @@ const PageStore = class {
|
||||
this.journalLastCommitted = this.journalLastUncommitted = -1;
|
||||
this.journalLastUncommittedOrigin = undefined;
|
||||
this.netFilteringCache = NetFilteringResultCache.factory();
|
||||
this.hostnameDetailsMap = new HostnameDetailsMap();
|
||||
this.counts = new CountDetails();
|
||||
this.init(tabId, context);
|
||||
}
|
||||
|
||||
static factory(tabId, context) {
|
||||
let entry = pageStoreJunkyard.pop();
|
||||
let entry = PageStore.junkyard.pop();
|
||||
if ( entry === undefined ) {
|
||||
entry = new PageStore(tabId, context);
|
||||
} else {
|
||||
@ -263,11 +312,10 @@ const PageStore = class {
|
||||
this.tabHostname = tabContext.rootHostname;
|
||||
this.title = tabContext.rawURL;
|
||||
this.rawURL = tabContext.rawURL;
|
||||
this.hostnameToCountMap = new Map();
|
||||
this.hostnameDetailsMap.reset();
|
||||
this.contentLastModified = 0;
|
||||
this.logData = undefined;
|
||||
this.perLoadBlockedRequestCount = 0;
|
||||
this.perLoadAllowedRequestCount = 0;
|
||||
this.counts.reset();
|
||||
this.remoteFontCount = 0;
|
||||
this.popupBlockedCount = 0;
|
||||
this.largeMediaCount = 0;
|
||||
@ -342,7 +390,7 @@ const PageStore = class {
|
||||
this.tabHostname = '';
|
||||
this.title = '';
|
||||
this.rawURL = '';
|
||||
this.hostnameToCountMap = null;
|
||||
this.hostnameDetailsMap.dispose();
|
||||
this.netFilteringCache.empty();
|
||||
this.allowLargeMediaElementsUntil = Date.now();
|
||||
this.allowLargeMediaElementsRegex = undefined;
|
||||
@ -358,8 +406,8 @@ const PageStore = class {
|
||||
this.journal = [];
|
||||
this.journalLastUncommittedOrigin = undefined;
|
||||
this.journalLastCommitted = this.journalLastUncommitted = -1;
|
||||
if ( pageStoreJunkyard.length < pageStoreJunkyardMax ) {
|
||||
pageStoreJunkyard.push(this);
|
||||
if ( PageStore.junkyard.length < PageStore.junkyardMax ) {
|
||||
PageStore.junkyard.push(this);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -454,6 +502,23 @@ const PageStore = class {
|
||||
this.netFilteringCache.empty();
|
||||
}
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/2105
|
||||
// Be sure to always include the current page's hostname -- it might not
|
||||
// be present when the page itself is pulled from the browser's
|
||||
// short-term memory cache.
|
||||
getAllHostnameDetails() {
|
||||
if (
|
||||
this.hostnameDetailsMap.has(this.tabHostname) === false &&
|
||||
µb.URI.isNetworkURI(this.rawURL)
|
||||
) {
|
||||
this.hostnameDetailsMap.set(
|
||||
this.tabHostname,
|
||||
new HostnameDetails(this.tabHostname)
|
||||
);
|
||||
}
|
||||
return this.hostnameDetailsMap;
|
||||
}
|
||||
|
||||
injectLargeMediaElementScriptlet() {
|
||||
vAPI.tabs.executeScript(this.tabId, {
|
||||
file: '/js/scriptlets/load-large-media-interactive.js',
|
||||
@ -478,19 +543,16 @@ const PageStore = class {
|
||||
// https://github.com/gorhill/uBlock/issues/2053
|
||||
// There is no way around using journaling to ensure we deal properly with
|
||||
// potentially out of order navigation events vs. network request events.
|
||||
journalAddRequest(hostname, result) {
|
||||
journalAddRequest(fctxt, result) {
|
||||
const hostname = fctxt.getHostname();
|
||||
if ( hostname === '' ) { return; }
|
||||
this.journal.push(
|
||||
hostname,
|
||||
result === 1 ? 0x00000001 : 0x00010000
|
||||
);
|
||||
if ( this.journalTimer === undefined ) {
|
||||
this.journal.push(hostname, result, fctxt.itype);
|
||||
if ( this.journalTimer !== undefined ) { return; }
|
||||
this.journalTimer = vAPI.setTimeout(
|
||||
( ) => { this.journalProcess(true); },
|
||||
µb.hiddenSettings.requestJournalProcessPeriod
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
journalAddRootFrame(type, url) {
|
||||
if ( type === 'committed' ) {
|
||||
@ -528,40 +590,57 @@ const PageStore = class {
|
||||
const journal = this.journal;
|
||||
const pivot = Math.max(0, this.journalLastCommitted);
|
||||
const now = Date.now();
|
||||
let aggregateCounts = 0;
|
||||
const { SCRIPT, SUB_FRAME } = µb.FilteringContext;
|
||||
let aggregateAllowed = 0;
|
||||
let aggregateBlocked = 0;
|
||||
|
||||
// Everything after pivot originates from current page.
|
||||
for ( let i = pivot; i < journal.length; i += 2 ) {
|
||||
const hostname = journal[i];
|
||||
let hostnameCounts = this.hostnameToCountMap.get(hostname);
|
||||
if ( hostnameCounts === undefined ) {
|
||||
hostnameCounts = 0;
|
||||
for ( let i = pivot; i < journal.length; i += 3 ) {
|
||||
const hostname = journal[i+0];
|
||||
let hnDetails = this.hostnameDetailsMap.get(hostname);
|
||||
if ( hnDetails === undefined ) {
|
||||
hnDetails = new HostnameDetails(hostname);
|
||||
this.hostnameDetailsMap.set(hostname, hnDetails);
|
||||
this.contentLastModified = now;
|
||||
}
|
||||
let count = journal[i+1];
|
||||
this.hostnameToCountMap.set(hostname, hostnameCounts + count);
|
||||
aggregateCounts += count;
|
||||
const blocked = journal[i+1] === 1;
|
||||
const itype = journal[i+2];
|
||||
if ( itype === SCRIPT ) {
|
||||
hnDetails.counts.inc(blocked, 'script');
|
||||
this.counts.inc(blocked, 'script');
|
||||
} else if ( itype === SUB_FRAME ) {
|
||||
hnDetails.counts.inc(blocked, 'frame');
|
||||
this.counts.inc(blocked, 'frame');
|
||||
} else {
|
||||
hnDetails.counts.inc(blocked);
|
||||
this.counts.inc(blocked);
|
||||
}
|
||||
if ( blocked ) {
|
||||
aggregateBlocked += 1;
|
||||
} else {
|
||||
aggregateAllowed += 1;
|
||||
}
|
||||
}
|
||||
this.perLoadBlockedRequestCount += aggregateCounts & 0xFFFF;
|
||||
this.perLoadAllowedRequestCount += aggregateCounts >>> 16 & 0xFFFF;
|
||||
this.journalLastUncommitted = this.journalLastCommitted = -1;
|
||||
|
||||
// https://github.com/chrisaljoudi/uBlock/issues/905#issuecomment-76543649
|
||||
// No point updating the badge if it's not being displayed.
|
||||
if ( (aggregateCounts & 0xFFFF) && µb.userSettings.showIconBadge ) {
|
||||
if ( aggregateBlocked !== 0 && µb.userSettings.showIconBadge ) {
|
||||
µb.updateToolbarIcon(this.tabId, 0x02);
|
||||
}
|
||||
|
||||
// Everything before pivot does not originate from current page -- we
|
||||
// still need to bump global blocked/allowed counts.
|
||||
for ( let i = 0; i < pivot; i += 2 ) {
|
||||
aggregateCounts += journal[i+1];
|
||||
for ( let i = 0; i < pivot; i += 3 ) {
|
||||
if ( journal[i+1] === 1 ) {
|
||||
aggregateBlocked += 1;
|
||||
} else {
|
||||
aggregateAllowed += 1;
|
||||
}
|
||||
if ( aggregateCounts !== 0 ) {
|
||||
µb.localSettings.blockedRequestCount +=
|
||||
aggregateCounts & 0xFFFF;
|
||||
µb.localSettings.allowedRequestCount +=
|
||||
aggregateCounts >>> 16 & 0xFFFF;
|
||||
}
|
||||
if ( aggregateAllowed !== 0 || aggregateBlocked !== 0 ) {
|
||||
µb.localSettings.blockedRequestCount += aggregateBlocked;
|
||||
µb.localSettings.allowedRequestCount += aggregateAllowed;
|
||||
µb.localSettingsLastModified = now;
|
||||
}
|
||||
journal.length = 0;
|
||||
@ -948,6 +1027,10 @@ PageStore.prototype.collapsibleResources = new Set([
|
||||
µb.FilteringContext.SUB_FRAME,
|
||||
]);
|
||||
|
||||
// To mitigate memory churning
|
||||
PageStore.junkyard = [];
|
||||
PageStore.junkyardMax = 10;
|
||||
|
||||
µb.PageStore = PageStore;
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -49,7 +49,6 @@ vAPI.localStorage.getItemAsync('popupPanelSections').then(bits => {
|
||||
/******************************************************************************/
|
||||
|
||||
const messaging = vAPI.messaging;
|
||||
const reIP = /^\d+(?:\.\d+){1,3}$/;
|
||||
const scopeToSrcHostnameMap = {
|
||||
'/': '*',
|
||||
'.': ''
|
||||
@ -61,10 +60,7 @@ const domainsHitStr = vAPI.i18n('popupHitDomainCount');
|
||||
let popupData = {};
|
||||
let dfPaneBuilt = false;
|
||||
let dfHotspots = null;
|
||||
let allDomains = {};
|
||||
let allDomainCount = 0;
|
||||
let allHostnameRows = [];
|
||||
let touchedDomainCount = 0;
|
||||
let cachedPopupHash = '';
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/2550
|
||||
@ -125,13 +121,8 @@ const hashFromPopupData = function(reset) {
|
||||
const rules = popupData.firewallRules;
|
||||
for ( const key in rules ) {
|
||||
const rule = rules[key];
|
||||
if ( rule === null ) { continue; }
|
||||
hasher.push(
|
||||
rule.src + ' ' +
|
||||
rule.des + ' ' +
|
||||
rule.type + ' ' +
|
||||
rule.action
|
||||
);
|
||||
if ( rule === undefined ) { continue; }
|
||||
hasher.push(rule);
|
||||
}
|
||||
hasher.sort();
|
||||
hasher.push(uDom('body').hasClass('off'));
|
||||
@ -149,6 +140,12 @@ const hashFromPopupData = function(reset) {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// greater-than-zero test
|
||||
|
||||
const gtz = n => typeof n === 'number' && n > 0;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const formatNumber = function(count) {
|
||||
if ( typeof count !== 'number' ) { return ''; }
|
||||
if ( count < 1e6 ) { return count.toLocaleString(); }
|
||||
@ -202,107 +199,149 @@ const safePunycodeToUnicode = function(hn) {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const rulekeyCompare = function(a, b) {
|
||||
let ha = a.slice(2, a.indexOf(' ', 2));
|
||||
if ( !reIP.test(ha) ) {
|
||||
ha = hostnameToSortableTokenMap.get(ha) || ' ';
|
||||
const updateFirewallCellCount = function(cells, allowed, blocked) {
|
||||
for ( const cell of cells ) {
|
||||
if ( gtz(allowed) ) {
|
||||
cell.setAttribute(
|
||||
'data-acount',
|
||||
Math.min(Math.ceil(Math.log(allowed + 1) / Math.LN10), 3)
|
||||
);
|
||||
} else {
|
||||
cell.setAttribute('data-acount', '0');
|
||||
}
|
||||
let hb = b.slice(2, b.indexOf(' ', 2));
|
||||
if ( !reIP.test(hb) ) {
|
||||
hb = hostnameToSortableTokenMap.get(hb) || ' ';
|
||||
if ( gtz(blocked) ) {
|
||||
cell.setAttribute(
|
||||
'data-bcount',
|
||||
Math.min(Math.ceil(Math.log(blocked + 1) / Math.LN10), 3)
|
||||
);
|
||||
} else {
|
||||
cell.setAttribute('data-bcount', '0');
|
||||
}
|
||||
const ca = ha.charCodeAt(0);
|
||||
const cb = hb.charCodeAt(0);
|
||||
if ( ca !== cb ) {
|
||||
return ca - cb;
|
||||
}
|
||||
return ha.localeCompare(hb);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const updateFirewallCell = function(scope, des, type, rule) {
|
||||
const row = document.querySelector(
|
||||
`#firewall div[data-des="${des}"][data-type="${type}"]`
|
||||
);
|
||||
if ( row === null ) { return; }
|
||||
const updateFirewallCellRule = function(cells, scope, des, type, rule) {
|
||||
const ruleParts = rule !== undefined ? rule.split(' ') : undefined;
|
||||
|
||||
const cells = row.querySelectorAll(`:scope > span[data-src="${scope}"]`);
|
||||
if ( cells.length === 0 ) { return; }
|
||||
|
||||
if ( rule !== null ) {
|
||||
cells.forEach(el => { el.setAttribute('class', rule.action + 'Rule'); });
|
||||
} else {
|
||||
cells.forEach(el => { el.removeAttribute('class'); });
|
||||
for ( const cell of cells ) {
|
||||
if ( ruleParts === undefined ) {
|
||||
cell.removeAttribute('class');
|
||||
continue;
|
||||
}
|
||||
|
||||
const action = updateFirewallCellRule.actionNames[ruleParts[3]];
|
||||
cell.setAttribute('class', `${action}Rule`);
|
||||
|
||||
// Use dark shade visual cue if the rule is specific to the cell.
|
||||
if (
|
||||
(rule !== null) &&
|
||||
(rule.des !== '*' || rule.type === type) &&
|
||||
(rule.des === des) &&
|
||||
(rule.src === scopeToSrcHostnameMap[scope])
|
||||
(ruleParts[1] !== '*' || ruleParts[2] === type) &&
|
||||
(ruleParts[1] === des) &&
|
||||
(ruleParts[0] === scopeToSrcHostnameMap[scope])
|
||||
|
||||
) {
|
||||
cells.forEach(el => { el.classList.add('ownRule'); });
|
||||
cell.classList.add('ownRule');
|
||||
}
|
||||
|
||||
if ( scope !== '.' || des === '*' ) { return; }
|
||||
|
||||
// Remember this may be a cell from a reused row, we need to clear text
|
||||
// content if we can't compute request counts.
|
||||
if ( popupData.hostnameDict.hasOwnProperty(des) === false ) {
|
||||
cells.forEach(el => {
|
||||
el.removeAttribute('data-acount');
|
||||
el.removeAttribute('data-bcount');
|
||||
});
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
updateFirewallCellRule.actionNames = { '1': 'block', '2': 'allow', '3': 'noop' };
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const updateAllFirewallCells = function(doRules = true, doCounts = true) {
|
||||
const { pageDomain } = popupData;
|
||||
const rowContainer = document.getElementById('firewall');
|
||||
const rows = rowContainer.querySelectorAll('#firewall > [data-des][data-type]');
|
||||
|
||||
let a1pScript = 0, b1pScript = 0;
|
||||
let a3pScript = 0, b3pScript = 0;
|
||||
let a3pFrame = 0, b3pFrame = 0;
|
||||
|
||||
for ( const row of rows ) {
|
||||
const des = row.getAttribute('data-des');
|
||||
const type = row.getAttribute('data-type');
|
||||
if ( doRules ) {
|
||||
updateFirewallCellRule(
|
||||
row.querySelectorAll(`:scope > span[data-src="/"]`),
|
||||
'/',
|
||||
des,
|
||||
type,
|
||||
popupData.firewallRules[`/ ${des} ${type}`]
|
||||
);
|
||||
}
|
||||
const cells = row.querySelectorAll(`:scope > span[data-src="."]`);
|
||||
if ( doRules ) {
|
||||
updateFirewallCellRule(
|
||||
cells,
|
||||
'.',
|
||||
des,
|
||||
type,
|
||||
popupData.firewallRules[`. ${des} ${type}`]
|
||||
);
|
||||
}
|
||||
if ( des === '*' || type !== '*' ) { continue; }
|
||||
if ( doCounts === false ) { continue; }
|
||||
const hnDetails = popupData.hostnameDict[des];
|
||||
let cell = cells[0];
|
||||
if ( hnDetails.allowCount !== 0 ) {
|
||||
cell.setAttribute('data-acount', Math.min(Math.ceil(Math.log(hnDetails.allowCount + 1) / Math.LN10), 3));
|
||||
} else {
|
||||
cell.setAttribute('data-acount', '0');
|
||||
if ( hnDetails === undefined ) {
|
||||
updateFirewallCellCount(cells);
|
||||
continue;
|
||||
}
|
||||
if ( hnDetails.blockCount !== 0 ) {
|
||||
cell.setAttribute('data-bcount', Math.min(Math.ceil(Math.log(hnDetails.blockCount + 1) / Math.LN10), 3));
|
||||
const { allowed, blocked } = hnDetails.counts;
|
||||
updateFirewallCellCount([ cells[0] ], allowed.any, blocked.any);
|
||||
const { totals } = hnDetails;
|
||||
if ( totals !== undefined ) {
|
||||
updateFirewallCellCount([ cells[1] ], totals.allowed.any, totals.blocked.any);
|
||||
}
|
||||
if ( hnDetails.domain === pageDomain ) {
|
||||
a1pScript += allowed.script; b1pScript += blocked.script;
|
||||
} else {
|
||||
cell.setAttribute('data-bcount', '0');
|
||||
a3pScript += allowed.script; b3pScript += blocked.script;
|
||||
a3pFrame += allowed.frame; b3pFrame += blocked.frame;
|
||||
}
|
||||
}
|
||||
|
||||
if ( hnDetails.domain !== des ) {
|
||||
return;
|
||||
if ( doCounts ) {
|
||||
const fromType = type =>
|
||||
document.querySelectorAll(
|
||||
`#firewall > [data-des="*"][data-type="${type}"] > [data-src="."]`
|
||||
);
|
||||
updateFirewallCellCount(fromType('1p-script'), a1pScript, b1pScript);
|
||||
updateFirewallCellCount(fromType('3p-script'), a3pScript, b3pScript);
|
||||
rowContainer.classList.toggle('has3pScript', a3pScript !== 0 || b3pScript !== 0);
|
||||
updateFirewallCellCount(fromType('3p-frame'), a3pFrame, b3pFrame);
|
||||
rowContainer.classList.toggle('has3pFrame', a3pFrame !== 0 || b3pFrame !== 0);
|
||||
}
|
||||
|
||||
cell = cells[1];
|
||||
if ( hnDetails.totalAllowCount !== 0 ) {
|
||||
cell.setAttribute('data-acount', Math.min(Math.ceil(Math.log(hnDetails.totalAllowCount + 1) / Math.LN10), 3));
|
||||
} else {
|
||||
cell.setAttribute('data-acount', '0');
|
||||
}
|
||||
if ( hnDetails.totalBlockCount !== 0 ) {
|
||||
cell.setAttribute('data-bcount', Math.min(Math.ceil(Math.log(hnDetails.totalBlockCount + 1) / Math.LN10), 3));
|
||||
} else {
|
||||
cell.setAttribute('data-bcount', '0');
|
||||
}
|
||||
document.body.classList.toggle('needSave', popupData.matrixIsDirty === true);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const updateAllFirewallCells = function() {
|
||||
const rules = popupData.firewallRules;
|
||||
for ( const key in rules ) {
|
||||
if ( rules.hasOwnProperty(key) === false ) { continue; }
|
||||
updateFirewallCell(
|
||||
key.charAt(0),
|
||||
key.slice(2, key.indexOf(' ', 2)),
|
||||
key.slice(key.lastIndexOf(' ') + 1),
|
||||
rules[key]
|
||||
);
|
||||
// Compute statistics useful only to firewall entries -- we need to call
|
||||
// this only when overview pane needs to be rendered.
|
||||
|
||||
const expandHostnameStats = ( ) => {
|
||||
let dnDetails;
|
||||
for ( const des of allHostnameRows ) {
|
||||
const hnDetails = popupData.hostnameDict[des];
|
||||
const { domain, counts } = hnDetails;
|
||||
const isDomain = des === domain;
|
||||
const { allowed: hnAllowed, blocked: hnBlocked } = counts;
|
||||
if ( isDomain ) {
|
||||
dnDetails = hnDetails;
|
||||
dnDetails.totals = JSON.parse(JSON.stringify(dnDetails.counts));
|
||||
} else {
|
||||
const { allowed: dnAllowed, blocked: dnBlocked } = dnDetails.totals;
|
||||
dnAllowed.any += hnAllowed.any;
|
||||
dnBlocked.any += hnBlocked.any;
|
||||
}
|
||||
hnDetails.hasScript = hnAllowed.script !== 0 || hnBlocked.script !== 0;
|
||||
dnDetails.hasScript = dnDetails.hasScript || hnDetails.hasScript;
|
||||
hnDetails.hasFrame = hnAllowed.frame !== 0 || hnBlocked.frame !== 0;
|
||||
dnDetails.hasFrame = dnDetails.hasFrame || hnDetails.hasFrame;
|
||||
}
|
||||
document.body.classList.toggle('needSave', popupData.matrixIsDirty === true);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
@ -315,33 +354,47 @@ const buildAllFirewallRows = function() {
|
||||
}
|
||||
dfHotspots.remove();
|
||||
|
||||
// This must be called before we create the rows.
|
||||
expandHostnameStats();
|
||||
|
||||
// Update incrementally: reuse existing rows if possible.
|
||||
const rowContainer = document.getElementById('firewall');
|
||||
const toAppend = document.createDocumentFragment();
|
||||
const rowTemplate = document.querySelector('#templates > div:nth-of-type(1)');
|
||||
let row = rowContainer.querySelector('div:nth-of-type(7) + div');
|
||||
const rowTemplate = document.querySelector(
|
||||
'#templates > div[data-des=""][data-type="*"]'
|
||||
);
|
||||
const { cnameMap, hostnameDict, pageDomain, pageHostname } = popupData;
|
||||
|
||||
let row = rowContainer.querySelector(
|
||||
'div[data-des="*"][data-type="3p-frame"] + div'
|
||||
);
|
||||
|
||||
for ( const des of allHostnameRows ) {
|
||||
if ( row === null ) {
|
||||
row = rowTemplate.cloneNode(true);
|
||||
toAppend.appendChild(row);
|
||||
}
|
||||
|
||||
row.setAttribute('data-des', des);
|
||||
|
||||
const hnDetails = popupData.hostnameDict[des] || {};
|
||||
const hnDetails = hostnameDict[des] || {};
|
||||
const isDomain = des === hnDetails.domain;
|
||||
const prettyDomainName = punycode.toUnicode(des);
|
||||
const isPunycoded = prettyDomainName !== des;
|
||||
|
||||
if ( isDomain && row.childElementCount < 4 ) {
|
||||
row.append(row.children[2].cloneNode(true));
|
||||
} else if ( isDomain === false && row.childElementCount === 4 ) {
|
||||
row.children[3].remove();
|
||||
}
|
||||
|
||||
const span = row.querySelector('span:first-of-type');
|
||||
span.querySelector('span').textContent = prettyDomainName;
|
||||
|
||||
const classList = row.classList;
|
||||
|
||||
let desExtra = '';
|
||||
if ( classList.toggle('isCname', popupData.cnameMap.has(des)) ) {
|
||||
desExtra = punycode.toUnicode(popupData.cnameMap.get(des));
|
||||
if ( classList.toggle('isCname', cnameMap.has(des)) ) {
|
||||
desExtra = punycode.toUnicode(cnameMap.get(des));
|
||||
} else if (
|
||||
isDomain && isPunycoded &&
|
||||
reCyrillicAmbiguous.test(prettyDomainName) &&
|
||||
@ -351,13 +404,18 @@ const buildAllFirewallRows = function() {
|
||||
}
|
||||
span.querySelector('sub').textContent = desExtra;
|
||||
|
||||
classList.toggle('isRootContext', des === popupData.pageHostname);
|
||||
classList.toggle('isRootContext', des === pageHostname);
|
||||
classList.toggle('is3p', hnDetails.domain !== pageDomain);
|
||||
classList.toggle('isDomain', isDomain);
|
||||
classList.toggle('isSubDomain', !isDomain);
|
||||
classList.toggle('allowed', hnDetails.allowCount !== 0);
|
||||
classList.toggle('blocked', hnDetails.blockCount !== 0);
|
||||
classList.toggle('totalAllowed', hnDetails.totalAllowCount !== 0);
|
||||
classList.toggle('totalBlocked', hnDetails.totalBlockCount !== 0);
|
||||
const { counts } = hnDetails;
|
||||
classList.toggle('allowed', gtz(counts.allowed.any));
|
||||
classList.toggle('blocked', gtz(counts.blocked.any));
|
||||
const { totals } = hnDetails;
|
||||
classList.toggle('totalAllowed', gtz(totals && totals.allowed.any));
|
||||
classList.toggle('totalBlocked', gtz(totals && totals.blocked.any));
|
||||
classList.toggle('hasScript', hnDetails.hasScript === true);
|
||||
classList.toggle('hasFrame', hnDetails.hasFrame === true);
|
||||
classList.toggle('expandException', expandExceptions.has(hnDetails.domain));
|
||||
|
||||
row = row.nextElementSibling;
|
||||
@ -366,14 +424,14 @@ const buildAllFirewallRows = function() {
|
||||
// Remove unused trailing rows
|
||||
if ( row !== null ) {
|
||||
while ( row.nextElementSibling !== null ) {
|
||||
rowContainer.removeChild(row.nextElementSibling);
|
||||
row.nextElementSibling.remove();
|
||||
}
|
||||
rowContainer.removeChild(row);
|
||||
row.remove();
|
||||
}
|
||||
|
||||
// Add new rows all at once
|
||||
if ( toAppend.childElementCount !== 0 ) {
|
||||
rowContainer.appendChild(toAppend);
|
||||
rowContainer.append(toAppend);
|
||||
}
|
||||
|
||||
if ( dfPaneBuilt !== true && popupData.advancedUserEnabled ) {
|
||||
@ -389,28 +447,48 @@ const buildAllFirewallRows = function() {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const hostnameCompare = function(a, b) {
|
||||
let ha = a;
|
||||
if ( !reIP.test(ha) ) {
|
||||
ha = hostnameToSortableTokenMap.get(ha) || ' ';
|
||||
}
|
||||
let hb = b;
|
||||
if ( !reIP.test(hb) ) {
|
||||
hb = hostnameToSortableTokenMap.get(hb) || ' ';
|
||||
}
|
||||
const ca = ha.charCodeAt(0);
|
||||
const cb = hb.charCodeAt(0);
|
||||
return ca !== cb ? ca - cb : ha.localeCompare(hb);
|
||||
};
|
||||
|
||||
const reIP = /(\d|\])$/;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const renderPrivacyExposure = function() {
|
||||
allDomains = {};
|
||||
allDomainCount = touchedDomainCount = 0;
|
||||
const allDomains = {};
|
||||
let allDomainCount = 0;
|
||||
let touchedDomainCount = 0;
|
||||
|
||||
allHostnameRows = [];
|
||||
|
||||
// Sort hostnames. First-party hostnames must always appear at the top
|
||||
// of the list.
|
||||
const desHostnameDone = {};
|
||||
const keys = Object.keys(popupData.firewallRules)
|
||||
.sort(rulekeyCompare);
|
||||
for ( const key of keys ) {
|
||||
const des = key.slice(2, key.indexOf(' ', 2));
|
||||
const keys = Object.keys(popupData.hostnameDict)
|
||||
.sort(hostnameCompare);
|
||||
for ( const des of keys ) {
|
||||
// Specific-type rules -- these are built-in
|
||||
if ( des === '*' || desHostnameDone.hasOwnProperty(des) ) { continue; }
|
||||
const hnDetails = popupData.hostnameDict[des] || {};
|
||||
if ( allDomains.hasOwnProperty(hnDetails.domain) === false ) {
|
||||
allDomains[hnDetails.domain] = false;
|
||||
const hnDetails = popupData.hostnameDict[des];
|
||||
const { domain, counts } = hnDetails;
|
||||
if ( allDomains.hasOwnProperty(domain) === false ) {
|
||||
allDomains[domain] = false;
|
||||
allDomainCount += 1;
|
||||
}
|
||||
if ( hnDetails.allowCount !== 0 ) {
|
||||
if ( allDomains[hnDetails.domain] === false ) {
|
||||
allDomains[hnDetails.domain] = true;
|
||||
if ( gtz(counts.allowed.any) ) {
|
||||
if ( allDomains[domain] === false ) {
|
||||
allDomains[domain] = true;
|
||||
touchedDomainCount += 1;
|
||||
}
|
||||
}
|
||||
@ -421,7 +499,9 @@ const renderPrivacyExposure = function() {
|
||||
const summary = domainsHitStr
|
||||
.replace('{{count}}', touchedDomainCount.toLocaleString())
|
||||
.replace('{{total}}', allDomainCount.toLocaleString());
|
||||
uDom.nodeFromSelector('[data-i18n^="popupDomainsConnected"] + span').textContent = summary;
|
||||
uDom.nodeFromSelector(
|
||||
'[data-i18n^="popupDomainsConnected"] + span'
|
||||
).textContent = summary;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
@ -481,8 +561,15 @@ const renderPopup = function() {
|
||||
uDom.nodeFromId('gotoPick').classList.toggle('enabled', canElementPicker);
|
||||
uDom.nodeFromId('gotoZap').classList.toggle('enabled', canElementPicker);
|
||||
|
||||
let blocked = popupData.pageBlockedRequestCount;
|
||||
let total = popupData.pageAllowedRequestCount + blocked;
|
||||
let blocked, total;
|
||||
if ( popupData.pageCounts !== undefined ) {
|
||||
const counts = popupData.pageCounts;
|
||||
blocked = counts.blocked.any;
|
||||
total = blocked + counts.allowed.any;
|
||||
} else {
|
||||
blocked = 0;
|
||||
total = 0;
|
||||
}
|
||||
let text;
|
||||
if ( total === 0 ) {
|
||||
text = formatNumber(0);
|
||||
@ -885,7 +972,7 @@ const setFirewallRule = async function(src, des, type, action, persist) {
|
||||
}
|
||||
|
||||
cachePopupData(response);
|
||||
updateAllFirewallCells();
|
||||
updateAllFirewallCells(true, false);
|
||||
hashFromPopupData();
|
||||
};
|
||||
|
||||
@ -1075,8 +1162,7 @@ const revertFirewallRules = async function() {
|
||||
tabId: popupData.tabId,
|
||||
});
|
||||
cachePopupData(response);
|
||||
updateAllFirewallCells();
|
||||
updateHnSwitches();
|
||||
updateAllFirewallCells(true, false);
|
||||
hashFromPopupData();
|
||||
};
|
||||
|
||||
@ -1106,8 +1192,9 @@ const toggleHostnameSwitch = async function(ev) {
|
||||
});
|
||||
|
||||
cachePopupData(response);
|
||||
updateAllFirewallCells();
|
||||
hashFromPopupData();
|
||||
|
||||
document.body.classList.toggle('needSave', popupData.matrixIsDirty === true);
|
||||
};
|
||||
|
||||
/*******************************************************************************
|
||||
@ -1276,6 +1363,8 @@ const getPopupData = async function(tabId) {
|
||||
});
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
uDom('#switch').on('click', toggleNetFilteringSwitch);
|
||||
uDom('#gotoZap').on('click', gotoZap);
|
||||
uDom('#gotoPick').on('click', gotoPick);
|
||||
@ -1284,6 +1373,36 @@ uDom('#saveRules').on('click', saveFirewallRules);
|
||||
uDom('#revertRules').on('click', ( ) => { revertFirewallRules(); });
|
||||
uDom('a[href]').on('click', gotoURL);
|
||||
|
||||
// Toggle emphasis of rows with[out] 3rd-party scripts/frames
|
||||
{
|
||||
const nextStep = (target, steps) => {
|
||||
const firewall = document.getElementById('firewall');
|
||||
const cl = firewall.classList;
|
||||
if ( cl.contains(steps[0]) ) {
|
||||
cl.remove(steps[0]);
|
||||
if ( firewall.querySelector(target) !== null ) {
|
||||
cl.add(steps[1]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ( cl.contains(steps[1]) ) {
|
||||
cl.remove(steps[1]);
|
||||
return;
|
||||
}
|
||||
cl.add(steps[0]);
|
||||
};
|
||||
document.querySelector('#firewall > [data-type="3p-script"] .filter')
|
||||
.addEventListener('click', ( ) => {
|
||||
nextStep('.is3p.hasScript', [ 'show3pScript', 'hide3pScript' ]);
|
||||
});
|
||||
|
||||
// Toggle visibility of rows with[out] 3rd-party frames
|
||||
document.querySelector('#firewall > [data-type="3p-frame"] .filter')
|
||||
.addEventListener('click', ( ) => {
|
||||
nextStep('.is3p.hasFrame', [ 'show3pFrame', 'hide3pFrame' ]);
|
||||
});
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// <<<<< end of local scope
|
||||
|
257
src/js/popup.js
257
src/js/popup.js
@ -75,10 +75,7 @@ const domainsHitStr = vAPI.i18n('popupHitDomainCount');
|
||||
let popupData = {};
|
||||
let dfPaneBuilt = false;
|
||||
let dfHotspots = null;
|
||||
let allDomains = {};
|
||||
let allDomainCount = 0;
|
||||
let allHostnameRows = [];
|
||||
let touchedDomainCount = 0;
|
||||
let cachedPopupHash = '';
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/2550
|
||||
@ -181,6 +178,10 @@ const hashFromPopupData = function(reset) {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const gtz = n => typeof n === 'number' && n > 0;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const formatNumber = function(count) {
|
||||
return typeof count === 'number' ? count.toLocaleString() : '';
|
||||
};
|
||||
@ -188,11 +189,11 @@ const formatNumber = function(count) {
|
||||
/******************************************************************************/
|
||||
|
||||
const rulekeyCompare = function(a, b) {
|
||||
let ha = a.slice(2, a.indexOf(' ', 2));
|
||||
let ha = a;
|
||||
if ( !reIP.test(ha) ) {
|
||||
ha = hostnameToSortableTokenMap.get(ha) || ' ';
|
||||
}
|
||||
let hb = b.slice(2, b.indexOf(' ', 2));
|
||||
let hb = b;
|
||||
if ( !reIP.test(hb) ) {
|
||||
hb = hostnameToSortableTokenMap.get(hb) || ' ';
|
||||
}
|
||||
@ -206,69 +207,20 @@ const rulekeyCompare = function(a, b) {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const updateFirewallCell = function(scope, des, type, rule) {
|
||||
const row = document.querySelector(
|
||||
`#firewallContainer div[data-des="${des}"][data-type="${type}"]`
|
||||
const updateFirewallCellCount = function(cell, allowed, blocked) {
|
||||
if ( gtz(allowed) ) {
|
||||
cell.setAttribute(
|
||||
'data-acount',
|
||||
Math.min(Math.ceil(Math.log(allowed + 1) / Math.LN10), 3)
|
||||
);
|
||||
if ( row === null ) { return; }
|
||||
|
||||
const cells = row.querySelectorAll(`:scope > span[data-src="${scope}"]`);
|
||||
if ( cells.length === 0 ) { return; }
|
||||
|
||||
if ( rule !== null ) {
|
||||
cells.forEach(el => { el.setAttribute('class', rule.action + 'Rule'); });
|
||||
} else {
|
||||
cells.forEach(el => { el.removeAttribute('class'); });
|
||||
}
|
||||
|
||||
// Use dark shade visual cue if the rule is specific to the cell.
|
||||
if (
|
||||
(rule !== null) &&
|
||||
(rule.des !== '*' || rule.type === type) &&
|
||||
(rule.des === des) &&
|
||||
(rule.src === scopeToSrcHostnameMap[scope])
|
||||
|
||||
) {
|
||||
cells.forEach(el => { el.classList.add('ownRule'); });
|
||||
}
|
||||
|
||||
if ( scope !== '.' || des === '*' ) { return; }
|
||||
|
||||
// Remember this may be a cell from a reused row, we need to clear text
|
||||
// content if we can't compute request counts.
|
||||
if ( popupData.hostnameDict.hasOwnProperty(des) === false ) {
|
||||
cells.forEach(el => {
|
||||
el.removeAttribute('data-acount');
|
||||
el.removeAttribute('data-bcount');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const hnDetails = popupData.hostnameDict[des];
|
||||
let cell = cells[0];
|
||||
if ( hnDetails.allowCount !== 0 ) {
|
||||
cell.setAttribute('data-acount', Math.min(Math.ceil(Math.log(hnDetails.allowCount + 1) / Math.LN10), 3));
|
||||
} else {
|
||||
cell.setAttribute('data-acount', '0');
|
||||
}
|
||||
if ( hnDetails.blockCount !== 0 ) {
|
||||
cell.setAttribute('data-bcount', Math.min(Math.ceil(Math.log(hnDetails.blockCount + 1) / Math.LN10), 3));
|
||||
} else {
|
||||
cell.setAttribute('data-bcount', '0');
|
||||
}
|
||||
|
||||
if ( hnDetails.domain !== des ) {
|
||||
return;
|
||||
}
|
||||
|
||||
cell = cells[1];
|
||||
if ( hnDetails.totalAllowCount !== 0 ) {
|
||||
cell.setAttribute('data-acount', Math.min(Math.ceil(Math.log(hnDetails.totalAllowCount + 1) / Math.LN10), 3));
|
||||
} else {
|
||||
cell.setAttribute('data-acount', '0');
|
||||
}
|
||||
if ( hnDetails.totalBlockCount !== 0 ) {
|
||||
cell.setAttribute('data-bcount', Math.min(Math.ceil(Math.log(hnDetails.totalBlockCount + 1) / Math.LN10), 3));
|
||||
if ( gtz(blocked) ) {
|
||||
cell.setAttribute(
|
||||
'data-bcount',
|
||||
Math.min(Math.ceil(Math.log(blocked + 1) / Math.LN10), 3)
|
||||
);
|
||||
} else {
|
||||
cell.setAttribute('data-bcount', '0');
|
||||
}
|
||||
@ -276,16 +228,89 @@ const updateFirewallCell = function(scope, des, type, rule) {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const updateAllFirewallCells = function() {
|
||||
const rules = popupData.firewallRules;
|
||||
for ( const key in rules ) {
|
||||
if ( rules.hasOwnProperty(key) === false ) { continue; }
|
||||
updateFirewallCell(
|
||||
key.charAt(0),
|
||||
key.slice(2, key.indexOf(' ', 2)),
|
||||
key.slice(key.lastIndexOf(' ') + 1),
|
||||
rules[key]
|
||||
const updateFirewallCellRule = function(cell, scope, des, type, ruleParts) {
|
||||
if ( cell instanceof HTMLElement === false ) { return; }
|
||||
|
||||
if ( ruleParts === undefined ) {
|
||||
cell.removeAttribute('class');
|
||||
return;
|
||||
}
|
||||
|
||||
const action = updateFirewallCellRule.actionNames[ruleParts[3]];
|
||||
cell.setAttribute('class', `${action}Rule`);
|
||||
|
||||
// Use dark shade visual cue if the rule is specific to the cell.
|
||||
if (
|
||||
(ruleParts[1] !== '*' || ruleParts[2] === type) &&
|
||||
(ruleParts[1] === des) &&
|
||||
(ruleParts[0] === scopeToSrcHostnameMap[scope])
|
||||
|
||||
) {
|
||||
cell.classList.add('ownRule');
|
||||
}
|
||||
};
|
||||
|
||||
updateFirewallCellRule.actionNames = { '1': 'block', '2': 'allow', '3': 'noop' };
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const updateFirewallCell = function(row, scope, des, type, doCounts) {
|
||||
if ( row instanceof HTMLElement === false ) { return; }
|
||||
|
||||
const cells = row.querySelectorAll(`:scope > span[data-src="${scope}"]`);
|
||||
if ( cells.length === 0 ) { return; }
|
||||
|
||||
const rule = popupData.firewallRules[`${scope} ${des} ${type}`];
|
||||
const ruleParts = rule !== undefined ? rule.split(' ') : undefined;
|
||||
for ( const cell of cells ) {
|
||||
updateFirewallCellRule(cell, scope, des, type, ruleParts);
|
||||
}
|
||||
|
||||
if ( scope !== '.' || des === '*' ) { return; }
|
||||
if ( doCounts !== true ) { return; }
|
||||
|
||||
// Remember this may be a cell from a reused row, we need to clear text
|
||||
// content if we can't compute request counts.
|
||||
const hnDetails = popupData.hostnameDict[des];
|
||||
if ( hnDetails === undefined ) {
|
||||
cells.forEach(el => {
|
||||
updateFirewallCellCount(el);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
updateFirewallCellCount(
|
||||
cells[0],
|
||||
hnDetails.counts.allowed.any,
|
||||
hnDetails.counts.blocked.any
|
||||
);
|
||||
|
||||
if ( hnDetails.domain !== des || hnDetails.totals === undefined ) {
|
||||
updateFirewallCellCount(
|
||||
cells[1],
|
||||
hnDetails.counts.allowed.any,
|
||||
hnDetails.counts.blocked.any
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
updateFirewallCellCount(
|
||||
cells[1],
|
||||
hnDetails.totals.allowed.any,
|
||||
hnDetails.totals.blocked.any
|
||||
);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const updateAllFirewallCells = function(doCounts = true) {
|
||||
const rows = document.querySelectorAll('#firewallContainer > [data-des][data-type]');
|
||||
|
||||
for ( const row of rows ) {
|
||||
const des = row.getAttribute('data-des');
|
||||
const type = row.getAttribute('data-type');
|
||||
updateFirewallCell(row, '/', des, type, doCounts);
|
||||
updateFirewallCell(row, '.', des, type, doCounts);
|
||||
}
|
||||
|
||||
const dirty = popupData.matrixIsDirty === true;
|
||||
@ -297,6 +322,33 @@ const updateAllFirewallCells = function() {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Compute statistics useful only to firewall entries -- we need to call
|
||||
// this only when overview pane needs to be rendered.
|
||||
|
||||
const expandHostnameStats = ( ) => {
|
||||
let dnDetails;
|
||||
for ( const des of allHostnameRows ) {
|
||||
const hnDetails = popupData.hostnameDict[des];
|
||||
const { domain, counts } = hnDetails;
|
||||
const isDomain = des === domain;
|
||||
const { allowed: hnAllowed, blocked: hnBlocked } = counts;
|
||||
if ( isDomain ) {
|
||||
dnDetails = hnDetails;
|
||||
dnDetails.totals = JSON.parse(JSON.stringify(dnDetails.counts));
|
||||
} else {
|
||||
const { allowed: dnAllowed, blocked: dnBlocked } = dnDetails.totals;
|
||||
dnAllowed.any += hnAllowed.any;
|
||||
dnBlocked.any += hnBlocked.any;
|
||||
}
|
||||
hnDetails.hasScript = hnAllowed.script !== 0 || hnBlocked.script !== 0;
|
||||
dnDetails.hasScript = dnDetails.hasScript || hnDetails.hasScript;
|
||||
hnDetails.hasFrame = hnAllowed.frame !== 0 || hnBlocked.frame !== 0;
|
||||
dnDetails.hasFrame = dnDetails.hasFrame || hnDetails.hasFrame;
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const buildAllFirewallRows = function() {
|
||||
// Do this before removing the rows
|
||||
if ( dfHotspots === null ) {
|
||||
@ -305,6 +357,9 @@ const buildAllFirewallRows = function() {
|
||||
}
|
||||
dfHotspots.remove();
|
||||
|
||||
// This must be called before we create the rows.
|
||||
expandHostnameStats();
|
||||
|
||||
// Update incrementally: reuse existing rows if possible.
|
||||
const rowContainer = document.getElementById('firewallContainer');
|
||||
const toAppend = document.createDocumentFragment();
|
||||
@ -324,6 +379,8 @@ const buildAllFirewallRows = function() {
|
||||
const prettyDomainName = punycode.toUnicode(des);
|
||||
const isPunycoded = prettyDomainName !== des;
|
||||
|
||||
const { allowed: hnAllowed, blocked: hnBlocked } = hnDetails.counts;
|
||||
|
||||
const span = row.querySelector('span:first-of-type');
|
||||
span.querySelector('span').textContent = prettyDomainName;
|
||||
|
||||
@ -344,12 +401,14 @@ const buildAllFirewallRows = function() {
|
||||
classList.toggle('isRootContext', des === popupData.pageHostname);
|
||||
classList.toggle('isDomain', isDomain);
|
||||
classList.toggle('isSubDomain', !isDomain);
|
||||
classList.toggle('allowed', hnDetails.allowCount !== 0);
|
||||
classList.toggle('blocked', hnDetails.blockCount !== 0);
|
||||
classList.toggle('totalAllowed', hnDetails.totalAllowCount !== 0);
|
||||
classList.toggle('totalBlocked', hnDetails.totalBlockCount !== 0);
|
||||
classList.toggle('allowed', gtz(hnAllowed.any));
|
||||
classList.toggle('blocked', gtz(hnBlocked.any));
|
||||
classList.toggle('expandException', expandExceptions.has(hnDetails.domain));
|
||||
|
||||
const { totals } = hnDetails;
|
||||
classList.toggle('totalAllowed', gtz(totals && totals.allowed.any));
|
||||
classList.toggle('totalBlocked', gtz(totals && totals.blocked.any));
|
||||
|
||||
row = row.nextElementSibling;
|
||||
}
|
||||
|
||||
@ -380,27 +439,29 @@ const buildAllFirewallRows = function() {
|
||||
/******************************************************************************/
|
||||
|
||||
const renderPrivacyExposure = function() {
|
||||
allDomains = {};
|
||||
allDomainCount = touchedDomainCount = 0;
|
||||
const allDomains = {};
|
||||
let allDomainCount = 0;
|
||||
let touchedDomainCount = 0;
|
||||
|
||||
allHostnameRows = [];
|
||||
|
||||
// Sort hostnames. First-party hostnames must always appear at the top
|
||||
// of the list.
|
||||
const desHostnameDone = {};
|
||||
const keys = Object.keys(popupData.firewallRules)
|
||||
const keys = Object.keys(popupData.hostnameDict)
|
||||
.sort(rulekeyCompare);
|
||||
for ( const key of keys ) {
|
||||
const des = key.slice(2, key.indexOf(' ', 2));
|
||||
for ( const des of keys ) {
|
||||
// Specific-type rules -- these are built-in
|
||||
if ( des === '*' || desHostnameDone.hasOwnProperty(des) ) { continue; }
|
||||
const hnDetails = popupData.hostnameDict[des] || {};
|
||||
if ( allDomains.hasOwnProperty(hnDetails.domain) === false ) {
|
||||
allDomains[hnDetails.domain] = false;
|
||||
const hnDetails = popupData.hostnameDict[des];
|
||||
const { domain, counts } = hnDetails;
|
||||
if ( allDomains.hasOwnProperty(domain) === false ) {
|
||||
allDomains[domain] = false;
|
||||
allDomainCount += 1;
|
||||
}
|
||||
if ( hnDetails.allowCount !== 0 ) {
|
||||
if ( allDomains[hnDetails.domain] === false ) {
|
||||
allDomains[hnDetails.domain] = true;
|
||||
if ( gtz(counts.allowed.any) ) {
|
||||
if ( allDomains[domain] === false ) {
|
||||
allDomains[domain] = true;
|
||||
touchedDomainCount += 1;
|
||||
}
|
||||
}
|
||||
@ -462,9 +523,16 @@ const renderPopup = function() {
|
||||
uDom.nodeFromId('gotoPick').classList.toggle('enabled', canElementPicker);
|
||||
uDom.nodeFromId('gotoZap').classList.toggle('enabled', canElementPicker);
|
||||
|
||||
let blocked = popupData.pageBlockedRequestCount,
|
||||
total = popupData.pageAllowedRequestCount + blocked,
|
||||
text;
|
||||
let blocked, total;
|
||||
if ( popupData.pageCounts !== undefined ) {
|
||||
const counts = popupData.pageCounts;
|
||||
blocked = counts.blocked.any;
|
||||
total = blocked + counts.allowed.any;
|
||||
} else {
|
||||
blocked = 0;
|
||||
total = 0;
|
||||
}
|
||||
let text;
|
||||
if ( total === 0 ) {
|
||||
text = formatNumber(0);
|
||||
} else {
|
||||
@ -855,7 +923,7 @@ const setFirewallRule = async function(src, des, type, action, persist) {
|
||||
}
|
||||
|
||||
cachePopupData(response);
|
||||
updateAllFirewallCells();
|
||||
updateAllFirewallCells(false);
|
||||
hashFromPopupData();
|
||||
};
|
||||
|
||||
@ -1046,7 +1114,7 @@ const revertFirewallRules = async function() {
|
||||
tabId: popupData.tabId,
|
||||
});
|
||||
cachePopupData(response);
|
||||
updateAllFirewallCells();
|
||||
updateAllFirewallCells(false);
|
||||
updateHnSwitches();
|
||||
hashFromPopupData();
|
||||
};
|
||||
@ -1077,8 +1145,9 @@ const toggleHostnameSwitch = async function(ev) {
|
||||
});
|
||||
|
||||
cachePopupData(response);
|
||||
updateAllFirewallCells();
|
||||
hashFromPopupData();
|
||||
|
||||
document.body.classList.toggle('needSave', popupData.matrixIsDirty === true);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -360,7 +360,7 @@
|
||||
// filtering pane.
|
||||
const pageStore = µb.pageStoreFromTabId(openerTabId);
|
||||
if ( pageStore ) {
|
||||
pageStore.journalAddRequest(fctxt.getHostname(), result);
|
||||
pageStore.journalAddRequest(fctxt, result);
|
||||
pageStore.popupBlockedCount += 1;
|
||||
}
|
||||
|
||||
@ -1038,14 +1038,15 @@ vAPI.tabs = new vAPI.Tabs();
|
||||
let badge = '';
|
||||
let color = '#666';
|
||||
|
||||
let pageStore = µb.pageStoreFromTabId(tabId);
|
||||
const pageStore = µb.pageStoreFromTabId(tabId);
|
||||
if ( pageStore !== null ) {
|
||||
state = pageStore.getNetFilteringSwitch() ? 1 : 0;
|
||||
if ( state === 1 ) {
|
||||
if ( (parts & 0b0010) !== 0 && pageStore.perLoadBlockedRequestCount ) {
|
||||
badge = µb.formatCount(
|
||||
pageStore.perLoadBlockedRequestCount
|
||||
);
|
||||
if ( (parts & 0b0010) !== 0 ) {
|
||||
const blockCount = pageStore.counts.blocked.any;
|
||||
if ( blockCount !== 0 ) {
|
||||
badge = µb.formatCount(blockCount);
|
||||
}
|
||||
}
|
||||
if ( (parts & 0b0100) !== 0 ) {
|
||||
color = computeBadgeColor(
|
||||
@ -1071,7 +1072,7 @@ vAPI.tabs = new vAPI.Tabs();
|
||||
return function(tabId, newParts = 0b0111) {
|
||||
if ( typeof tabId !== 'number' ) { return; }
|
||||
if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; }
|
||||
let currentParts = tabIdToDetails.get(tabId);
|
||||
const currentParts = tabIdToDetails.get(tabId);
|
||||
if ( currentParts === newParts ) { return; }
|
||||
if ( currentParts === undefined ) {
|
||||
self.requestIdleCallback(
|
||||
|
@ -88,7 +88,7 @@ const onBeforeRequest = function(details) {
|
||||
|
||||
const result = pageStore.filterRequest(fctxt);
|
||||
|
||||
pageStore.journalAddRequest(fctxt.getHostname(), result);
|
||||
pageStore.journalAddRequest(fctxt, result);
|
||||
|
||||
if ( µb.logger.enabled ) {
|
||||
fctxt.setRealm('network').toLogger();
|
||||
@ -208,7 +208,7 @@ const onBeforeRootFrameRequest = function(fctxt) {
|
||||
const pageStore = µb.bindTabToPageStore(fctxt.tabId, 'beforeRequest');
|
||||
if ( pageStore !== null ) {
|
||||
pageStore.journalAddRootFrame('uncommitted', requestURL);
|
||||
pageStore.journalAddRequest(requestHostname, result);
|
||||
pageStore.journalAddRequest(fctxt, result);
|
||||
}
|
||||
|
||||
if ( loggerEnabled ) {
|
||||
@ -400,7 +400,7 @@ const onBeforeBehindTheSceneRequest = function(fctxt) {
|
||||
gcTimer = vAPI.setTimeout(gc, 30011);
|
||||
}
|
||||
for ( const pageStore of pageStores ) {
|
||||
pageStore.journalAddRequest(fctxt.getHostname(), result);
|
||||
pageStore.journalAddRequest(fctxt, result);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -451,7 +451,7 @@ const onHeadersReceived = function(details) {
|
||||
fctxt.setRealm('network').toLogger();
|
||||
}
|
||||
if ( result === 1 ) {
|
||||
pageStore.journalAddRequest(fctxt.getHostname(), 1);
|
||||
pageStore.journalAddRequest(fctxt, 1);
|
||||
return { cancel: true };
|
||||
}
|
||||
}
|
||||
|
@ -82,13 +82,13 @@
|
||||
<div data-des="*" data-type="3p"><span data-i18n="popup3pAnyRulePrompt"></span><span data-src="/"> </span><span data-src="."> </span></div>
|
||||
<div data-des="*" data-type="inline-script"><span data-i18n="popupInlineScriptRulePrompt"></span><span data-src="/"> </span><span data-src="."> </span></div>
|
||||
<div data-des="*" data-type="1p-script"><span data-i18n="popup1pScriptRulePrompt"></span><span data-src="/"> </span><span data-src="."> </span></div>
|
||||
<div data-des="*" data-type="3p-script"><span data-i18n="popup3pScriptRulePrompt"></span><span data-src="/"> </span><span data-src="."> </span></div>
|
||||
<div data-des="*" data-type="3p-frame"><span data-i18n="popup3pFrameRulePrompt"></span><span data-src="/"> </span><span data-src="."> </span></div>
|
||||
<div data-des="*" data-type="3p-script"><span><span class="filter" title="↑: Emphasize rows which have 3rd-party scripts
↓: De-emphasize rows which have 3rd-party scripts"></span><span data-i18n="popup3pScriptRulePrompt"></span></span><span data-src="/"> </span><span data-src="."> </span></div>
|
||||
<div data-des="*" data-type="3p-frame"><span><span class="filter" title="↑: Emphasize rows which have 3rd-party frames
↓: De-emphasize rows which have 3rd-party frames"></span><span data-i18n="popup3pFrameRulePrompt"></span></span><span data-src="/"> </span><span data-src="."> </span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="templates" style="display: none">
|
||||
<div data-des="" data-type="*"><span><span></span><sub></sub></span><span data-src="/"></span><span data-src="."></span><span data-src="."></span></div>
|
||||
<div data-des="" data-type="*"><span><span></span><sub></sub></span><span data-src="/"></span><span data-src="."></span></div>
|
||||
<div id="actionSelector"><span id="dynaAllow"></span><span id="dynaNoop"></span><span id="dynaBlock"></span><span id="dynaCounts"></span></div>
|
||||
<div id="hotspotTip"></div>
|
||||
<div id="tooltip"></div>
|
||||
|
Loading…
Reference in New Issue
Block a user