mirror of https://github.com/gorhill/uBlock.git
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);
|
min-width: var(--popup-firewall-min-width);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
text-align: right;
|
|
||||||
}
|
}
|
||||||
:root.desktop body.vMin #firewall {
|
:root.desktop body.vMin #firewall {
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
|
@ -271,7 +270,6 @@ body[data-more=""] #lessButton {
|
||||||
border: 0;
|
border: 0;
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -305,10 +303,45 @@ body[data-more=""] #lessButton {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
padding-right: 2px;
|
padding-right: 2px;
|
||||||
|
text-align: right;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
width: calc(100% - var(--popup-rule-cell-width));
|
width: calc(100% - var(--popup-rule-cell-width));
|
||||||
word-break: break-word;
|
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 {
|
#firewall > div.isCname > span:first-of-type {
|
||||||
color: var(--fg-popup-cell-cname);
|
color: var(--fg-popup-cell-cname);
|
||||||
}
|
}
|
||||||
|
@ -431,33 +464,33 @@ body.advancedUser #firewall > div > span:first-of-type ~ span {
|
||||||
width: 7px;
|
width: 7px;
|
||||||
}
|
}
|
||||||
#firewall > div.isRootContext > span:first-of-type::before {
|
#firewall > div.isRootContext > span:first-of-type::before {
|
||||||
background-color: var(--fg-0-50);
|
background: var(--fg-0-50);
|
||||||
width: 14px !important;
|
width: 14px !important;
|
||||||
}
|
}
|
||||||
#firewall > div.allowed > span:first-of-type::before,
|
#firewall > div.allowed > span:first-of-type::before,
|
||||||
#firewall > div.isDomain.totalAllowed > 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.blocked > span:first-of-type::before,
|
||||||
#firewall > div.isDomain.totalBlocked > 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.allowed.blocked > span:first-of-type::before,
|
||||||
#firewall > div.isDomain.totalAllowed.totalBlocked > 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 */
|
/* Rule cells */
|
||||||
body.advancedUser #firewall > div > span.allowRule,
|
body.advancedUser #firewall > div > span.allowRule,
|
||||||
#actionSelector > #dynaAllow {
|
#actionSelector > #dynaAllow {
|
||||||
background-color: var(--bg-popup-cell-allow);
|
background: var(--bg-popup-cell-allow);
|
||||||
}
|
}
|
||||||
body.advancedUser #firewall > div > span.blockRule,
|
body.advancedUser #firewall > div > span.blockRule,
|
||||||
#actionSelector > #dynaBlock {
|
#actionSelector > #dynaBlock {
|
||||||
background-color: var(--bg-popup-cell-block);
|
background: var(--bg-popup-cell-block);
|
||||||
}
|
}
|
||||||
body.advancedUser #firewall > div > span.noopRule,
|
body.advancedUser #firewall > div > span.noopRule,
|
||||||
#actionSelector > #dynaNoop {
|
#actionSelector > #dynaNoop {
|
||||||
background-color: var(--bg-popup-cell-noop);
|
background: var(--bg-popup-cell-noop);
|
||||||
}
|
}
|
||||||
body.advancedUser #firewall > div > span.ownRule,
|
body.advancedUser #firewall > div > span.ownRule,
|
||||||
#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,
|
body.advancedUser #firewall > div > span.allowRule.ownRule,
|
||||||
:root:not(.mobile) #actionSelector > #dynaAllow:hover {
|
: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,
|
body.advancedUser #firewall > div > span.blockRule.ownRule,
|
||||||
:root:not(.mobile) #actionSelector > #dynaBlock:hover {
|
: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,
|
body.advancedUser #firewall > div > span.noopRule.ownRule,
|
||||||
:root:not(.mobile) #actionSelector > #dynaNoop:hover {
|
:root:not(.mobile) #actionSelector > #dynaNoop:hover {
|
||||||
background-color: var(--bg-popup-cell-noop-own);
|
background: var(--bg-popup-cell-noop-own);
|
||||||
}
|
}
|
||||||
|
|
||||||
#actionSelector {
|
#actionSelector {
|
||||||
|
|
|
@ -26,17 +26,12 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
µBlock.Firewall = (function() {
|
{
|
||||||
|
// >>>>> start of local scope
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var Matrix = function() {
|
const supportedDynamicTypes = {
|
||||||
this.reset();
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
var supportedDynamicTypes = {
|
|
||||||
'3p': true,
|
'3p': true,
|
||||||
'image': true,
|
'image': true,
|
||||||
'inline-script': true,
|
'inline-script': true,
|
||||||
|
@ -45,7 +40,7 @@ var supportedDynamicTypes = {
|
||||||
'3p-frame': true
|
'3p-frame': true
|
||||||
};
|
};
|
||||||
|
|
||||||
var typeBitOffsets = {
|
const typeBitOffsets = {
|
||||||
'*': 0,
|
'*': 0,
|
||||||
'inline-script': 2,
|
'inline-script': 2,
|
||||||
'1p-script': 4,
|
'1p-script': 4,
|
||||||
|
@ -55,13 +50,13 @@ var typeBitOffsets = {
|
||||||
'3p': 12
|
'3p': 12
|
||||||
};
|
};
|
||||||
|
|
||||||
var actionToNameMap = {
|
const actionToNameMap = {
|
||||||
'1': 'block',
|
'1': 'block',
|
||||||
'2': 'allow',
|
'2': 'allow',
|
||||||
'3': 'noop'
|
'3': 'noop'
|
||||||
};
|
};
|
||||||
|
|
||||||
var nameToActionMap = {
|
const nameToActionMap = {
|
||||||
'block': 1,
|
'block': 1,
|
||||||
'allow': 2,
|
'allow': 2,
|
||||||
'noop': 3
|
'noop': 3
|
||||||
|
@ -70,187 +65,10 @@ var nameToActionMap = {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// For performance purpose, as simple tests as possible
|
// For performance purpose, as simple tests as possible
|
||||||
var reBadHostname = /[^0-9a-z_.\[\]:%-]/;
|
const reBadHostname = /[^0-9a-z_.\[\]:%-]/;
|
||||||
var reNotASCII = /[^\x20-\x7F]/;
|
const reNotASCII = /[^\x20-\x7F]/;
|
||||||
|
|
||||||
/******************************************************************************/
|
const is3rdParty = function(srcHostname, desHostname) {
|
||||||
|
|
||||||
Matrix.prototype.reset = function() {
|
|
||||||
this.r = 0;
|
|
||||||
this.type = '';
|
|
||||||
this.y = '';
|
|
||||||
this.z = '';
|
|
||||||
this.rules = new Map();
|
|
||||||
this.changed = false;
|
|
||||||
this.decomposedSource = [];
|
|
||||||
this.decomposedDestination = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
Matrix.prototype.assign = function(other) {
|
|
||||||
// Remove rules not in other
|
|
||||||
for ( var 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 ) {
|
|
||||||
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) {
|
|
||||||
// Specific types
|
|
||||||
let thisBits = this.rules.get('* *');
|
|
||||||
let fromBits = from.rules.get('* *');
|
|
||||||
if ( fromBits !== thisBits ) {
|
|
||||||
if ( fromBits !== undefined ) {
|
|
||||||
this.rules.set('* *', fromBits);
|
|
||||||
} else {
|
|
||||||
this.rules.delete('* *');
|
|
||||||
}
|
|
||||||
this.changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let key = srcHostname + ' *';
|
|
||||||
thisBits = this.rules.get(key);
|
|
||||||
fromBits = from.rules.get(key);
|
|
||||||
if ( fromBits !== thisBits ) {
|
|
||||||
if ( fromBits !== undefined ) {
|
|
||||||
this.rules.set(key, fromBits);
|
|
||||||
} else {
|
|
||||||
this.rules.delete(key);
|
|
||||||
}
|
|
||||||
this.changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Specific destinations
|
|
||||||
for ( let desHostname in desHostnames ) {
|
|
||||||
if ( desHostnames.hasOwnProperty(desHostname) === false ) { continue; }
|
|
||||||
key = '* ' + desHostname;
|
|
||||||
thisBits = this.rules.get(key);
|
|
||||||
fromBits = from.rules.get(key);
|
|
||||||
if ( fromBits !== thisBits ) {
|
|
||||||
if ( fromBits !== undefined ) {
|
|
||||||
this.rules.set(key, fromBits);
|
|
||||||
} else {
|
|
||||||
this.rules.delete(key);
|
|
||||||
}
|
|
||||||
this.changed = true;
|
|
||||||
}
|
|
||||||
key = srcHostname + ' ' + desHostname ;
|
|
||||||
thisBits = this.rules.get(key);
|
|
||||||
fromBits = from.rules.get(key);
|
|
||||||
if ( fromBits !== thisBits ) {
|
|
||||||
if ( fromBits !== undefined ) {
|
|
||||||
this.rules.set(key, fromBits);
|
|
||||||
} else {
|
|
||||||
this.rules.delete(key);
|
|
||||||
}
|
|
||||||
this.changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.changed;
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// - * * type
|
|
||||||
// - from * type
|
|
||||||
// - * to *
|
|
||||||
// - from to *
|
|
||||||
|
|
||||||
Matrix.prototype.hasSameRules = function(other, srcHostname, desHostnames) {
|
|
||||||
|
|
||||||
// Specific types
|
|
||||||
var key = '* *';
|
|
||||||
if ( this.rules.get(key) !== other.rules.get(key) ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
key = srcHostname + ' *';
|
|
||||||
if ( this.rules.get(key) !== other.rules.get(key) ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Specific destinations
|
|
||||||
for ( var desHostname in desHostnames ) {
|
|
||||||
key = '* ' + desHostname;
|
|
||||||
if ( this.rules.get(key) !== other.rules.get(key) ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
key = srcHostname + ' ' + desHostname ;
|
|
||||||
if ( this.rules.get(key) !== other.rules.get(key) ) {
|
|
||||||
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);
|
|
||||||
if ( newBitmap === oldBitmap ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ( newBitmap === 0 ) {
|
|
||||||
this.rules.delete(k);
|
|
||||||
} else {
|
|
||||||
this.rules.set(k, newBitmap);
|
|
||||||
}
|
|
||||||
this.changed = true;
|
|
||||||
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.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;
|
|
||||||
}
|
|
||||||
return bitmap >> typeBitOffsets[type] & 3;
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
Matrix.prototype.clearRegisters = function() {
|
|
||||||
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
|
// If at least one is party-less, the relation can't be labelled
|
||||||
// "3rd-party"
|
// "3rd-party"
|
||||||
if ( desHostname === '*' || srcHostname === '*' || srcHostname === '' ) {
|
if ( desHostname === '*' || srcHostname === '*' || srcHostname === '' ) {
|
||||||
|
@ -261,7 +79,7 @@ var is3rdParty = function(srcHostname, desHostname) {
|
||||||
// - localhost
|
// - localhost
|
||||||
// - file-scheme
|
// - file-scheme
|
||||||
// etc.
|
// etc.
|
||||||
var srcDomain = domainFromHostname(srcHostname) || srcHostname;
|
const srcDomain = domainFromHostname(srcHostname) || srcHostname;
|
||||||
|
|
||||||
if ( desHostname.endsWith(srcDomain) === false ) {
|
if ( desHostname.endsWith(srcDomain) === false ) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -271,145 +89,446 @@ var is3rdParty = function(srcHostname, desHostname) {
|
||||||
desHostname.charAt(desHostname.length - srcDomain.length - 1) !== '.';
|
desHostname.charAt(desHostname.length - srcDomain.length - 1) !== '.';
|
||||||
};
|
};
|
||||||
|
|
||||||
var domainFromHostname = µBlock.URI.domainFromHostname;
|
const domainFromHostname = µBlock.URI.domainFromHostname;
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
Matrix.prototype.evaluateCellZ = function(srcHostname, desHostname, type) {
|
const Matrix = class {
|
||||||
µBlock.decomposeHostname(srcHostname, this.decomposedSource);
|
|
||||||
this.type = type;
|
constructor() {
|
||||||
let bitOffset = typeBitOffsets[type];
|
this.reset();
|
||||||
for ( let shn of this.decomposedSource ) {
|
}
|
||||||
this.z = shn;
|
|
||||||
let v = this.rules.get(shn + ' ' + desHostname);
|
|
||||||
if ( v !== undefined ) {
|
reset() {
|
||||||
v = v >>> bitOffset & 3;
|
this.r = 0;
|
||||||
if ( v !== 0 ) {
|
this.type = '';
|
||||||
this.r = v;
|
this.y = '';
|
||||||
return v;
|
this.z = '';
|
||||||
|
this.rules = new Map();
|
||||||
|
this.changed = false;
|
||||||
|
this.decomposedSource = [];
|
||||||
|
this.decomposedDestination = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
assign(other) {
|
||||||
|
// Remove rules not in other
|
||||||
|
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 ( const entry of other.rules ) {
|
||||||
|
if ( this.rules.get(entry[0]) !== entry[1] ) {
|
||||||
|
this.rules.set(entry[0], entry[1]);
|
||||||
|
this.changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// srcHostname is '*' at this point
|
|
||||||
this.r = 0;
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
Matrix.prototype.evaluateCellZY = function(srcHostname, desHostname, type) {
|
copyRules(from, srcHostname, desHostnames) {
|
||||||
// Pathological cases.
|
// Specific types
|
||||||
if ( desHostname === '' ) {
|
let thisBits = this.rules.get('* *');
|
||||||
|
let fromBits = from.rules.get('* *');
|
||||||
|
if ( fromBits !== thisBits ) {
|
||||||
|
if ( fromBits !== undefined ) {
|
||||||
|
this.rules.set('* *', fromBits);
|
||||||
|
} else {
|
||||||
|
this.rules.delete('* *');
|
||||||
|
}
|
||||||
|
this.changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = srcHostname + ' *';
|
||||||
|
thisBits = this.rules.get(key);
|
||||||
|
fromBits = from.rules.get(key);
|
||||||
|
if ( fromBits !== thisBits ) {
|
||||||
|
if ( fromBits !== undefined ) {
|
||||||
|
this.rules.set(key, fromBits);
|
||||||
|
} else {
|
||||||
|
this.rules.delete(key);
|
||||||
|
}
|
||||||
|
this.changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specific destinations
|
||||||
|
for ( const desHostname in desHostnames ) {
|
||||||
|
if ( desHostnames.hasOwnProperty(desHostname) === false ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
key = '* ' + desHostname;
|
||||||
|
thisBits = this.rules.get(key);
|
||||||
|
fromBits = from.rules.get(key);
|
||||||
|
if ( fromBits !== thisBits ) {
|
||||||
|
if ( fromBits !== undefined ) {
|
||||||
|
this.rules.set(key, fromBits);
|
||||||
|
} else {
|
||||||
|
this.rules.delete(key);
|
||||||
|
}
|
||||||
|
this.changed = true;
|
||||||
|
}
|
||||||
|
key = srcHostname + ' ' + desHostname ;
|
||||||
|
thisBits = this.rules.get(key);
|
||||||
|
fromBits = from.rules.get(key);
|
||||||
|
if ( fromBits !== thisBits ) {
|
||||||
|
if ( fromBits !== undefined ) {
|
||||||
|
this.rules.set(key, fromBits);
|
||||||
|
} else {
|
||||||
|
this.rules.delete(key);
|
||||||
|
}
|
||||||
|
this.changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// - * * type
|
||||||
|
// - from * type
|
||||||
|
// - * to *
|
||||||
|
// - from to *
|
||||||
|
|
||||||
|
hasSameRules(other, srcHostname, desHostnames) {
|
||||||
|
// Specific types
|
||||||
|
let key = '* *';
|
||||||
|
if ( this.rules.get(key) !== other.rules.get(key) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
key = srcHostname + ' *';
|
||||||
|
if ( this.rules.get(key) !== other.rules.get(key) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Specific destinations
|
||||||
|
for ( const desHostname in desHostnames ) {
|
||||||
|
key = '* ' + desHostname;
|
||||||
|
if ( this.rules.get(key) !== other.rules.get(key) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
key = srcHostname + ' ' + desHostname ;
|
||||||
|
if ( this.rules.get(key) !== other.rules.get(key) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if ( newBitmap === 0 ) {
|
||||||
|
this.rules.delete(k);
|
||||||
|
} else {
|
||||||
|
this.rules.set(k, newBitmap);
|
||||||
|
}
|
||||||
|
this.changed = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
unsetCell(srcHostname, desHostname, type) {
|
||||||
|
this.evaluateCellZY(srcHostname, desHostname, type);
|
||||||
|
if ( this.r === 0 ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.setCell(srcHostname, desHostname, type, 0);
|
||||||
|
this.changed = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
evaluateCell(srcHostname, desHostname, type) {
|
||||||
|
const key = srcHostname + ' ' + desHostname;
|
||||||
|
const bitmap = this.rules.get(key);
|
||||||
|
if ( bitmap === undefined ) { return 0; }
|
||||||
|
return bitmap >> typeBitOffsets[type] & 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
clearRegisters() {
|
||||||
|
this.r = 0;
|
||||||
|
this.type = this.y = this.z = '';
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
evaluateCellZ(srcHostname, desHostname, type) {
|
||||||
|
µBlock.decomposeHostname(srcHostname, this.decomposedSource);
|
||||||
|
this.type = type;
|
||||||
|
const bitOffset = typeBitOffsets[type];
|
||||||
|
for ( const shn of this.decomposedSource ) {
|
||||||
|
this.z = shn;
|
||||||
|
let v = this.rules.get(shn + ' ' + desHostname);
|
||||||
|
if ( v !== undefined ) {
|
||||||
|
v = v >>> bitOffset & 3;
|
||||||
|
if ( v !== 0 ) {
|
||||||
|
this.r = v;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// srcHostname is '*' at this point
|
||||||
this.r = 0;
|
this.r = 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Precedence: from most specific to least specific
|
|
||||||
|
|
||||||
// Specific-destination, any party, any type
|
evaluateCellZY(srcHostname, desHostname, type) {
|
||||||
µBlock.decomposeHostname(desHostname, this.decomposedDestination);
|
// Pathological cases.
|
||||||
for ( let dhn of this.decomposedDestination ) {
|
if ( desHostname === '' ) {
|
||||||
if ( dhn === '*' ) { break; }
|
this.r = 0;
|
||||||
this.y = dhn;
|
return 0;
|
||||||
if ( this.evaluateCellZ(srcHostname, dhn, '*') !== 0 ) {
|
|
||||||
return this.r;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let thirdParty = is3rdParty(srcHostname, desHostname);
|
// Precedence: from most specific to least specific
|
||||||
|
|
||||||
// Any destination
|
// Specific-destination, any party, any type
|
||||||
this.y = '*';
|
µBlock.decomposeHostname(desHostname, this.decomposedDestination);
|
||||||
|
for ( const dhn of this.decomposedDestination ) {
|
||||||
// Specific party
|
if ( dhn === '*' ) { break; }
|
||||||
if ( thirdParty ) {
|
this.y = dhn;
|
||||||
// 3rd-party, specific type
|
if ( this.evaluateCellZ(srcHostname, dhn, '*') !== 0 ) {
|
||||||
if ( type === 'script' ) {
|
|
||||||
if ( this.evaluateCellZ(srcHostname, '*', '3p-script') !== 0 ) {
|
|
||||||
return this.r;
|
|
||||||
}
|
|
||||||
} else if ( type === 'sub_frame' ) {
|
|
||||||
if ( this.evaluateCellZ(srcHostname, '*', '3p-frame') !== 0 ) {
|
|
||||||
return this.r;
|
return this.r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 3rd-party, any type
|
|
||||||
if ( this.evaluateCellZ(srcHostname, '*', '3p') !== 0 ) {
|
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' ) {
|
||||||
|
if ( this.evaluateCellZ(srcHostname, '*', '3p-script') !== 0 ) {
|
||||||
|
return this.r;
|
||||||
|
}
|
||||||
|
} else if ( type === 'sub_frame' ) {
|
||||||
|
if ( this.evaluateCellZ(srcHostname, '*', '3p-frame') !== 0 ) {
|
||||||
|
return this.r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 3rd-party, any type
|
||||||
|
if ( this.evaluateCellZ(srcHostname, '*', '3p') !== 0 ) {
|
||||||
|
return this.r;
|
||||||
|
}
|
||||||
|
} else if ( type === 'script' ) {
|
||||||
|
// 1st party, specific type
|
||||||
|
if ( this.evaluateCellZ(srcHostname, '*', '1p-script') !== 0 ) {
|
||||||
|
return this.r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any destination, any party, specific type
|
||||||
|
if ( supportedDynamicTypes.hasOwnProperty(type) ) {
|
||||||
|
if ( this.evaluateCellZ(srcHostname, '*', type) !== 0 ) {
|
||||||
|
return this.r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any destination, any party, any type
|
||||||
|
if ( this.evaluateCellZ(srcHostname, '*', '*') !== 0 ) {
|
||||||
return this.r;
|
return this.r;
|
||||||
}
|
}
|
||||||
} else if ( type === 'script' ) {
|
|
||||||
// 1st party, specific type
|
this.type = '';
|
||||||
if ( this.evaluateCellZ(srcHostname, '*', '1p-script') !== 0 ) {
|
return 0;
|
||||||
return this.r;
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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)}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
srcHostnameFromRule(rule) {
|
||||||
|
return rule.slice(0, rule.indexOf(' '));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
desHostnameFromRule(rule) {
|
||||||
|
return rule.slice(rule.indexOf(' ') + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
toArray() {
|
||||||
|
const out = [],
|
||||||
|
toUnicode = punycode.toUnicode;
|
||||||
|
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; }
|
||||||
|
const val = this.evaluateCell(srcHostname, desHostname, type);
|
||||||
|
if ( val === 0 ) { continue; }
|
||||||
|
if ( srcHostname.indexOf('xn--') !== -1 ) {
|
||||||
|
srcHostname = toUnicode(srcHostname);
|
||||||
|
}
|
||||||
|
if ( desHostname.indexOf('xn--') !== -1 ) {
|
||||||
|
desHostname = toUnicode(desHostname);
|
||||||
|
}
|
||||||
|
out.push(
|
||||||
|
srcHostname + ' ' +
|
||||||
|
desHostname + ' ' +
|
||||||
|
type + ' ' +
|
||||||
|
actionToNameMap[val]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return this.toArray().join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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+/));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Any destination, any party, specific type
|
|
||||||
if ( supportedDynamicTypes.hasOwnProperty(type) ) {
|
validateRuleParts(parts) {
|
||||||
if ( this.evaluateCellZ(srcHostname, '*', type) !== 0 ) {
|
if ( parts.length < 4 ) { return; }
|
||||||
return this.r;
|
|
||||||
|
// Ignore hostname-based switch rules
|
||||||
|
if ( parts[0].endsWith(':') ) { return; }
|
||||||
|
|
||||||
|
// Ignore URL-based rules
|
||||||
|
if ( parts[1].indexOf('/') !== -1 ) { return; }
|
||||||
|
|
||||||
|
if ( typeBitOffsets.hasOwnProperty(parts[2]) === false ) { return; }
|
||||||
|
|
||||||
|
if ( nameToActionMap.hasOwnProperty(parts[3]) === false ) { return; }
|
||||||
|
|
||||||
|
// https://github.com/chrisaljoudi/uBlock/issues/840
|
||||||
|
// Discard invalid rules
|
||||||
|
if ( parts[1] !== '*' && parts[2] !== '*' ) { return; }
|
||||||
|
|
||||||
|
// Performance: avoid punycoding if hostnames are made only of ASCII chars.
|
||||||
|
if ( reNotASCII.test(parts[0]) ) { parts[0] = punycode.toASCII(parts[0]); }
|
||||||
|
if ( reNotASCII.test(parts[1]) ) { parts[1] = punycode.toASCII(parts[1]); }
|
||||||
|
|
||||||
|
// https://github.com/chrisaljoudi/uBlock/issues/1082
|
||||||
|
// Discard rules with invalid hostnames
|
||||||
|
if (
|
||||||
|
(parts[0] !== '*' && reBadHostname.test(parts[0])) ||
|
||||||
|
(parts[1] !== '*' && reBadHostname.test(parts[1]))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Any destination, any party, any type
|
|
||||||
if ( this.evaluateCellZ(srcHostname, '*', '*') !== 0 ) {
|
addFromRuleParts(parts) {
|
||||||
return this.r;
|
if ( this.validateRuleParts(parts) !== undefined ) {
|
||||||
|
this.setCell(parts[0], parts[1], parts[2], nameToActionMap[parts[3]]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.type = '';
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// http://youtu.be/gSGk1bQ9rcU?t=25m6s
|
removeFromRuleParts(parts) {
|
||||||
|
if ( this.validateRuleParts(parts) !== undefined ) {
|
||||||
/******************************************************************************/
|
this.setCell(parts[0], parts[1], parts[2], 0);
|
||||||
|
return true;
|
||||||
Matrix.prototype.mustAllowCellZY = function(srcHostname, desHostname, type) {
|
}
|
||||||
return this.evaluateCellZY(srcHostname, desHostname, type) === 2;
|
return false;
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
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() {
|
toSelfie() {
|
||||||
if ( this.r === 0 || this.type === '' ) { return; }
|
return {
|
||||||
return {
|
magicId: this.magicId,
|
||||||
source: 'dynamicHost',
|
rules: Array.from(this.rules)
|
||||||
result: this.r,
|
};
|
||||||
raw: `${this.z} ${this.y} ${this.type} ${this.intToActionMap.get(this.r)}`
|
}
|
||||||
};
|
|
||||||
|
|
||||||
|
fromSelfie(selfie) {
|
||||||
|
if ( selfie.magicId !== this.magicId ) { return false; }
|
||||||
|
this.rules = new Map(selfie.rules);
|
||||||
|
this.changed = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async benchmark() {
|
||||||
|
const requests = await µBlock.loadBenchmarkDataset();
|
||||||
|
if ( Array.isArray(requests) === false || requests.length === 0 ) {
|
||||||
|
log.print('No requests found to benchmark');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.print(`Benchmarking sessionFirewall.evaluateCellZY()...`);
|
||||||
|
const fctxt = µBlock.filteringContext.duplicate();
|
||||||
|
const t0 = self.performance.now();
|
||||||
|
for ( const request of requests ) {
|
||||||
|
fctxt.setURL(request.url);
|
||||||
|
fctxt.setTabOriginFromURL(request.frameUrl);
|
||||||
|
fctxt.setType(request.cpt);
|
||||||
|
this.evaluateCellZY(
|
||||||
|
fctxt.getTabHostname(),
|
||||||
|
fctxt.getHostname(),
|
||||||
|
fctxt.type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const t1 = self.performance.now();
|
||||||
|
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([
|
Matrix.prototype.intToActionMap = new Map([
|
||||||
|
@ -418,168 +537,14 @@ Matrix.prototype.intToActionMap = new Map([
|
||||||
[ 3, 'noop' ]
|
[ 3, 'noop' ]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/******************************************************************************/
|
Matrix.prototype.magicId = 1;
|
||||||
|
|
||||||
Matrix.prototype.srcHostnameFromRule = function(rule) {
|
|
||||||
return rule.slice(0, rule.indexOf(' '));
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
Matrix.prototype.desHostnameFromRule = function(rule) {
|
µBlock.Firewall = Matrix;
|
||||||
return rule.slice(rule.indexOf(' ') + 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
// <<<<< end of local scope
|
||||||
|
}
|
||||||
Matrix.prototype.toArray = function() {
|
|
||||||
var 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 ) {
|
|
||||||
if ( typeBitOffsets.hasOwnProperty(type) === false ) { continue; }
|
|
||||||
var val = this.evaluateCell(srcHostname, desHostname, type);
|
|
||||||
if ( val === 0 ) { continue; }
|
|
||||||
if ( srcHostname.indexOf('xn--') !== -1 ) {
|
|
||||||
srcHostname = toUnicode(srcHostname);
|
|
||||||
}
|
|
||||||
if ( desHostname.indexOf('xn--') !== -1 ) {
|
|
||||||
desHostname = toUnicode(desHostname);
|
|
||||||
}
|
|
||||||
out.push(
|
|
||||||
srcHostname + ' ' +
|
|
||||||
desHostname + ' ' +
|
|
||||||
type + ' ' +
|
|
||||||
actionToNameMap[val]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
};
|
|
||||||
|
|
||||||
Matrix.prototype.toString = function() {
|
|
||||||
return this.toArray().join('\n');
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
Matrix.prototype.fromString = function(text, append) {
|
|
||||||
var 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) {
|
|
||||||
if ( parts.length < 4 ) { return; }
|
|
||||||
|
|
||||||
// Ignore hostname-based switch rules
|
|
||||||
if ( parts[0].endsWith(':') ) { return; }
|
|
||||||
|
|
||||||
// Ignore URL-based rules
|
|
||||||
if ( parts[1].indexOf('/') !== -1 ) { return; }
|
|
||||||
|
|
||||||
if ( typeBitOffsets.hasOwnProperty(parts[2]) === false ) { return; }
|
|
||||||
|
|
||||||
if ( nameToActionMap.hasOwnProperty(parts[3]) === false ) { return; }
|
|
||||||
|
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/840
|
|
||||||
// Discard invalid rules
|
|
||||||
if ( parts[1] !== '*' && parts[2] !== '*' ) { return; }
|
|
||||||
|
|
||||||
// Performance: avoid punycoding if hostnames are made only of ASCII chars.
|
|
||||||
if ( reNotASCII.test(parts[0]) ) { parts[0] = punycode.toASCII(parts[0]); }
|
|
||||||
if ( reNotASCII.test(parts[1]) ) { parts[1] = punycode.toASCII(parts[1]); }
|
|
||||||
|
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/1082
|
|
||||||
// Discard rules with invalid hostnames
|
|
||||||
if (
|
|
||||||
(parts[0] !== '*' && reBadHostname.test(parts[0])) ||
|
|
||||||
(parts[1] !== '*' && reBadHostname.test(parts[1]))
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts;
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
Matrix.prototype.addFromRuleParts = function(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) {
|
|
||||||
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() {
|
|
||||||
return {
|
|
||||||
magicId: magicId,
|
|
||||||
rules: Array.from(this.rules)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
Matrix.prototype.fromSelfie = function(selfie) {
|
|
||||||
if ( selfie.magicId !== magicId ) { return false; }
|
|
||||||
this.rules = new Map(selfie.rules);
|
|
||||||
this.changed = true;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
Matrix.prototype.benchmark = async function() {
|
|
||||||
const requests = await µBlock.loadBenchmarkDataset();
|
|
||||||
if ( Array.isArray(requests) === false || requests.length === 0 ) {
|
|
||||||
log.print('No requests found to benchmark');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log.print(`Benchmarking sessionFirewall.evaluateCellZY()...`);
|
|
||||||
const fctxt = µBlock.filteringContext.duplicate();
|
|
||||||
const t0 = self.performance.now();
|
|
||||||
for ( const request of requests ) {
|
|
||||||
fctxt.setURL(request.url);
|
|
||||||
fctxt.setTabOriginFromURL(request.frameUrl);
|
|
||||||
fctxt.setType(request.cpt);
|
|
||||||
this.evaluateCellZY(
|
|
||||||
fctxt.getTabHostname(),
|
|
||||||
fctxt.getHostname(),
|
|
||||||
fctxt.type
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const t1 = self.performance.now();
|
|
||||||
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`);
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
return Matrix;
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// http://youtu.be/5-K8R1hDG9E?t=31m1s
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
|
|
@ -216,78 +216,78 @@ vAPI.messaging.setup(onMessage);
|
||||||
|
|
||||||
const µb = µBlock;
|
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 hnDict = Object.create(null);
|
||||||
const cnMap = [];
|
const cnMap = [];
|
||||||
for ( const [ hostname, hnCounts ] of hostnameToCountMap ) {
|
|
||||||
if ( hnDict[hostname] !== undefined ) { continue; }
|
const createDictEntry = (domain, hostname, details) => {
|
||||||
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 cname = vAPI.net.canonicalNameFromHostname(hostname);
|
const cname = vAPI.net.canonicalNameFromHostname(hostname);
|
||||||
if ( cname !== undefined ) {
|
if ( cname !== undefined ) {
|
||||||
cnMap.push([ cname, hostname ]);
|
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.hostnameDict = hnDict;
|
||||||
out.cnameMap = cnMap;
|
out.cnameMap = cnMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFirewallRules = function(srcHostname, desHostnames) {
|
const firewallRuleTypes = [
|
||||||
const out = {};
|
'*',
|
||||||
|
'image',
|
||||||
|
'3p',
|
||||||
|
'inline-script',
|
||||||
|
'1p-script',
|
||||||
|
'3p-script',
|
||||||
|
'3p-frame',
|
||||||
|
];
|
||||||
|
|
||||||
|
const getFirewallRules = function(src, out) {
|
||||||
|
const { hostnameDict } = out;
|
||||||
|
const ruleset = {};
|
||||||
const df = µb.sessionFirewall;
|
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, '*', '*');
|
for ( const type of firewallRuleTypes ) {
|
||||||
out['. * image'] = df.lookupRuleData(srcHostname, '*', 'image');
|
let r = df.lookupRuleData('*', '*', type);
|
||||||
out['. * 3p'] = df.lookupRuleData(srcHostname, '*', '3p');
|
if ( r === undefined ) { continue; }
|
||||||
out['. * inline-script'] = df.lookupRuleData(srcHostname,
|
ruleset[`/ * ${type}`] = r;
|
||||||
'*',
|
|
||||||
'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');
|
|
||||||
|
|
||||||
for ( const desHostname in desHostnames ) {
|
|
||||||
out[`/ ${desHostname} *`] = df.lookupRuleData('*', desHostname, '*');
|
|
||||||
out[`. ${desHostname} *`] = df.lookupRuleData(srcHostname, desHostname, '*');
|
|
||||||
}
|
}
|
||||||
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) {
|
const popupDataFromTabId = function(tabId, tabTitle) {
|
||||||
|
@ -311,8 +311,6 @@ const popupDataFromTabId = function(tabId, tabTitle) {
|
||||||
pageURL: tabContext.normalURL,
|
pageURL: tabContext.normalURL,
|
||||||
pageHostname: rootHostname,
|
pageHostname: rootHostname,
|
||||||
pageDomain: tabContext.rootDomain,
|
pageDomain: tabContext.rootDomain,
|
||||||
pageAllowedRequestCount: 0,
|
|
||||||
pageBlockedRequestCount: 0,
|
|
||||||
popupBlockedCount: 0,
|
popupBlockedCount: 0,
|
||||||
popupPanelSections: µbus.popupPanelSections,
|
popupPanelSections: µbus.popupPanelSections,
|
||||||
popupPanelDisabledSections: µbhs.popupPanelDisabledSections,
|
popupPanelDisabledSections: µbhs.popupPanelDisabledSections,
|
||||||
|
@ -329,23 +327,11 @@ const popupDataFromTabId = function(tabId, tabTitle) {
|
||||||
|
|
||||||
const pageStore = µb.pageStoreFromTabId(tabId);
|
const pageStore = µb.pageStoreFromTabId(tabId);
|
||||||
if ( pageStore ) {
|
if ( pageStore ) {
|
||||||
// https://github.com/gorhill/uBlock/issues/2105
|
r.pageCounts = pageStore.counts;
|
||||||
// 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.netFilteringSwitch = pageStore.getNetFilteringSwitch();
|
r.netFilteringSwitch = pageStore.getNetFilteringSwitch();
|
||||||
getHostnameDict(pageStore.hostnameToCountMap, r);
|
getHostnameDict(pageStore.getAllHostnameDetails(), r);
|
||||||
r.contentLastModified = pageStore.contentLastModified;
|
r.contentLastModified = pageStore.contentLastModified;
|
||||||
r.firewallRules = getFirewallRules(rootHostname, r.hostnameDict);
|
getFirewallRules(rootHostname, r);
|
||||||
r.canElementPicker = µb.URI.isNetworkURI(r.rawURL);
|
r.canElementPicker = µb.URI.isNetworkURI(r.rawURL);
|
||||||
r.noPopups = µb.sessionSwitches.evaluateZ(
|
r.noPopups = µb.sessionSwitches.evaluateZ(
|
||||||
'no-popups',
|
'no-popups',
|
||||||
|
|
|
@ -176,10 +176,6 @@ NetFilteringResultCache.prototype.extensionOriginURL = vAPI.getURL('/');
|
||||||
|
|
||||||
// Frame stores are used solely to associate a URL with a frame id.
|
// 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 {
|
const FrameStore = class {
|
||||||
constructor(frameURL, parentId) {
|
constructor(frameURL, parentId) {
|
||||||
this.init(frameURL, parentId);
|
this.init(frameURL, parentId);
|
||||||
|
@ -201,14 +197,14 @@ const FrameStore = class {
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this.rawURL = this.hostname = this.domain = '';
|
this.rawURL = this.hostname = this.domain = '';
|
||||||
if ( frameStoreJunkyard.length < frameStoreJunkyardMax ) {
|
if ( FrameStore.junkyard.length < FrameStore.junkyardMax ) {
|
||||||
frameStoreJunkyard.push(this);
|
FrameStore.junkyard.push(this);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static factory(frameURL, parentId = -1) {
|
static factory(frameURL, parentId = -1) {
|
||||||
const entry = frameStoreJunkyard.pop();
|
const entry = FrameStore.junkyard.pop();
|
||||||
if ( entry === undefined ) {
|
if ( entry === undefined ) {
|
||||||
return new FrameStore(frameURL, parentId);
|
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 CountDetails = class {
|
||||||
const pageStoreJunkyard = [];
|
constructor() {
|
||||||
const pageStoreJunkyardMax = 10;
|
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 {
|
const PageStore = class {
|
||||||
constructor(tabId, context) {
|
constructor(tabId, context) {
|
||||||
|
@ -230,11 +277,13 @@ const PageStore = class {
|
||||||
this.journalLastCommitted = this.journalLastUncommitted = -1;
|
this.journalLastCommitted = this.journalLastUncommitted = -1;
|
||||||
this.journalLastUncommittedOrigin = undefined;
|
this.journalLastUncommittedOrigin = undefined;
|
||||||
this.netFilteringCache = NetFilteringResultCache.factory();
|
this.netFilteringCache = NetFilteringResultCache.factory();
|
||||||
|
this.hostnameDetailsMap = new HostnameDetailsMap();
|
||||||
|
this.counts = new CountDetails();
|
||||||
this.init(tabId, context);
|
this.init(tabId, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
static factory(tabId, context) {
|
static factory(tabId, context) {
|
||||||
let entry = pageStoreJunkyard.pop();
|
let entry = PageStore.junkyard.pop();
|
||||||
if ( entry === undefined ) {
|
if ( entry === undefined ) {
|
||||||
entry = new PageStore(tabId, context);
|
entry = new PageStore(tabId, context);
|
||||||
} else {
|
} else {
|
||||||
|
@ -263,11 +312,10 @@ const PageStore = class {
|
||||||
this.tabHostname = tabContext.rootHostname;
|
this.tabHostname = tabContext.rootHostname;
|
||||||
this.title = tabContext.rawURL;
|
this.title = tabContext.rawURL;
|
||||||
this.rawURL = tabContext.rawURL;
|
this.rawURL = tabContext.rawURL;
|
||||||
this.hostnameToCountMap = new Map();
|
this.hostnameDetailsMap.reset();
|
||||||
this.contentLastModified = 0;
|
this.contentLastModified = 0;
|
||||||
this.logData = undefined;
|
this.logData = undefined;
|
||||||
this.perLoadBlockedRequestCount = 0;
|
this.counts.reset();
|
||||||
this.perLoadAllowedRequestCount = 0;
|
|
||||||
this.remoteFontCount = 0;
|
this.remoteFontCount = 0;
|
||||||
this.popupBlockedCount = 0;
|
this.popupBlockedCount = 0;
|
||||||
this.largeMediaCount = 0;
|
this.largeMediaCount = 0;
|
||||||
|
@ -342,7 +390,7 @@ const PageStore = class {
|
||||||
this.tabHostname = '';
|
this.tabHostname = '';
|
||||||
this.title = '';
|
this.title = '';
|
||||||
this.rawURL = '';
|
this.rawURL = '';
|
||||||
this.hostnameToCountMap = null;
|
this.hostnameDetailsMap.dispose();
|
||||||
this.netFilteringCache.empty();
|
this.netFilteringCache.empty();
|
||||||
this.allowLargeMediaElementsUntil = Date.now();
|
this.allowLargeMediaElementsUntil = Date.now();
|
||||||
this.allowLargeMediaElementsRegex = undefined;
|
this.allowLargeMediaElementsRegex = undefined;
|
||||||
|
@ -358,8 +406,8 @@ const PageStore = class {
|
||||||
this.journal = [];
|
this.journal = [];
|
||||||
this.journalLastUncommittedOrigin = undefined;
|
this.journalLastUncommittedOrigin = undefined;
|
||||||
this.journalLastCommitted = this.journalLastUncommitted = -1;
|
this.journalLastCommitted = this.journalLastUncommitted = -1;
|
||||||
if ( pageStoreJunkyard.length < pageStoreJunkyardMax ) {
|
if ( PageStore.junkyard.length < PageStore.junkyardMax ) {
|
||||||
pageStoreJunkyard.push(this);
|
PageStore.junkyard.push(this);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -454,6 +502,23 @@ const PageStore = class {
|
||||||
this.netFilteringCache.empty();
|
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() {
|
injectLargeMediaElementScriptlet() {
|
||||||
vAPI.tabs.executeScript(this.tabId, {
|
vAPI.tabs.executeScript(this.tabId, {
|
||||||
file: '/js/scriptlets/load-large-media-interactive.js',
|
file: '/js/scriptlets/load-large-media-interactive.js',
|
||||||
|
@ -478,18 +543,15 @@ const PageStore = class {
|
||||||
// https://github.com/gorhill/uBlock/issues/2053
|
// https://github.com/gorhill/uBlock/issues/2053
|
||||||
// There is no way around using journaling to ensure we deal properly with
|
// There is no way around using journaling to ensure we deal properly with
|
||||||
// potentially out of order navigation events vs. network request events.
|
// potentially out of order navigation events vs. network request events.
|
||||||
journalAddRequest(hostname, result) {
|
journalAddRequest(fctxt, result) {
|
||||||
|
const hostname = fctxt.getHostname();
|
||||||
if ( hostname === '' ) { return; }
|
if ( hostname === '' ) { return; }
|
||||||
this.journal.push(
|
this.journal.push(hostname, result, fctxt.itype);
|
||||||
hostname,
|
if ( this.journalTimer !== undefined ) { return; }
|
||||||
result === 1 ? 0x00000001 : 0x00010000
|
this.journalTimer = vAPI.setTimeout(
|
||||||
|
( ) => { this.journalProcess(true); },
|
||||||
|
µb.hiddenSettings.requestJournalProcessPeriod
|
||||||
);
|
);
|
||||||
if ( this.journalTimer === undefined ) {
|
|
||||||
this.journalTimer = vAPI.setTimeout(
|
|
||||||
( ) => { this.journalProcess(true); },
|
|
||||||
µb.hiddenSettings.requestJournalProcessPeriod
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
journalAddRootFrame(type, url) {
|
journalAddRootFrame(type, url) {
|
||||||
|
@ -528,40 +590,57 @@ const PageStore = class {
|
||||||
const journal = this.journal;
|
const journal = this.journal;
|
||||||
const pivot = Math.max(0, this.journalLastCommitted);
|
const pivot = Math.max(0, this.journalLastCommitted);
|
||||||
const now = Date.now();
|
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.
|
// Everything after pivot originates from current page.
|
||||||
for ( let i = pivot; i < journal.length; i += 2 ) {
|
for ( let i = pivot; i < journal.length; i += 3 ) {
|
||||||
const hostname = journal[i];
|
const hostname = journal[i+0];
|
||||||
let hostnameCounts = this.hostnameToCountMap.get(hostname);
|
let hnDetails = this.hostnameDetailsMap.get(hostname);
|
||||||
if ( hostnameCounts === undefined ) {
|
if ( hnDetails === undefined ) {
|
||||||
hostnameCounts = 0;
|
hnDetails = new HostnameDetails(hostname);
|
||||||
|
this.hostnameDetailsMap.set(hostname, hnDetails);
|
||||||
this.contentLastModified = now;
|
this.contentLastModified = now;
|
||||||
}
|
}
|
||||||
let count = journal[i+1];
|
const blocked = journal[i+1] === 1;
|
||||||
this.hostnameToCountMap.set(hostname, hostnameCounts + count);
|
const itype = journal[i+2];
|
||||||
aggregateCounts += count;
|
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;
|
this.journalLastUncommitted = this.journalLastCommitted = -1;
|
||||||
|
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/905#issuecomment-76543649
|
// https://github.com/chrisaljoudi/uBlock/issues/905#issuecomment-76543649
|
||||||
// No point updating the badge if it's not being displayed.
|
// 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);
|
µb.updateToolbarIcon(this.tabId, 0x02);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Everything before pivot does not originate from current page -- we
|
// Everything before pivot does not originate from current page -- we
|
||||||
// still need to bump global blocked/allowed counts.
|
// still need to bump global blocked/allowed counts.
|
||||||
for ( let i = 0; i < pivot; i += 2 ) {
|
for ( let i = 0; i < pivot; i += 3 ) {
|
||||||
aggregateCounts += journal[i+1];
|
if ( journal[i+1] === 1 ) {
|
||||||
|
aggregateBlocked += 1;
|
||||||
|
} else {
|
||||||
|
aggregateAllowed += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ( aggregateCounts !== 0 ) {
|
if ( aggregateAllowed !== 0 || aggregateBlocked !== 0 ) {
|
||||||
µb.localSettings.blockedRequestCount +=
|
µb.localSettings.blockedRequestCount += aggregateBlocked;
|
||||||
aggregateCounts & 0xFFFF;
|
µb.localSettings.allowedRequestCount += aggregateAllowed;
|
||||||
µb.localSettings.allowedRequestCount +=
|
|
||||||
aggregateCounts >>> 16 & 0xFFFF;
|
|
||||||
µb.localSettingsLastModified = now;
|
µb.localSettingsLastModified = now;
|
||||||
}
|
}
|
||||||
journal.length = 0;
|
journal.length = 0;
|
||||||
|
@ -948,6 +1027,10 @@ PageStore.prototype.collapsibleResources = new Set([
|
||||||
µb.FilteringContext.SUB_FRAME,
|
µb.FilteringContext.SUB_FRAME,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// To mitigate memory churning
|
||||||
|
PageStore.junkyard = [];
|
||||||
|
PageStore.junkyardMax = 10;
|
||||||
|
|
||||||
µb.PageStore = PageStore;
|
µb.PageStore = PageStore;
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
|
@ -49,7 +49,6 @@ vAPI.localStorage.getItemAsync('popupPanelSections').then(bits => {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const messaging = vAPI.messaging;
|
const messaging = vAPI.messaging;
|
||||||
const reIP = /^\d+(?:\.\d+){1,3}$/;
|
|
||||||
const scopeToSrcHostnameMap = {
|
const scopeToSrcHostnameMap = {
|
||||||
'/': '*',
|
'/': '*',
|
||||||
'.': ''
|
'.': ''
|
||||||
|
@ -61,10 +60,7 @@ const domainsHitStr = vAPI.i18n('popupHitDomainCount');
|
||||||
let popupData = {};
|
let popupData = {};
|
||||||
let dfPaneBuilt = false;
|
let dfPaneBuilt = false;
|
||||||
let dfHotspots = null;
|
let dfHotspots = null;
|
||||||
let allDomains = {};
|
|
||||||
let allDomainCount = 0;
|
|
||||||
let allHostnameRows = [];
|
let allHostnameRows = [];
|
||||||
let touchedDomainCount = 0;
|
|
||||||
let cachedPopupHash = '';
|
let cachedPopupHash = '';
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/2550
|
// https://github.com/gorhill/uBlock/issues/2550
|
||||||
|
@ -125,13 +121,8 @@ const hashFromPopupData = function(reset) {
|
||||||
const rules = popupData.firewallRules;
|
const rules = popupData.firewallRules;
|
||||||
for ( const key in rules ) {
|
for ( const key in rules ) {
|
||||||
const rule = rules[key];
|
const rule = rules[key];
|
||||||
if ( rule === null ) { continue; }
|
if ( rule === undefined ) { continue; }
|
||||||
hasher.push(
|
hasher.push(rule);
|
||||||
rule.src + ' ' +
|
|
||||||
rule.des + ' ' +
|
|
||||||
rule.type + ' ' +
|
|
||||||
rule.action
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
hasher.sort();
|
hasher.sort();
|
||||||
hasher.push(uDom('body').hasClass('off'));
|
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) {
|
const formatNumber = function(count) {
|
||||||
if ( typeof count !== 'number' ) { return ''; }
|
if ( typeof count !== 'number' ) { return ''; }
|
||||||
if ( count < 1e6 ) { return count.toLocaleString(); }
|
if ( count < 1e6 ) { return count.toLocaleString(); }
|
||||||
|
@ -202,111 +199,153 @@ const safePunycodeToUnicode = function(hn) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const rulekeyCompare = function(a, b) {
|
const updateFirewallCellCount = function(cells, allowed, blocked) {
|
||||||
let ha = a.slice(2, a.indexOf(' ', 2));
|
for ( const cell of cells ) {
|
||||||
if ( !reIP.test(ha) ) {
|
if ( gtz(allowed) ) {
|
||||||
ha = hostnameToSortableTokenMap.get(ha) || ' ';
|
cell.setAttribute(
|
||||||
}
|
'data-acount',
|
||||||
let hb = b.slice(2, b.indexOf(' ', 2));
|
Math.min(Math.ceil(Math.log(allowed + 1) / Math.LN10), 3)
|
||||||
if ( !reIP.test(hb) ) {
|
);
|
||||||
hb = hostnameToSortableTokenMap.get(hb) || ' ';
|
} else {
|
||||||
}
|
cell.setAttribute('data-acount', '0');
|
||||||
const ca = ha.charCodeAt(0);
|
}
|
||||||
const cb = hb.charCodeAt(0);
|
if ( gtz(blocked) ) {
|
||||||
if ( ca !== cb ) {
|
cell.setAttribute(
|
||||||
return ca - cb;
|
'data-bcount',
|
||||||
}
|
Math.min(Math.ceil(Math.log(blocked + 1) / Math.LN10), 3)
|
||||||
return ha.localeCompare(hb);
|
);
|
||||||
};
|
} else {
|
||||||
|
cell.setAttribute('data-bcount', '0');
|
||||||
/******************************************************************************/
|
}
|
||||||
|
|
||||||
const updateFirewallCell = function(scope, des, type, rule) {
|
|
||||||
const row = document.querySelector(
|
|
||||||
`#firewall div[data-des="${des}"][data-type="${type}"]`
|
|
||||||
);
|
|
||||||
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));
|
|
||||||
} else {
|
|
||||||
cell.setAttribute('data-bcount', '0');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const updateAllFirewallCells = function() {
|
const updateFirewallCellRule = function(cells, scope, des, type, rule) {
|
||||||
const rules = popupData.firewallRules;
|
const ruleParts = rule !== undefined ? rule.split(' ') : undefined;
|
||||||
for ( const key in rules ) {
|
|
||||||
if ( rules.hasOwnProperty(key) === false ) { continue; }
|
for ( const cell of cells ) {
|
||||||
updateFirewallCell(
|
if ( ruleParts === undefined ) {
|
||||||
key.charAt(0),
|
cell.removeAttribute('class');
|
||||||
key.slice(2, key.indexOf(' ', 2)),
|
continue;
|
||||||
key.slice(key.lastIndexOf(' ') + 1),
|
}
|
||||||
rules[key]
|
|
||||||
);
|
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 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];
|
||||||
|
if ( hnDetails === undefined ) {
|
||||||
|
updateFirewallCellCount(cells);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
a3pScript += allowed.script; b3pScript += blocked.script;
|
||||||
|
a3pFrame += allowed.frame; b3pFrame += blocked.frame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
document.body.classList.toggle('needSave', popupData.matrixIsDirty === true);
|
document.body.classList.toggle('needSave', popupData.matrixIsDirty === true);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// 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() {
|
const buildAllFirewallRows = function() {
|
||||||
// Do this before removing the rows
|
// Do this before removing the rows
|
||||||
if ( dfHotspots === null ) {
|
if ( dfHotspots === null ) {
|
||||||
|
@ -315,33 +354,47 @@ const buildAllFirewallRows = function() {
|
||||||
}
|
}
|
||||||
dfHotspots.remove();
|
dfHotspots.remove();
|
||||||
|
|
||||||
|
// This must be called before we create the rows.
|
||||||
|
expandHostnameStats();
|
||||||
|
|
||||||
// Update incrementally: reuse existing rows if possible.
|
// Update incrementally: reuse existing rows if possible.
|
||||||
const rowContainer = document.getElementById('firewall');
|
const rowContainer = document.getElementById('firewall');
|
||||||
const toAppend = document.createDocumentFragment();
|
const toAppend = document.createDocumentFragment();
|
||||||
const rowTemplate = document.querySelector('#templates > div:nth-of-type(1)');
|
const rowTemplate = document.querySelector(
|
||||||
let row = rowContainer.querySelector('div:nth-of-type(7) + div');
|
'#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 ) {
|
for ( const des of allHostnameRows ) {
|
||||||
if ( row === null ) {
|
if ( row === null ) {
|
||||||
row = rowTemplate.cloneNode(true);
|
row = rowTemplate.cloneNode(true);
|
||||||
toAppend.appendChild(row);
|
toAppend.appendChild(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
row.setAttribute('data-des', des);
|
row.setAttribute('data-des', des);
|
||||||
|
|
||||||
const hnDetails = popupData.hostnameDict[des] || {};
|
const hnDetails = hostnameDict[des] || {};
|
||||||
const isDomain = des === hnDetails.domain;
|
const isDomain = des === hnDetails.domain;
|
||||||
const prettyDomainName = punycode.toUnicode(des);
|
const prettyDomainName = punycode.toUnicode(des);
|
||||||
const isPunycoded = prettyDomainName !== 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');
|
const span = row.querySelector('span:first-of-type');
|
||||||
span.querySelector('span').textContent = prettyDomainName;
|
span.querySelector('span').textContent = prettyDomainName;
|
||||||
|
|
||||||
const classList = row.classList;
|
const classList = row.classList;
|
||||||
|
|
||||||
let desExtra = '';
|
let desExtra = '';
|
||||||
if ( classList.toggle('isCname', popupData.cnameMap.has(des)) ) {
|
if ( classList.toggle('isCname', cnameMap.has(des)) ) {
|
||||||
desExtra = punycode.toUnicode(popupData.cnameMap.get(des));
|
desExtra = punycode.toUnicode(cnameMap.get(des));
|
||||||
} else if (
|
} else if (
|
||||||
isDomain && isPunycoded &&
|
isDomain && isPunycoded &&
|
||||||
reCyrillicAmbiguous.test(prettyDomainName) &&
|
reCyrillicAmbiguous.test(prettyDomainName) &&
|
||||||
|
@ -351,13 +404,18 @@ const buildAllFirewallRows = function() {
|
||||||
}
|
}
|
||||||
span.querySelector('sub').textContent = desExtra;
|
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('isDomain', isDomain);
|
||||||
classList.toggle('isSubDomain', !isDomain);
|
classList.toggle('isSubDomain', !isDomain);
|
||||||
classList.toggle('allowed', hnDetails.allowCount !== 0);
|
const { counts } = hnDetails;
|
||||||
classList.toggle('blocked', hnDetails.blockCount !== 0);
|
classList.toggle('allowed', gtz(counts.allowed.any));
|
||||||
classList.toggle('totalAllowed', hnDetails.totalAllowCount !== 0);
|
classList.toggle('blocked', gtz(counts.blocked.any));
|
||||||
classList.toggle('totalBlocked', hnDetails.totalBlockCount !== 0);
|
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));
|
classList.toggle('expandException', expandExceptions.has(hnDetails.domain));
|
||||||
|
|
||||||
row = row.nextElementSibling;
|
row = row.nextElementSibling;
|
||||||
|
@ -366,14 +424,14 @@ const buildAllFirewallRows = function() {
|
||||||
// Remove unused trailing rows
|
// Remove unused trailing rows
|
||||||
if ( row !== null ) {
|
if ( row !== null ) {
|
||||||
while ( row.nextElementSibling !== null ) {
|
while ( row.nextElementSibling !== null ) {
|
||||||
rowContainer.removeChild(row.nextElementSibling);
|
row.nextElementSibling.remove();
|
||||||
}
|
}
|
||||||
rowContainer.removeChild(row);
|
row.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new rows all at once
|
// Add new rows all at once
|
||||||
if ( toAppend.childElementCount !== 0 ) {
|
if ( toAppend.childElementCount !== 0 ) {
|
||||||
rowContainer.appendChild(toAppend);
|
rowContainer.append(toAppend);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( dfPaneBuilt !== true && popupData.advancedUserEnabled ) {
|
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() {
|
const renderPrivacyExposure = function() {
|
||||||
allDomains = {};
|
const allDomains = {};
|
||||||
allDomainCount = touchedDomainCount = 0;
|
let allDomainCount = 0;
|
||||||
|
let touchedDomainCount = 0;
|
||||||
|
|
||||||
allHostnameRows = [];
|
allHostnameRows = [];
|
||||||
|
|
||||||
// Sort hostnames. First-party hostnames must always appear at the top
|
// Sort hostnames. First-party hostnames must always appear at the top
|
||||||
// of the list.
|
// of the list.
|
||||||
const desHostnameDone = {};
|
const desHostnameDone = {};
|
||||||
const keys = Object.keys(popupData.firewallRules)
|
const keys = Object.keys(popupData.hostnameDict)
|
||||||
.sort(rulekeyCompare);
|
.sort(hostnameCompare);
|
||||||
for ( const key of keys ) {
|
for ( const des of keys ) {
|
||||||
const des = key.slice(2, key.indexOf(' ', 2));
|
|
||||||
// Specific-type rules -- these are built-in
|
// Specific-type rules -- these are built-in
|
||||||
if ( des === '*' || desHostnameDone.hasOwnProperty(des) ) { continue; }
|
if ( des === '*' || desHostnameDone.hasOwnProperty(des) ) { continue; }
|
||||||
const hnDetails = popupData.hostnameDict[des] || {};
|
const hnDetails = popupData.hostnameDict[des];
|
||||||
if ( allDomains.hasOwnProperty(hnDetails.domain) === false ) {
|
const { domain, counts } = hnDetails;
|
||||||
allDomains[hnDetails.domain] = false;
|
if ( allDomains.hasOwnProperty(domain) === false ) {
|
||||||
|
allDomains[domain] = false;
|
||||||
allDomainCount += 1;
|
allDomainCount += 1;
|
||||||
}
|
}
|
||||||
if ( hnDetails.allowCount !== 0 ) {
|
if ( gtz(counts.allowed.any) ) {
|
||||||
if ( allDomains[hnDetails.domain] === false ) {
|
if ( allDomains[domain] === false ) {
|
||||||
allDomains[hnDetails.domain] = true;
|
allDomains[domain] = true;
|
||||||
touchedDomainCount += 1;
|
touchedDomainCount += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -419,9 +497,11 @@ const renderPrivacyExposure = function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const summary = domainsHitStr
|
const summary = domainsHitStr
|
||||||
.replace('{{count}}', touchedDomainCount.toLocaleString())
|
.replace('{{count}}', touchedDomainCount.toLocaleString())
|
||||||
.replace('{{total}}', allDomainCount.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('gotoPick').classList.toggle('enabled', canElementPicker);
|
||||||
uDom.nodeFromId('gotoZap').classList.toggle('enabled', canElementPicker);
|
uDom.nodeFromId('gotoZap').classList.toggle('enabled', canElementPicker);
|
||||||
|
|
||||||
let blocked = popupData.pageBlockedRequestCount;
|
let blocked, total;
|
||||||
let total = popupData.pageAllowedRequestCount + blocked;
|
if ( popupData.pageCounts !== undefined ) {
|
||||||
|
const counts = popupData.pageCounts;
|
||||||
|
blocked = counts.blocked.any;
|
||||||
|
total = blocked + counts.allowed.any;
|
||||||
|
} else {
|
||||||
|
blocked = 0;
|
||||||
|
total = 0;
|
||||||
|
}
|
||||||
let text;
|
let text;
|
||||||
if ( total === 0 ) {
|
if ( total === 0 ) {
|
||||||
text = formatNumber(0);
|
text = formatNumber(0);
|
||||||
|
@ -885,7 +972,7 @@ const setFirewallRule = async function(src, des, type, action, persist) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cachePopupData(response);
|
cachePopupData(response);
|
||||||
updateAllFirewallCells();
|
updateAllFirewallCells(true, false);
|
||||||
hashFromPopupData();
|
hashFromPopupData();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1075,8 +1162,7 @@ const revertFirewallRules = async function() {
|
||||||
tabId: popupData.tabId,
|
tabId: popupData.tabId,
|
||||||
});
|
});
|
||||||
cachePopupData(response);
|
cachePopupData(response);
|
||||||
updateAllFirewallCells();
|
updateAllFirewallCells(true, false);
|
||||||
updateHnSwitches();
|
|
||||||
hashFromPopupData();
|
hashFromPopupData();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1106,8 +1192,9 @@ const toggleHostnameSwitch = async function(ev) {
|
||||||
});
|
});
|
||||||
|
|
||||||
cachePopupData(response);
|
cachePopupData(response);
|
||||||
updateAllFirewallCells();
|
|
||||||
hashFromPopupData();
|
hashFromPopupData();
|
||||||
|
|
||||||
|
document.body.classList.toggle('needSave', popupData.matrixIsDirty === true);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
@ -1276,6 +1363,8 @@ const getPopupData = async function(tabId) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
uDom('#switch').on('click', toggleNetFilteringSwitch);
|
uDom('#switch').on('click', toggleNetFilteringSwitch);
|
||||||
uDom('#gotoZap').on('click', gotoZap);
|
uDom('#gotoZap').on('click', gotoZap);
|
||||||
uDom('#gotoPick').on('click', gotoPick);
|
uDom('#gotoPick').on('click', gotoPick);
|
||||||
|
@ -1284,6 +1373,36 @@ uDom('#saveRules').on('click', saveFirewallRules);
|
||||||
uDom('#revertRules').on('click', ( ) => { revertFirewallRules(); });
|
uDom('#revertRules').on('click', ( ) => { revertFirewallRules(); });
|
||||||
uDom('a[href]').on('click', gotoURL);
|
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
|
// <<<<< end of local scope
|
||||||
|
|
261
src/js/popup.js
261
src/js/popup.js
|
@ -75,10 +75,7 @@ const domainsHitStr = vAPI.i18n('popupHitDomainCount');
|
||||||
let popupData = {};
|
let popupData = {};
|
||||||
let dfPaneBuilt = false;
|
let dfPaneBuilt = false;
|
||||||
let dfHotspots = null;
|
let dfHotspots = null;
|
||||||
let allDomains = {};
|
|
||||||
let allDomainCount = 0;
|
|
||||||
let allHostnameRows = [];
|
let allHostnameRows = [];
|
||||||
let touchedDomainCount = 0;
|
|
||||||
let cachedPopupHash = '';
|
let cachedPopupHash = '';
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/2550
|
// 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) {
|
const formatNumber = function(count) {
|
||||||
return typeof count === 'number' ? count.toLocaleString() : '';
|
return typeof count === 'number' ? count.toLocaleString() : '';
|
||||||
};
|
};
|
||||||
|
@ -188,11 +189,11 @@ const formatNumber = function(count) {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const rulekeyCompare = function(a, b) {
|
const rulekeyCompare = function(a, b) {
|
||||||
let ha = a.slice(2, a.indexOf(' ', 2));
|
let ha = a;
|
||||||
if ( !reIP.test(ha) ) {
|
if ( !reIP.test(ha) ) {
|
||||||
ha = hostnameToSortableTokenMap.get(ha) || ' ';
|
ha = hostnameToSortableTokenMap.get(ha) || ' ';
|
||||||
}
|
}
|
||||||
let hb = b.slice(2, b.indexOf(' ', 2));
|
let hb = b;
|
||||||
if ( !reIP.test(hb) ) {
|
if ( !reIP.test(hb) ) {
|
||||||
hb = hostnameToSortableTokenMap.get(hb) || ' ';
|
hb = hostnameToSortableTokenMap.get(hb) || ' ';
|
||||||
}
|
}
|
||||||
|
@ -206,69 +207,20 @@ const rulekeyCompare = function(a, b) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const updateFirewallCell = function(scope, des, type, rule) {
|
const updateFirewallCellCount = function(cell, allowed, blocked) {
|
||||||
const row = document.querySelector(
|
if ( gtz(allowed) ) {
|
||||||
`#firewallContainer div[data-des="${des}"][data-type="${type}"]`
|
cell.setAttribute(
|
||||||
);
|
'data-acount',
|
||||||
if ( row === null ) { return; }
|
Math.min(Math.ceil(Math.log(allowed + 1) / Math.LN10), 3)
|
||||||
|
);
|
||||||
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 {
|
} else {
|
||||||
cell.setAttribute('data-acount', '0');
|
cell.setAttribute('data-acount', '0');
|
||||||
}
|
}
|
||||||
if ( hnDetails.blockCount !== 0 ) {
|
if ( gtz(blocked) ) {
|
||||||
cell.setAttribute('data-bcount', Math.min(Math.ceil(Math.log(hnDetails.blockCount + 1) / Math.LN10), 3));
|
cell.setAttribute(
|
||||||
} else {
|
'data-bcount',
|
||||||
cell.setAttribute('data-bcount', '0');
|
Math.min(Math.ceil(Math.log(blocked + 1) / Math.LN10), 3)
|
||||||
}
|
);
|
||||||
|
|
||||||
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));
|
|
||||||
} else {
|
} else {
|
||||||
cell.setAttribute('data-bcount', '0');
|
cell.setAttribute('data-bcount', '0');
|
||||||
}
|
}
|
||||||
|
@ -276,16 +228,89 @@ const updateFirewallCell = function(scope, des, type, rule) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const updateAllFirewallCells = function() {
|
const updateFirewallCellRule = function(cell, scope, des, type, ruleParts) {
|
||||||
const rules = popupData.firewallRules;
|
if ( cell instanceof HTMLElement === false ) { return; }
|
||||||
for ( const key in rules ) {
|
|
||||||
if ( rules.hasOwnProperty(key) === false ) { continue; }
|
if ( ruleParts === undefined ) {
|
||||||
updateFirewallCell(
|
cell.removeAttribute('class');
|
||||||
key.charAt(0),
|
return;
|
||||||
key.slice(2, key.indexOf(' ', 2)),
|
}
|
||||||
key.slice(key.lastIndexOf(' ') + 1),
|
|
||||||
rules[key]
|
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;
|
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() {
|
const buildAllFirewallRows = function() {
|
||||||
// Do this before removing the rows
|
// Do this before removing the rows
|
||||||
if ( dfHotspots === null ) {
|
if ( dfHotspots === null ) {
|
||||||
|
@ -305,6 +357,9 @@ const buildAllFirewallRows = function() {
|
||||||
}
|
}
|
||||||
dfHotspots.remove();
|
dfHotspots.remove();
|
||||||
|
|
||||||
|
// This must be called before we create the rows.
|
||||||
|
expandHostnameStats();
|
||||||
|
|
||||||
// Update incrementally: reuse existing rows if possible.
|
// Update incrementally: reuse existing rows if possible.
|
||||||
const rowContainer = document.getElementById('firewallContainer');
|
const rowContainer = document.getElementById('firewallContainer');
|
||||||
const toAppend = document.createDocumentFragment();
|
const toAppend = document.createDocumentFragment();
|
||||||
|
@ -324,6 +379,8 @@ const buildAllFirewallRows = function() {
|
||||||
const prettyDomainName = punycode.toUnicode(des);
|
const prettyDomainName = punycode.toUnicode(des);
|
||||||
const isPunycoded = prettyDomainName !== des;
|
const isPunycoded = prettyDomainName !== des;
|
||||||
|
|
||||||
|
const { allowed: hnAllowed, blocked: hnBlocked } = hnDetails.counts;
|
||||||
|
|
||||||
const span = row.querySelector('span:first-of-type');
|
const span = row.querySelector('span:first-of-type');
|
||||||
span.querySelector('span').textContent = prettyDomainName;
|
span.querySelector('span').textContent = prettyDomainName;
|
||||||
|
|
||||||
|
@ -344,12 +401,14 @@ const buildAllFirewallRows = function() {
|
||||||
classList.toggle('isRootContext', des === popupData.pageHostname);
|
classList.toggle('isRootContext', des === popupData.pageHostname);
|
||||||
classList.toggle('isDomain', isDomain);
|
classList.toggle('isDomain', isDomain);
|
||||||
classList.toggle('isSubDomain', !isDomain);
|
classList.toggle('isSubDomain', !isDomain);
|
||||||
classList.toggle('allowed', hnDetails.allowCount !== 0);
|
classList.toggle('allowed', gtz(hnAllowed.any));
|
||||||
classList.toggle('blocked', hnDetails.blockCount !== 0);
|
classList.toggle('blocked', gtz(hnBlocked.any));
|
||||||
classList.toggle('totalAllowed', hnDetails.totalAllowCount !== 0);
|
|
||||||
classList.toggle('totalBlocked', hnDetails.totalBlockCount !== 0);
|
|
||||||
classList.toggle('expandException', expandExceptions.has(hnDetails.domain));
|
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;
|
row = row.nextElementSibling;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,27 +439,29 @@ const buildAllFirewallRows = function() {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const renderPrivacyExposure = function() {
|
const renderPrivacyExposure = function() {
|
||||||
allDomains = {};
|
const allDomains = {};
|
||||||
allDomainCount = touchedDomainCount = 0;
|
let allDomainCount = 0;
|
||||||
|
let touchedDomainCount = 0;
|
||||||
|
|
||||||
allHostnameRows = [];
|
allHostnameRows = [];
|
||||||
|
|
||||||
// Sort hostnames. First-party hostnames must always appear at the top
|
// Sort hostnames. First-party hostnames must always appear at the top
|
||||||
// of the list.
|
// of the list.
|
||||||
const desHostnameDone = {};
|
const desHostnameDone = {};
|
||||||
const keys = Object.keys(popupData.firewallRules)
|
const keys = Object.keys(popupData.hostnameDict)
|
||||||
.sort(rulekeyCompare);
|
.sort(rulekeyCompare);
|
||||||
for ( const key of keys ) {
|
for ( const des of keys ) {
|
||||||
const des = key.slice(2, key.indexOf(' ', 2));
|
|
||||||
// Specific-type rules -- these are built-in
|
// Specific-type rules -- these are built-in
|
||||||
if ( des === '*' || desHostnameDone.hasOwnProperty(des) ) { continue; }
|
if ( des === '*' || desHostnameDone.hasOwnProperty(des) ) { continue; }
|
||||||
const hnDetails = popupData.hostnameDict[des] || {};
|
const hnDetails = popupData.hostnameDict[des];
|
||||||
if ( allDomains.hasOwnProperty(hnDetails.domain) === false ) {
|
const { domain, counts } = hnDetails;
|
||||||
allDomains[hnDetails.domain] = false;
|
if ( allDomains.hasOwnProperty(domain) === false ) {
|
||||||
|
allDomains[domain] = false;
|
||||||
allDomainCount += 1;
|
allDomainCount += 1;
|
||||||
}
|
}
|
||||||
if ( hnDetails.allowCount !== 0 ) {
|
if ( gtz(counts.allowed.any) ) {
|
||||||
if ( allDomains[hnDetails.domain] === false ) {
|
if ( allDomains[domain] === false ) {
|
||||||
allDomains[hnDetails.domain] = true;
|
allDomains[domain] = true;
|
||||||
touchedDomainCount += 1;
|
touchedDomainCount += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -462,9 +523,16 @@ const renderPopup = function() {
|
||||||
uDom.nodeFromId('gotoPick').classList.toggle('enabled', canElementPicker);
|
uDom.nodeFromId('gotoPick').classList.toggle('enabled', canElementPicker);
|
||||||
uDom.nodeFromId('gotoZap').classList.toggle('enabled', canElementPicker);
|
uDom.nodeFromId('gotoZap').classList.toggle('enabled', canElementPicker);
|
||||||
|
|
||||||
let blocked = popupData.pageBlockedRequestCount,
|
let blocked, total;
|
||||||
total = popupData.pageAllowedRequestCount + blocked,
|
if ( popupData.pageCounts !== undefined ) {
|
||||||
text;
|
const counts = popupData.pageCounts;
|
||||||
|
blocked = counts.blocked.any;
|
||||||
|
total = blocked + counts.allowed.any;
|
||||||
|
} else {
|
||||||
|
blocked = 0;
|
||||||
|
total = 0;
|
||||||
|
}
|
||||||
|
let text;
|
||||||
if ( total === 0 ) {
|
if ( total === 0 ) {
|
||||||
text = formatNumber(0);
|
text = formatNumber(0);
|
||||||
} else {
|
} else {
|
||||||
|
@ -855,7 +923,7 @@ const setFirewallRule = async function(src, des, type, action, persist) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cachePopupData(response);
|
cachePopupData(response);
|
||||||
updateAllFirewallCells();
|
updateAllFirewallCells(false);
|
||||||
hashFromPopupData();
|
hashFromPopupData();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1046,7 +1114,7 @@ const revertFirewallRules = async function() {
|
||||||
tabId: popupData.tabId,
|
tabId: popupData.tabId,
|
||||||
});
|
});
|
||||||
cachePopupData(response);
|
cachePopupData(response);
|
||||||
updateAllFirewallCells();
|
updateAllFirewallCells(false);
|
||||||
updateHnSwitches();
|
updateHnSwitches();
|
||||||
hashFromPopupData();
|
hashFromPopupData();
|
||||||
};
|
};
|
||||||
|
@ -1077,8 +1145,9 @@ const toggleHostnameSwitch = async function(ev) {
|
||||||
});
|
});
|
||||||
|
|
||||||
cachePopupData(response);
|
cachePopupData(response);
|
||||||
updateAllFirewallCells();
|
|
||||||
hashFromPopupData();
|
hashFromPopupData();
|
||||||
|
|
||||||
|
document.body.classList.toggle('needSave', popupData.matrixIsDirty === true);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
|
@ -360,7 +360,7 @@
|
||||||
// filtering pane.
|
// filtering pane.
|
||||||
const pageStore = µb.pageStoreFromTabId(openerTabId);
|
const pageStore = µb.pageStoreFromTabId(openerTabId);
|
||||||
if ( pageStore ) {
|
if ( pageStore ) {
|
||||||
pageStore.journalAddRequest(fctxt.getHostname(), result);
|
pageStore.journalAddRequest(fctxt, result);
|
||||||
pageStore.popupBlockedCount += 1;
|
pageStore.popupBlockedCount += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1038,14 +1038,15 @@ vAPI.tabs = new vAPI.Tabs();
|
||||||
let badge = '';
|
let badge = '';
|
||||||
let color = '#666';
|
let color = '#666';
|
||||||
|
|
||||||
let pageStore = µb.pageStoreFromTabId(tabId);
|
const pageStore = µb.pageStoreFromTabId(tabId);
|
||||||
if ( pageStore !== null ) {
|
if ( pageStore !== null ) {
|
||||||
state = pageStore.getNetFilteringSwitch() ? 1 : 0;
|
state = pageStore.getNetFilteringSwitch() ? 1 : 0;
|
||||||
if ( state === 1 ) {
|
if ( state === 1 ) {
|
||||||
if ( (parts & 0b0010) !== 0 && pageStore.perLoadBlockedRequestCount ) {
|
if ( (parts & 0b0010) !== 0 ) {
|
||||||
badge = µb.formatCount(
|
const blockCount = pageStore.counts.blocked.any;
|
||||||
pageStore.perLoadBlockedRequestCount
|
if ( blockCount !== 0 ) {
|
||||||
);
|
badge = µb.formatCount(blockCount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ( (parts & 0b0100) !== 0 ) {
|
if ( (parts & 0b0100) !== 0 ) {
|
||||||
color = computeBadgeColor(
|
color = computeBadgeColor(
|
||||||
|
@ -1071,7 +1072,7 @@ vAPI.tabs = new vAPI.Tabs();
|
||||||
return function(tabId, newParts = 0b0111) {
|
return function(tabId, newParts = 0b0111) {
|
||||||
if ( typeof tabId !== 'number' ) { return; }
|
if ( typeof tabId !== 'number' ) { return; }
|
||||||
if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; }
|
if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; }
|
||||||
let currentParts = tabIdToDetails.get(tabId);
|
const currentParts = tabIdToDetails.get(tabId);
|
||||||
if ( currentParts === newParts ) { return; }
|
if ( currentParts === newParts ) { return; }
|
||||||
if ( currentParts === undefined ) {
|
if ( currentParts === undefined ) {
|
||||||
self.requestIdleCallback(
|
self.requestIdleCallback(
|
||||||
|
|
|
@ -88,7 +88,7 @@ const onBeforeRequest = function(details) {
|
||||||
|
|
||||||
const result = pageStore.filterRequest(fctxt);
|
const result = pageStore.filterRequest(fctxt);
|
||||||
|
|
||||||
pageStore.journalAddRequest(fctxt.getHostname(), result);
|
pageStore.journalAddRequest(fctxt, result);
|
||||||
|
|
||||||
if ( µb.logger.enabled ) {
|
if ( µb.logger.enabled ) {
|
||||||
fctxt.setRealm('network').toLogger();
|
fctxt.setRealm('network').toLogger();
|
||||||
|
@ -208,7 +208,7 @@ const onBeforeRootFrameRequest = function(fctxt) {
|
||||||
const pageStore = µb.bindTabToPageStore(fctxt.tabId, 'beforeRequest');
|
const pageStore = µb.bindTabToPageStore(fctxt.tabId, 'beforeRequest');
|
||||||
if ( pageStore !== null ) {
|
if ( pageStore !== null ) {
|
||||||
pageStore.journalAddRootFrame('uncommitted', requestURL);
|
pageStore.journalAddRootFrame('uncommitted', requestURL);
|
||||||
pageStore.journalAddRequest(requestHostname, result);
|
pageStore.journalAddRequest(fctxt, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( loggerEnabled ) {
|
if ( loggerEnabled ) {
|
||||||
|
@ -400,7 +400,7 @@ const onBeforeBehindTheSceneRequest = function(fctxt) {
|
||||||
gcTimer = vAPI.setTimeout(gc, 30011);
|
gcTimer = vAPI.setTimeout(gc, 30011);
|
||||||
}
|
}
|
||||||
for ( const pageStore of pageStores ) {
|
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();
|
fctxt.setRealm('network').toLogger();
|
||||||
}
|
}
|
||||||
if ( result === 1 ) {
|
if ( result === 1 ) {
|
||||||
pageStore.journalAddRequest(fctxt.getHostname(), 1);
|
pageStore.journalAddRequest(fctxt, 1);
|
||||||
return { cancel: true };
|
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="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="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="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-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 data-i18n="popup3pFrameRulePrompt"></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>
|
</div>
|
||||||
|
|
||||||
<div id="templates" style="display: none">
|
<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="actionSelector"><span id="dynaAllow"></span><span id="dynaNoop"></span><span id="dynaBlock"></span><span id="dynaCounts"></span></div>
|
||||||
<div id="hotspotTip"></div>
|
<div id="hotspotTip"></div>
|
||||||
<div id="tooltip"></div>
|
<div id="tooltip"></div>
|
||||||
|
|
Loading…
Reference in New Issue