Raymond Hill 6 years ago
parent cb2ab59297
commit 2256552899
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2

@ -31,6 +31,9 @@
display: inline-block; display: inline-block;
} }
body {
font-size: 14px;
}
body[dir="ltr"] { body[dir="ltr"] {
direction: ltr; direction: ltr;
} }

@ -92,7 +92,7 @@ input:focus {
width: 4.6em; width: 4.6em;
} }
#content table > colgroup > col:nth-of-type(2) { #content table > colgroup > col:nth-of-type(2) {
width: 2.2em; width: 30%;
} }
#content table > colgroup > col:nth-of-type(3) { #content table > colgroup > col:nth-of-type(3) {
width: 2.2em; width: 2.2em;
@ -101,7 +101,7 @@ input:focus {
width: 5.4em; width: 5.4em;
} }
#content table > colgroup > col:nth-of-type(5) { #content table > colgroup > col:nth-of-type(5) {
width: calc(100% - 14.4em); width: calc(100% - 4.6em - 30% - 2.2em - 5.4em);
} }
#content table tr { #content table tr {
background-color: #fafafa; background-color: #fafafa;
@ -151,13 +151,8 @@ body.compactView #content tr:not(.vExpanded) td {
text-align: right; text-align: right;
white-space: nowrap; white-space: nowrap;
} }
#content table tr td:nth-of-type(2) { #content table tr td:nth-of-type(2):not([colspan]) {
text-align: center; text-align: right;
white-space: nowrap;
}
#content table tr.tab_bts > td:nth-of-type(2):before {
content: '\f070';
font: 1em FontAwesome;
} }
#content table tr.tab:not(.canMtx) { #content table tr.tab:not(.canMtx) {
opacity: 0.3; opacity: 0.3;
@ -165,17 +160,8 @@ body.compactView #content tr:not(.vExpanded) td {
#content table tr.tab:not(.canMtx):hover { #content table tr.tab:not(.canMtx):hover {
opacity: 0.7; opacity: 0.7;
} }
#content table tr.tab:not(.canMtx) > td:nth-of-type(2):before {
content: '\f00d';
font: 1em FontAwesome;
}
body:not(.popupOn) #content table tr.canMtx td:nth-of-type(2) {
cursor: zoom-in;
}
body:not(.popupOn) #content table tr.canMtx td:nth-of-type(2):hover {
background: #ccc;
}
#content table tr.cat_net td:nth-of-type(3) { #content table tr.cat_net td:nth-of-type(3) {
cursor: pointer;
font: 12px monospace; font: 12px monospace;
text-align: center; text-align: center;
white-space: nowrap; white-space: nowrap;
@ -189,45 +175,232 @@ body:not(.popupOn) #content table tr.canMtx td:nth-of-type(2):hover {
opacity: 1; opacity: 1;
} }
#popupContainer { .modalDialog {
background: white; align-items: center;
border: 1px solid gray; background-color: rgba(0, 0, 0, 0.5);
display: none; display: flex;
overflow: hidden; height: 100vh;
justify-content: center;
left: 0;
position: fixed; position: fixed;
right: 1em;
top: 0; top: 0;
z-index: 200; width: 100vw;
z-index: 1000;
} }
body.popupOn #popupContainer { .modalDialog > .dialog {
display: block; background-color: white;
font: 15px httpsb,sans-serif;
min-width: fit-content;
padding: 0.5em;
width: 90%;
}
#ruleEditor section {
display: flex;
}
.scopeWidget {
line-height: 2.5em;
margin-bottom: 0.5em;
}
#specificScope, .ruleCell:nth-of-type(1) {
flex-grow: 1;
}
#globalScope, .ruleCell:nth-of-type(2) {
width: 4em;
} }
#popupContainer > div { .ruleEditorToolbar {
background: #888; display: flex;
flex-direction: column;
justify-content: space-around;
margin-left: 0.5em;
}
.ruleEditorToolbar button {
background-color: white;
border: 0; border: 0;
color: black;
cursor: pointer;
margin: 0;
padding: 0.2em;
position: relative;
} }
#popupContainer > div { .ruleEditorToolbar button:hover {
text-align: right; background-color: #eee;
} }
#popupContainer > div > span { .ruleEditorToolbar button.disabled {
color: #ccc; color: #ccc;
}
.ruleEditorToolbar button.fa {
font: 1.7em FontAwesome;
min-width: 1.5em;
}
.ruleEditorToolbar button > span.badge {
background-color: rgba(240,240,240,0.75);
bottom: 1px;
color: #000;
display: inline-block;
font-family: sans-serif;
font-size: 40%;
padding: 1px 1px;
pointer-events: none;
position: absolute;
right: 1px;
}
.ruleEditorToolbar button.disabled > span.badge {
display: none;
}
button.scopeRel {
color: #24c;
}
body[data-scope="*"] button.scopeRel {
color: #000;
}
.ruleWidgets {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.ruleRow {
display: flex;
line-height: 2em;
margin-top: 1px;
}
.ruleCell {
background-color: #eee;
border: 1px dotted rgba(0,0,0,0.2);
cursor: pointer; cursor: pointer;
display: inline-block; display: inline-block;
font: 14px FontAwesome; margin-left: 1px;
padding: 3px; padding: 1px;
position: relative;
}
.ruleCell:hover {
border-style: solid;
}
.ruleCell:nth-of-type(1) {
margin-left: 0;
text-align: right;
}
.ruleCell:nth-of-type(2) {
text-align: center;
} }
#popupContainer > div > span:hover { .ruleCell[data-tcolor="1"] {
border-color: #debaba;
color: black;
background-color: #f8d0d0;
}
#ruleEditor.colorblind .ruleCell[data-tcolor="1"] {
border-color: rgba(0, 19, 110, 0.3);
color: black;
background-color: rgba(0, 19, 110, 0.2);
}
.ruleCell[data-tcolor="2"] {
border-color: #bad6ba;
color: black;
background-color: #d0f0d0;
}
#ruleEditor.colorblind .ruleCell[data-tcolor="2"] {
border-color: rgba(255, 194, 57, 0.3);
color: black;
background-color: rgba(255, 194, 57, 0.2);
}
.ruleCell[data-tcolor="129"] {
color: white;
background-color: #c00;
}
#ruleEditor.colorblind .ruleCell[data-tcolor="129"] {
color: white;
background-color: rgb(0, 19, 110);
}
.ruleCell[data-tcolor="130"] {
color: white; color: white;
background-color: #080;
} }
#popupContainer > iframe { #ruleEditor.colorblind .ruleCell[data-tcolor="130"] {
border-color: rgb(255, 194, 57);
color: black;
background-color: rgb(255, 194, 57);
}
.ruleCell[data-pcolor="129"] {
background-image: url('../img/permanent-black-small.png');
background-repeat: no-repeat;
background-position: -1px -1px;
}
#ruleEditor.colorblind .ruleCell[data-pcolor="129"] {
background-image: url('../img/permanent-black-small-cb.png');
}
.ruleCell[data-pcolor="130"] {
background-image: url('../img/permanent-white-small.png');
background-repeat: no-repeat;
background-position: -1px -1px;
}
#ruleEditor.colorblind .ruleCell[data-pcolor="130"] {
background-image: url('../img/permanent-white-small-cb.png');
}
#ruleActionPicker {
border: 0; border: 0;
height: 100%;
left: 0;
margin: 0;
padding: 0; padding: 0;
position: absolute;
top: 0;
width: 100%;
z-index: 10;
}
.allowRule, .blockRule {
margin: 0; margin: 0;
border: 0;
padding: 0;
position: absolute;
left: 0;
width: 100%; width: 100%;
height: 50%;
background: transparent;
} }
#popupContainer.hide { .allowRule {
width: 6em !important; top: 0;
} }
#popupContainer.hide > iframe { .blockRule {
display: none; top: 50%;
}
.ruleCell[data-tcolor="1"] .allowRule:hover {
background-color: #080;
opacity: 0.25;
}
.ruleCell[data-tcolor="1"] .blockRule:hover {
background-color: #c00;
opacity: 0.25;
}
.ruleCell[data-tcolor="2"] .allowRule:hover {
background-color: #080;
opacity: 0.25;
}
.ruleCell[data-tcolor="2"] .blockRule:hover {
background-color: #c00;
opacity: 0.25;
}
.ruleCell[data-tcolor="129"] .allowRule:hover {
background-color: transparent;
}
.ruleCell[data-tcolor="129"] .blockRule:hover {
background-color: transparent;
}
.ruleCell[data-pcolor="130"] .allowRule:hover {
background-color: transparent;
}
.ruleCell[data-pcolor="130"] .blockRule:hover {
background-color: transparent;
}
#ruleEditor.colorblind .ruleCell[data-tcolor="1"] .allowRule:hover,
#ruleEditor.colorblind .ruleCell[data-tcolor="2"] .allowRule:hover {
background-color: rgb(255, 194, 57);
opacity: 0.6;
}
#ruleEditor.colorblind .ruleCell[data-tcolor="1"] .blockRule:hover,
#ruleEditor.colorblind .ruleCell[data-tcolor="2"] .blockRule:hover {
background-color: rgb(0, 19, 110);
opacity: 0.4;
} }

@ -1,3 +1,24 @@
/*******************************************************************************
uMatrix - a browser extension to black/white list requests.
Copyright (C) 2014-present Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uMatrix
*/
body { body {
background-color: white; background-color: white;
border: 0; border: 0;
@ -301,76 +322,13 @@ button.disabled > span.badge {
display: block; display: block;
} }
body .toolbar .scope {
background-color: #ccc;
border: 1px solid #ccc;
box-sizing: content-box;
-moz-box-sizing: content-box;
display: inline-flex;
color: white;
margin: 0;
padding: 1px;
cursor: pointer;
}
body .toolbar .scope > span {
align-items: center;
display: inline-flex;
}
body .toolbar .scope > span > span {
pointer-events: none;
white-space: nowrap;
}
body .toolbar #specificScope {
direction: ltr;
justify-content: flex-end;
width: 16em;
}
body .toolbar #specificScope.on {
background-color: #24c;
border-color: #24c;
}
body .toolbar #specificScope > span {
background-color: #ccc;
justify-content: flex-end;
}
body .toolbar #specificScope > span.on {
background-color: #24c;
}
body .toolbar #specificScope > span:first-of-type:not(.on):hover,
body .toolbar #specificScope > span:first-of-type:not(.on):hover ~ span:not(.on),
body .toolbar #specificScope > span:not(.on) + span:not(.on):hover,
body .toolbar #specificScope > span:not(.on) + span:not(.on):hover ~ span:not(.on) {
background-color: #999;
}
body .toolbar #specificScope > span:first-of-type:not(.on):hover ~ span,
body .toolbar #specificScope > span:not(.on) + span:not(.on):hover ~ span,
body .toolbar #specificScope > span.on + span:hover,
body .toolbar #specificScope > span.on + span:hover ~ span {
background-color: #139;
}
body .toolbar #specificScope > span:first-of-type {
flex: 1;
}
body .toolbar #globalScope {
justify-content: center;
margin-left: 1px;
width: 1.6em;
}
body .toolbar #globalScope.on {
background-color: #000;
border-color: #000;
}
body .toolbar #globalScope:not(.on):hover {
background-color: #999;
border-color: #999;
}
body .toolbar .scopeRel { body .toolbar .scopeRel {
color: #24c; color: #24c;
} }
body.globalScope .toolbar .scopeRel { body[data-scope="*"] .toolbar .scopeRel {
color: #000; color: #000;
} }
body.globalScope .toolbar .scopeRel.disabled { body[data-scope="*"] .toolbar .scopeRel.disabled {
color: #ccc; color: #ccc;
} }

@ -0,0 +1,84 @@
/*******************************************************************************
uMatrix - a browser extension to black/white list requests.
Copyright (C) 2018-present Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uMatrix
*/
.scope {
background-color: #ccc;
border: 1px solid #ccc;
box-sizing: content-box;
-moz-box-sizing: content-box;
display: inline-flex;
color: white;
margin: 0;
padding: 1px;
cursor: pointer;
}
.scope > span {
align-items: center;
display: inline-flex;
}
.scope > span > span {
pointer-events: none;
white-space: nowrap;
}
#specificScope {
direction: ltr;
justify-content: flex-end;
width: 16em;
}
#specificScope.on {
background-color: #24c;
border-color: #24c;
}
#specificScope > span {
background-color: #ccc;
justify-content: flex-end;
}
#specificScope > span.on {
background-color: #24c;
}
#specificScope > span:first-of-type:not(.on):hover,
#specificScope > span:first-of-type:not(.on):hover ~ span:not(.on),
#specificScope > span:not(.on) + span:not(.on):hover,
#specificScope > span:not(.on) + span:not(.on):hover ~ span:not(.on) {
background-color: #999;
}
#specificScope > span:first-of-type:not(.on):hover ~ span,
#specificScope > span:not(.on) + span:not(.on):hover ~ span,
#specificScope > span.on + span:hover,
#specificScope > span.on + span:hover ~ span {
background-color: #139;
}
#specificScope > span:first-of-type {
flex: 1;
}
#globalScope {
justify-content: center;
margin-left: 1px;
width: 1.6em;
}
#globalScope.on {
background-color: #000;
border-color: #000;
}
#globalScope:not(.on):hover {
background-color: #999;
border-color: #999;
}

@ -1,7 +1,7 @@
/******************************************************************************* /*******************************************************************************
uMatrix - a browser extension to black/white list requests. uMatrix - a browser extension to black/white list requests.
Copyright (C) 2013-2018 Raymond Hill Copyright (C) 2013-present Raymond Hill
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -101,11 +101,9 @@ api.fetchText = function(url, onLoad, onError) {
var onErrorReceived = function() { var onErrorReceived = function() {
this.onload = this.onerror = this.ontimeout = null; this.onload = this.onerror = this.ontimeout = null;
µMatrix.logger.writeOne( µMatrix.logger.writeOne({
'', error: errorCantConnectTo.replace('{{url}}', actualUrl)
'error', });
errorCantConnectTo.replace('{{url}}', actualUrl)
);
onError.call(null, { url: url, content: '' }); onError.call(null, { url: url, content: '' });
}; };

@ -1,7 +1,7 @@
/******************************************************************************* /*******************************************************************************
uMatrix - a Chromium browser extension to black/white list requests. uMatrix - a browser extension to black/white list requests.
Copyright (C) 2015 Raymond Hill Copyright (C) 2015-present Raymond Hill
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,14 +19,12 @@
Home: https://github.com/gorhill/uMatrix Home: https://github.com/gorhill/uMatrix
*/ */
/* global µMatrix */ 'use strict';
/******************************************************************************/ /******************************************************************************/
(function() { (function() {
'use strict';
/******************************************************************************/ /******************************************************************************/
// Browser data jobs // Browser data jobs
@ -50,7 +48,7 @@ var clearCache = function() {
µm.browserCacheClearedCounter++; µm.browserCacheClearedCounter++;
// TODO: i18n // TODO: i18n
µm.logger.writeOne('', 'info', vAPI.i18n('loggerEntryBrowserCacheCleared')); µm.logger.writeOne({ info: vAPI.i18n('loggerEntryBrowserCacheCleared') });
//console.debug('clearBrowserCacheCallback()> vAPI.browserData.clearCache() called'); //console.debug('clearBrowserCacheCallback()> vAPI.browserData.clearCache() called');
}; };

@ -1,7 +1,7 @@
/******************************************************************************* /*******************************************************************************
uMatrix - a browser extension to black/white list requests. uMatrix - a browser extension to black/white list requests.
Copyright (C) 2013-2018 Raymond Hill Copyright (C) 2013-present Raymond Hill
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -210,7 +210,7 @@ var recordPageCookie = (function() {
for ( let cookieKey of qentry[1] ) { for ( let cookieKey of qentry[1] ) {
let cookieEntry = cookieDict.get(cookieKey); let cookieEntry = cookieDict.get(cookieKey);
if ( cookieEntry === undefined ) { continue; } if ( cookieEntry === undefined ) { continue; }
let block = µm.mustBlock( let blocked = µm.mustBlock(
pageStore.pageHostname, pageStore.pageHostname,
cookieEntry.hostname, cookieEntry.hostname,
'cookie' 'cookie'
@ -224,17 +224,17 @@ var recordPageCookie = (function() {
cookieLogEntryBuilder[4] = cookieLogEntryBuilder[4] =
encodeURIComponent(cookieEntry.name); encodeURIComponent(cookieEntry.name);
let cookieURL = cookieLogEntryBuilder.join(''); let cookieURL = cookieLogEntryBuilder.join('');
pageStore.recordRequest('cookie', cookieURL, block); pageStore.recordRequest('cookie', cookieURL, blocked);
µm.logger.writeOne( µm.logger.writeOne({
pageStore.tabId, tabId: pageStore.tabId,
'net', srcHn: pageStore.pageHostname,
pageStore.pageHostname, desHn: cookieEntry.hostname,
cookieURL, desURL: cookieURL,
'cookie', type: 'cookie',
block blocked
); });
cookieEntry.usedOn.add(pageStore.pageHostname); cookieEntry.usedOn.add(pageStore.pageHostname);
if ( !block ) { continue; } if ( !blocked ) { continue; }
if ( µm.userSettings.deleteCookies ) { if ( µm.userSettings.deleteCookies ) {
removeCookieAsync(cookieKey); removeCookieAsync(cookieKey);
} }
@ -288,9 +288,8 @@ var removeCookieAsync = function(cookieKey) {
var chromeCookieRemove = function(cookieEntry, name) { var chromeCookieRemove = function(cookieEntry, name) {
var url = cookieURLFromCookieEntry(cookieEntry); var url = cookieURLFromCookieEntry(cookieEntry);
if ( url === '' ) { if ( url === '' ) { return; }
return;
}
var sessionCookieKey = cookieKeyFromCookieURL(url, 'session', name); var sessionCookieKey = cookieKeyFromCookieURL(url, 'session', name);
var persistCookieKey = cookieKeyFromCookieURL(url, 'persistent', name); var persistCookieKey = cookieKeyFromCookieURL(url, 'persistent', name);
var callback = function(details) { var callback = function(details) {
@ -300,13 +299,17 @@ var chromeCookieRemove = function(cookieEntry, name) {
if ( success ) { if ( success ) {
µm.cookieRemovedCounter += 1; µm.cookieRemovedCounter += 1;
} }
µm.logger.writeOne('', 'info', 'cookie', template.replace('{{value}}', sessionCookieKey)); µm.logger.writeOne({
info: template.replace('{{value}}', sessionCookieKey)
});
} }
if ( removeCookieFromDict(persistCookieKey) ) { if ( removeCookieFromDict(persistCookieKey) ) {
if ( success ) { if ( success ) {
µm.cookieRemovedCounter += 1; µm.cookieRemovedCounter += 1;
} }
µm.logger.writeOne('', 'info', 'cookie', template.replace('{{value}}', persistCookieKey)); µm.logger.writeOne({
info: template.replace('{{value}}', persistCookieKey)
});
} }
}; };
@ -549,7 +552,10 @@ vAPI.cookies.onChanged = (function() {
vAPI.cookies.onRemoved = function(cookie) { vAPI.cookies.onRemoved = function(cookie) {
var cookieKey = cookieKeyFromCookie(cookie); var cookieKey = cookieKeyFromCookie(cookie);
if ( removeCookieFromDict(cookieKey) ) { if ( removeCookieFromDict(cookieKey) ) {
µm.logger.writeOne('', 'info', 'cookie', i18nCookieDeleteSuccess.replace('{{value}}', cookieKey)); µm.logger.writeOne({
info: i18nCookieDeleteSuccess.replace('{{value}}', cookieKey),
prettify: 'cookie'
});
} }
}; };
@ -560,7 +566,10 @@ vAPI.cookies.onRemoved = function(cookie) {
vAPI.cookies.onAllRemoved = function() { vAPI.cookies.onAllRemoved = function() {
for ( var cookieKey of cookieDict.keys() ) { for ( var cookieKey of cookieDict.keys() ) {
if ( removeCookieFromDict(cookieKey) ) { if ( removeCookieFromDict(cookieKey) ) {
µm.logger.writeOne('', 'info', 'cookie', i18nCookieDeleteSuccess.replace('{{value}}', cookieKey)); µm.logger.writeOne({
info: i18nCookieDeleteSuccess.replace('{{value}}', cookieKey),
prettify: 'cookie'
});
} }
} }
}; };

@ -1,7 +1,7 @@
/******************************************************************************* /*******************************************************************************
uMatrix - a browser extension to benchmark browser session. uMatrix - a browser extension to benchmark browser session.
Copyright (C) 2015-2018 Raymond Hill Copyright (C) 2015-present Raymond Hill
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,7 +19,7 @@
Home: https://github.com/gorhill/sessbench Home: https://github.com/gorhill/sessbench
*/ */
/* global uDom */ /* global publicSuffixList, uDom, uMatrixScopeWidget */
'use strict'; 'use strict';
@ -32,8 +32,8 @@
var tbody = document.querySelector('#content tbody'); var tbody = document.querySelector('#content tbody');
var trJunkyard = []; var trJunkyard = [];
var tdJunkyard = []; var tdJunkyard = [];
var firstVarDataCol = 2; // currently, column 2 (0-based index) var firstVarDataCol = 1; // currently, column 2 (0-based index)
var lastVarDataIndex = 3; // currently, d0-d3 var lastVarDataIndex = 4; // currently, 5 columns at most
var maxEntries = 0; var maxEntries = 0;
var noTabId = ''; var noTabId = '';
var pageStores = new Map(); var pageStores = new Map();
@ -41,7 +41,6 @@ var pageStoresToken;
var ownerId = Date.now(); var ownerId = Date.now();
var emphasizeTemplate = document.querySelector('#emphasizeTemplate > span'); var emphasizeTemplate = document.querySelector('#emphasizeTemplate > span');
var hiddenTemplate = document.querySelector('#hiddenTemplate > span');
var prettyRequestTypes = { var prettyRequestTypes = {
'main_frame': 'doc', 'main_frame': 'doc',
@ -67,14 +66,21 @@ document.getElementById('content').style.setProperty(
/******************************************************************************/ /******************************************************************************/
var classNameFromTabId = function(tabId) { let removeChildren = function(node) {
if ( tabId === noTabId ) { while ( node.firstChild ) {
return 'tab_bts'; node.removeChild(node.firstChild);
} }
if ( tabId > 0 ) { };
return 'tab_' + tabId;
let removeSelf = function(node) {
let parent = node && node.parentNode;
if ( parent ) {
parent.removeChild(node);
} }
return ''; };
let prependChild = function(parent, child) {
parent.insertBefore(child, parent.firstElementChild);
}; };
/******************************************************************************/ /******************************************************************************/
@ -158,21 +164,21 @@ var createCellAt = function(tr, index) {
/******************************************************************************/ /******************************************************************************/
var createRow = function(layout) { var createRow = function(layout) {
var tr = trJunkyard.pop(); let tr = trJunkyard.pop();
if ( tr ) { if ( tr ) {
tr.className = ''; tr.className = '';
} else { } else {
tr = document.createElement('tr'); tr = document.createElement('tr');
} }
for ( var index = 0; index < firstVarDataCol; index++ ) { let index;
for ( index = 0; index < firstVarDataCol; index++ ) {
createCellAt(tr, index); createCellAt(tr, index);
} }
var i = 1, span = 1, td; let i = 1, span = 1;
let td;
for (;;) { for (;;) {
td = createCellAt(tr, index); td = createCellAt(tr, index);
if ( i === lastVarDataIndex ) { if ( i === lastVarDataIndex ) { break; }
break;
}
if ( layout.charAt(i) !== '1' ) { if ( layout.charAt(i) !== '1' ) {
span += 1; span += 1;
} else { } else {
@ -188,22 +194,18 @@ var createRow = function(layout) {
td.setAttribute('colspan', span); td.setAttribute('colspan', span);
} }
index += 1; index += 1;
while ( (td = tr.cells[index]) ) { for (;;) {
td = tr.cells[index];
if ( !td ) { break; }
tdJunkyard.push(tr.removeChild(td)); tdJunkyard.push(tr.removeChild(td));
} }
tr.removeAttribute('data-srchn');
tr.removeAttribute('data-deshn');
return tr; return tr;
}; };
/******************************************************************************/ /******************************************************************************/
var createHiddenTextNode = function(text) {
var node = hiddenTemplate.cloneNode(true);
node.textContent = text;
return node;
};
/******************************************************************************/
var padTo2 = function(v) { var padTo2 = function(v) {
return v < 10 ? '0' + v : v; return v < 10 ? '0' + v : v;
}; };
@ -223,66 +225,84 @@ var createGap = function(tabId, url) {
/******************************************************************************/ /******************************************************************************/
var renderLogEntry = function(entry) { var renderLogEntry = function(entry) {
var tr; let details;
var fvdc = firstVarDataCol; try {
details = JSON.parse(entry.details);
} catch(ex) {
console.error(ex);
}
if ( details instanceof Object === false ) { return; }
let tr;
let fvdc = firstVarDataCol;
switch ( entry.cat ) { if ( details.error !== undefined ) {
case 'error':
case 'info':
tr = createRow('1'); tr = createRow('1');
if ( entry.d0 === 'cookie' ) { tr.classList.add('cat_error');
tr.cells[fvdc].appendChild(emphasizeCookie(entry.d1)); tr.cells[fvdc].textContent = details.error;
} else if ( details.info !== undefined ) {
tr = createRow('1');
tr.classList.add('cat_info');
if ( details.prettify === 'cookie' ) {
tr.cells[fvdc].appendChild(emphasizeCookie(details.info));
} else { } else {
tr.cells[fvdc].textContent = entry.d0; tr.cells[fvdc].textContent = details.info;
} }
break; } else if ( details.srcHn !== undefined && details.desHn !== undefined ) {
tr = createRow('1111');
case 'net':
tr = createRow('111');
tr.classList.add('canMtx'); tr.classList.add('canMtx');
tr.classList.add('cat_net');
tr.setAttribute('data-srchn', details.srcHn);
tr.setAttribute('data-deshn', details.desHn);
tr.setAttribute('data-type', details.type);
// If the request is that of a root frame, insert a gap in the table // If the request is that of a root frame, insert a gap in the table
// in order to visually separate entries for different documents. // in order to visually separate entries for different documents.
if ( entry.d2 === 'doc' && entry.tab !== noTabId ) { if ( details.type === 'doc' && details.tabId !== noTabId ) {
createGap(entry.tab, entry.d1); createGap(details.tabId, details.desURL);
} }
if ( entry.d3 ) { tr.cells[fvdc+0].textContent = details.srcHn;
if ( details.blocked ) {
tr.classList.add('blocked'); tr.classList.add('blocked');
tr.cells[fvdc].textContent = '--'; tr.cells[fvdc+1].textContent = '--';
} else { } else {
tr.cells[fvdc].textContent = ''; tr.cells[fvdc+1].textContent = '';
} }
tr.cells[fvdc+1].textContent = (prettyRequestTypes[entry.d2] || entry.d2); tr.cells[fvdc+2].textContent = (prettyRequestTypes[details.type] || details.type);
if ( dontEmphasizeSet.has(entry.d2) ) { if ( dontEmphasizeSet.has(details.type) ) {
tr.cells[fvdc+2].textContent = entry.d1; tr.cells[fvdc+3].textContent = details.desURL;
} else if ( entry.d2 === 'cookie' ) {
tr.cells[fvdc+2].appendChild(emphasizeCookie(entry.d1));
} else { } else {
tr.cells[fvdc+2].appendChild(emphasizeHostname(entry.d1)); tr.cells[fvdc+3].appendChild(emphasizeHostname(details.desURL));
} }
break; } else if ( details.header ) {
tr = createRow('1111');
default: tr.classList.add('canMtx');
tr.classList.add('cat_net');
tr.cells[fvdc+0].textContent = details.srcHn || '';
if ( details.change === -1 ) {
tr.classList.add('blocked');
tr.cells[fvdc+1].textContent = '--';
} else {
tr.cells[fvdc+1].textContent = '';
}
tr.cells[fvdc+2].textContent = details.header.name;
tr.cells[fvdc+3].textContent = details.header.value;
} else {
tr = createRow('1'); tr = createRow('1');
tr.cells[fvdc].textContent = entry.d0; tr.cells[fvdc].textContent = 'huh?';
break;
} }
// Fields common to all rows. // Fields common to all rows.
var time = logDate; let time = logDate;
time.setTime(entry.tstamp - logDateTimezoneOffset); time.setTime(entry.tstamp - logDateTimezoneOffset);
tr.cells[0].textContent = padTo2(time.getUTCHours()) + ':' + tr.cells[0].textContent = padTo2(time.getUTCHours()) + ':' +
padTo2(time.getUTCMinutes()) + ':' + padTo2(time.getUTCMinutes()) + ':' +
padTo2(time.getSeconds()); padTo2(time.getSeconds());
if ( entry.tab ) { if ( details.tabId ) {
tr.classList.add('tab'); tr.classList.add('tab');
tr.classList.add(classNameFromTabId(entry.tab)); tr.setAttribute('data-tabid', details.tabId);
if ( entry.tab === noTabId ) { } else {
tr.cells[1].appendChild(createHiddenTextNode('bts')); tr.removeAttribute('data-tabid');
}
}
if ( entry.cat !== '' ) {
tr.classList.add('cat_' + entry.cat);
} }
rowFilterer.filterOne(tr, true); rowFilterer.filterOne(tr, true);
@ -297,18 +317,13 @@ var logDate = new Date(),
/******************************************************************************/ /******************************************************************************/
var renderLogEntries = function(response) { var renderLogEntries = function(response) {
var entries = response.entries; let entries = response.entries;
if ( entries.length === 0 ) { return; } if ( entries.length === 0 ) { return; }
// Preserve scroll position // Preserve scroll position
var height = tbody.offsetHeight; let height = tbody.offsetHeight;
var n = entries.length; for ( let i = 0, n = entries.length; i < n; i++ ) {
var entry;
for ( var i = 0; i < n; i++ ) {
entry = entries[i];
// Unlikely, but it may happen
if ( entry.tab && pageStores.has(entry.tab) === false ) { continue; }
renderLogEntry(entries[i]); renderLogEntry(entries[i]);
} }
@ -317,10 +332,8 @@ var renderLogEntries = function(response) {
// dynamically refreshed pages. // dynamically refreshed pages.
truncateLog(maxEntries); truncateLog(maxEntries);
var yDelta = tbody.offsetHeight - height; let yDelta = tbody.offsetHeight - height;
if ( yDelta === 0 ) { if ( yDelta === 0 ) { return; }
return;
}
// Chromium: // Chromium:
// body.scrollTop = good value // body.scrollTop = good value
@ -333,7 +346,7 @@ var renderLogEntries = function(response) {
// Firefox: // Firefox:
// body.scrollTop = 0 // body.scrollTop = 0
// body.parentNode.scrollTop = good value // body.parentNode.scrollTop = good value
var parentNode = document.body.parentNode; let parentNode = document.body.parentNode;
if ( parentNode && parentNode.scrollTop !== 0 ) { if ( parentNode && parentNode.scrollTop !== 0 ) {
parentNode.scrollTop += yDelta; parentNode.scrollTop += yDelta;
} }
@ -357,10 +370,6 @@ var synchronizeTabIds = function(newPageStores) {
trs.removeClass('canMtx'); trs.removeClass('canMtx');
rowVoided = true; rowVoided = true;
} }
// Remove popup if it is currently bound to a removed tab.
if ( tabId === popupManager.tabId ) {
popupManager.toggleOff();
}
} }
var select = document.getElementById('pageSelector'); var select = document.getElementById('pageSelector');
@ -379,7 +388,7 @@ var synchronizeTabIds = function(newPageStores) {
select.appendChild(option); select.appendChild(option);
} }
option.textContent = newPageStores.get(tabId); option.textContent = newPageStores.get(tabId);
option.value = classNameFromTabId(tabId); option.value = tabId;
if ( option.value === selectValue ) { if ( option.value === selectValue ) {
option.setAttribute('selected', ''); option.setAttribute('selected', '');
} else { } else {
@ -487,22 +496,19 @@ var readLogBufferAsync = function() {
/******************************************************************************/ /******************************************************************************/
var pageSelectorChanged = function() { var pageSelectorChanged = function() {
var style = document.getElementById('tabFilterer'); let style = document.getElementById('tabFilterer');
var tabClass = document.getElementById('pageSelector').value; let tabId = document.getElementById('pageSelector').value;
var sheet = style.sheet; let sheet = style.sheet;
while ( sheet.cssRules.length !== 0 ) { while ( sheet.cssRules.length !== 0 ) {
sheet.deleteRule(0); sheet.deleteRule(0);
} }
if ( tabClass !== '' ) { if ( tabId.length !== 0 ) {
sheet.insertRule( sheet.insertRule(
'#content table tr:not(.' + tabClass + ') { display: none; }', '#content table tr:not([data-tabid="' + tabId + '"]) { display: none; }',
0 0
); );
} }
uDom('#refresh').toggleClass( uDom('#refresh').toggleClass('disabled', /^\d+$/.test(tabId) === false);
'disabled',
tabClass === '' || tabClass === 'tab_bts'
);
}; };
/******************************************************************************/ /******************************************************************************/
@ -678,6 +684,285 @@ var rowFilterer = (function() {
}; };
})(); })();
/******************************************************************************/
/******************************************************************************/
var ruleEditor = (function() {
let ruleEditorNode = document.getElementById('ruleEditor');
let ruleActionPicker = document.getElementById('ruleActionPicker');
let listeners = [];
let addListener = function(node, type, handler, bits) {
let options;
if ( typeof bits === 'number' && (bits & 0b11) !== 0 ) {
options = {};
if ( bits & 0b01 ) {
options.capture = true;
}
if ( bits & 0b10 ) {
options.passive = true;
}
}
listeners.push({ node, type, handler, options });
return node.addEventListener(type, handler, options);
};
let setup = function(details) {
ruleEditorNode.setAttribute('data-tabid', details.tabId);
ruleEditorNode.classList.toggle(
'colorblind',
details.options.colorBlindFriendly === true
);
// Initialize scope selector
let srcDn = domainFromSrcHostname(details.srcHn);
let scope = details.options.popupScopeLevel === '*' ?
'*' :
details.options.popupScopeLevel === 'domain' ?
srcDn :
details.srcHn;
uMatrixScopeWidget.init(srcDn, details.srcHn, scope, ruleEditorNode);
// Create rule rows
let ruleWidgets = ruleEditorNode.querySelector('.ruleWidgets');
removeChildren(ruleWidgets);
let ruleWidgetTemplate =
document.querySelector('#ruleRowTemplate .ruleRow');
// Rules: specific to desHn, from broadest to narrowest
let desHn = details.desHn;
let desDn = domainFromDesHostname(desHn);
for (;;) {
let ruleRow = ruleWidgetTemplate.cloneNode(true);
ruleRow.setAttribute('data-deshn', desHn);
ruleRow.children[0].textContent = desHn;
ruleRow.children[1].setAttribute('data-type', details.type);
if ( desHn === details.desHn ) {
ruleRow.children[1].textContent = '1';
}
prependChild(ruleWidgets, ruleRow);
if ( desHn === desDn ) { break; }
let pos = desHn.indexOf('.');
if ( pos === -1 ) { break; }
desHn = desHn.slice(pos + 1);
}
// Rules: 1st-party, if needed
if ( desDn === srcDn ) {
let ruleRow = ruleWidgetTemplate.cloneNode(true);
ruleRow.setAttribute('data-deshn', '1st-party');
ruleRow.children[0].textContent = '1st-party';
ruleRow.children[1].setAttribute('data-type', details.type);
prependChild(ruleWidgets, ruleRow);
}
// Rules: unspecific
{
let ruleRow = ruleWidgetTemplate.cloneNode(true);
ruleRow.setAttribute('data-deshn', '*');
ruleRow.children[0].textContent = 'all';
ruleRow.children[1].setAttribute('data-type', details.type);
ruleRow.children[1].textContent = details.type;
prependChild(ruleWidgets, ruleRow);
}
colorize();
addListener(ruleEditorNode, 'click', quitHandler, 0b01);
addListener(window, 'uMatrixScopeWidgetChange', scopeChangeHandler);
addListener(ruleWidgets, 'mouseenter', attachRulePicker, 0b11);
addListener(ruleWidgets, 'mouseleave', removeRulePicker, 0b11);
addListener(ruleActionPicker, 'click', rulePickerHandler, 0b11);
addListener(ruleEditorNode.querySelector('.buttonReload'), 'click', reload);
addListener(ruleEditorNode.querySelector('.buttonRevertScope'), 'click', revert);
addListener(ruleEditorNode.querySelector('.buttonPersist'), 'click', persist);
document.body.appendChild(ruleEditorNode);
};
let colorize = function() {
let srcHn = uMatrixScopeWidget.getScope();
let ruleCells = ruleEditorNode.querySelectorAll('.ruleCell');
let ruleParts = [];
for ( let ruleCell of ruleCells ) {
ruleParts.push(
srcHn,
ruleCell.closest('.ruleRow').getAttribute('data-deshn'),
ruleCell.getAttribute('data-type')
);
}
vAPI.messaging.send(
'default',
{ what: 'getCellColors', ruleParts },
response => {
let tColors = response.tColors,
pColors = response.pColors,
diffCount = 0;
for ( let i = 0; i < ruleCells.length; i++ ) {
let ruleCell = ruleCells[i];
let tColor = tColors[i];
let pColor = pColors[i];
ruleCell.setAttribute('data-tcolor', tColor);
ruleCell.setAttribute('data-pcolor', pColor);
if ( tColor === pColor ) { continue; }
if ( tColor < 128 && pColor < 128 ) { continue; }
diffCount += 1;
}
let dirty = diffCount !== 0;
ruleEditorNode
.querySelector('.buttonPersist .badge')
.textContent = dirty ? diffCount : '';
ruleEditorNode
.querySelector('.buttonRevertScope')
.classList
.toggle('disabled', !dirty);
ruleEditorNode
.querySelector('.buttonPersist')
.classList
.toggle('disabled', !dirty);
}
);
};
let quitHandler = function(ev) {
let target = ev.target;
if ( target.classList.contains('modalDialog') ) {
stop();
}
};
let scopeChangeHandler = function() {
colorize();
};
let attachRulePicker = function(ev) {
let target = ev.target;
if (
target instanceof HTMLElement === false ||
target.classList.contains('ruleCell') === false
) {
return;
}
target.appendChild(ruleActionPicker);
};
let removeRulePicker = function(ev) {
let target = ev.target;
if (
target instanceof HTMLElement === false ||
ruleActionPicker.closest('.ruleCell') === target.closest('.ruleCell')
) {
return;
}
removeSelf(ruleActionPicker);
};
let rulePickerHandler = function(ev) {
let action = ev.target.className;
if ( action !== 'allowRule' && action !== 'blockRule' ) { return; }
let cell = ev.target.closest('.ruleCell');
if ( cell === null ) { return; }
let row = cell.closest('.ruleRow');
let desHn = row.getAttribute('data-deshn');
let type = cell.getAttribute('data-type');
let color = parseInt(cell.getAttribute('data-tcolor'), 10);
let what;
if ( color === 1 || color === 2 ) {
what = action === 'blockRule' ?
'blacklistMatrixCell' :
'whitelistMatrixCell';
} else if ( desHn === '*' && type === '*' ) {
what = color === 130 ?
'blacklistMatrixCell' :
'whitelistMatrixCell';
} else {
what = 'graylistMatrixCell';
}
let request = {
what,
srcHostname: uMatrixScopeWidget.getScope(),
desHostname: desHn,
type
};
vAPI.messaging.send('default', request, colorize);
};
let reload = function(ev) {
vAPI.messaging.send('default', {
what: 'forceReloadTab',
tabId: parseInt(ruleEditorNode.getAttribute('data-tabid'), 10),
bypassCache: ev && (ev.ctrlKey || ev.metaKey || ev.shiftKey)
});
};
let diff = function() {
let entries = [];
let cells = ruleEditorNode.querySelectorAll('.ruleCell');
let srcHn = uMatrixScopeWidget.getScope();
for ( let cell of cells ) {
let tColor = cell.getAttribute('data-tcolor');
let pColor = cell.getAttribute('data-pcolor');
if ( tColor === pColor || tColor < 128 && pColor < 128 ) {
continue;
}
let row = cell.closest('.ruleRow');
entries.push({
srcHn,
desHn: row.getAttribute('data-deshn'),
type: cell.getAttribute('data-type')
});
}
return entries;
};
let persist = function() {
let entries = diff();
if ( entries.length === 0 ) { return; }
vAPI.messaging.send(
'default',
{ what: 'rulesetPersist', entries },
colorize
);
};
let revert = function() {
let entries = diff();
if ( entries.length === 0 ) { return; }
vAPI.messaging.send(
'default',
{ what: 'rulesetRevert', entries },
colorize
);
};
let start = function(ev) {
let targetRow = ev.target.parentElement;
let srcHn = targetRow.getAttribute('data-srchn') || '';
let desHn = targetRow.getAttribute('data-deshn') || '';
let type = targetRow.getAttribute('data-type') || '';
if ( srcHn === '' || desHn === '' || type === '' ) { return; }
let tabId = parseInt(targetRow.getAttribute('data-tabid'), 10);
vAPI.messaging.send(
'logger-ui.js',
{ what: 'getRuleEditorOptions' },
options => { setup({ tabId, srcHn, desHn, type, options }); }
);
};
let stop = function() {
for ( let { node, type, handler, options } of listeners ) {
node.removeEventListener(type, handler, options);
}
listeners = [];
ruleEditorNode.querySelector('.buttonReload').removeEventListener('click', reload);
removeSelf(ruleEditorNode);
};
return { start, stop };
})();
/******************************************************************************/ /******************************************************************************/
var toJunkyard = function(trs) { var toJunkyard = function(trs) {
@ -725,140 +1010,6 @@ var toggleCompactRow = function(ev) {
/******************************************************************************/ /******************************************************************************/
var popupManager = (function() {
var realTabId = null;
var localTabId = null;
var container = null;
var popup = null;
var popupObserver = null;
var style = null;
var styleTemplate = [
'tr:not(.tab_{{tabId}}) {',
'cursor: not-allowed;',
'opacity: 0.2;',
'}'
].join('\n');
var resizePopup = function() {
if ( popup === null ) {
return;
}
var popupBody = popup.contentWindow.document.body;
if ( popupBody.clientWidth !== 0 && container.clientWidth !== popupBody.clientWidth ) {
container.style.setProperty('width', popupBody.clientWidth + 'px');
}
popup.style.removeProperty('height');
if ( popupBody.clientHeight !== 0 && popup.clientHeight !== popupBody.clientHeight ) {
popup.style.setProperty('height', popupBody.clientHeight + 'px');
}
var ph = document.documentElement.clientHeight;
var crect = container.getBoundingClientRect();
if ( crect.height > ph ) {
popup.style.setProperty('height', 'calc(' + ph + 'px - 1.8em)');
}
// Adjust width for presence/absence of vertical scroll bar which may
// have appeared as a result of last operation.
var cw = container.clientWidth;
var dw = popup.contentWindow.document.documentElement.clientWidth;
if ( cw !== dw ) {
container.style.setProperty('width', (2 * cw - dw) + 'px');
}
};
var toggleSize = function() {
container.classList.toggle('hide');
};
var onResizeRequested = function() {
var popupBody = popup.contentWindow.document.body;
if ( popupBody.hasAttribute('data-resize-popup') === false ) {
return;
}
popupBody.removeAttribute('data-resize-popup');
resizePopup();
};
var onLoad = function() {
resizePopup();
var popupBody = popup.contentDocument.body;
popupBody.removeAttribute('data-resize-popup');
popupObserver.observe(popupBody, {
attributes: true,
attributesFilter: [ 'data-resize-popup' ]
});
};
var toggleOn = function(td) {
var tr = td.parentNode;
var matches = tr.className.match(/(?:^| )tab_([^ ]+)/);
if ( matches === null ) {
return;
}
realTabId = localTabId = matches[1];
if ( localTabId === 'bts' ) {
realTabId = noTabId;
}
container = document.getElementById('popupContainer');
container.querySelector('div > span:nth-of-type(1)').addEventListener('click', toggleSize);
container.querySelector('div > span:nth-of-type(2)').addEventListener('click', toggleOff);
popup = document.createElement('iframe');
popup.addEventListener('load', onLoad);
popup.setAttribute('src', 'popup.html?tabId=' + realTabId);
popupObserver = new MutationObserver(onResizeRequested);
container.appendChild(popup);
style = document.getElementById('popupFilterer');
style.textContent = styleTemplate.replace('{{tabId}}', localTabId);
document.body.classList.add('popupOn');
};
var toggleOff = function() {
document.body.classList.remove('popupOn');
container.querySelector('div > span:nth-of-type(1)').removeEventListener('click', toggleSize);
container.querySelector('div > span:nth-of-type(2)').removeEventListener('click', toggleOff);
container.classList.remove('hide');
popup.removeEventListener('load', onLoad);
popupObserver.disconnect();
popupObserver = null;
popup.setAttribute('src', '');
container.removeChild(popup);
popup = null;
style.textContent = '';
style = null;
container = null;
realTabId = null;
};
var exports = {
toggleOn: function(ev) {
if ( realTabId === null ) {
toggleOn(ev.target);
}
},
toggleOff: function() {
if ( realTabId !== null ) {
toggleOff();
}
}
};
Object.defineProperty(exports, 'tabId', {
get: function() { return realTabId || 0; }
});
return exports;
})();
/******************************************************************************/
var grabView = function() { var grabView = function() {
if ( ownerId === undefined ) { if ( ownerId === undefined ) {
ownerId = Date.now(); ownerId = Date.now();
@ -882,6 +1033,40 @@ window.addEventListener('beforeunload', releaseView);
/******************************************************************************/ /******************************************************************************/
// We will lookup domains locally.
let domainFromSrcHostname = (function() {
let srcHn = '', srcDn = '';
return function(hn) {
if ( hn !== srcHn ) {
srcHn = hn;
srcDn = publicSuffixList.getDomain(hn);
}
return srcDn;
};
})();
let domainFromDesHostname = (function() {
let desHn = '', desDn = '';
return function(hn) {
if ( hn !== desHn ) {
desHn = hn;
desDn = publicSuffixList.getDomain(hn);
}
return desDn;
};
})();
vAPI.messaging.send(
'logger-ui.js',
{ what: 'getPublicSuffixListData' },
response => {
publicSuffixList.fromSelfie(response);
}
);
/******************************************************************************/
readLogBuffer(); readLogBuffer();
uDom('#pageSelector').on('change', pageSelectorChanged); uDom('#pageSelector').on('change', pageSelectorChanged);
@ -891,7 +1076,7 @@ uDom('#clean').on('click', cleanBuffer);
uDom('#clear').on('click', clearBuffer); uDom('#clear').on('click', clearBuffer);
uDom('#maxEntries').on('change', onMaxEntriesChanged); uDom('#maxEntries').on('change', onMaxEntriesChanged);
uDom('#content table').on('click', 'tr > td:nth-of-type(1)', toggleCompactRow); uDom('#content table').on('click', 'tr > td:nth-of-type(1)', toggleCompactRow);
uDom('#content table').on('click', 'tr.canMtx > td:nth-of-type(2)', popupManager.toggleOn); uDom('#content table').on('click', 'tr.canMtx > td:nth-of-type(3)', ruleEditor.start);
/******************************************************************************/ /******************************************************************************/

@ -1,7 +1,7 @@
/******************************************************************************* /*******************************************************************************
uMatrix - a browser extension to block requests. uMatrix - a browser extension to block requests.
Copyright (C) 2015-2017 Raymond Hill Copyright (C) 2015-present Raymond Hill
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -26,29 +26,24 @@
µMatrix.logger = (function() { µMatrix.logger = (function() {
var LogEntry = function(args) { let LogEntry = function(details) {
this.init(args); this.init(details);
}; };
LogEntry.prototype.init = function(args) { LogEntry.prototype.init = function(details) {
this.tstamp = Date.now(); this.tstamp = Date.now();
this.tab = args[0] || ''; this.details = JSON.stringify(details);
this.cat = args[1] || '';
this.d0 = args[2];
this.d1 = args[3];
this.d2 = args[4];
this.d3 = args[5];
}; };
var buffer = null; let buffer = null;
var lastReadTime = 0; let lastReadTime = 0;
var writePtr = 0; let writePtr = 0;
// After 60 seconds without being read, a buffer will be considered // After 60 seconds without being read, a buffer will be considered
// unused, and thus removed from memory. // unused, and thus removed from memory.
var logBufferObsoleteAfter = 30 * 1000; let logBufferObsoleteAfter = 30 * 1000;
var janitor = function() { let janitor = function() {
if ( if (
buffer !== null && buffer !== null &&
lastReadTime < (Date.now() - logBufferObsoleteAfter) lastReadTime < (Date.now() - logBufferObsoleteAfter)
@ -56,34 +51,37 @@
buffer = null; buffer = null;
writePtr = 0; writePtr = 0;
api.ownerId = undefined; api.ownerId = undefined;
api.enabled = false;
} }
if ( buffer !== null ) { if ( buffer !== null ) {
vAPI.setTimeout(janitor, logBufferObsoleteAfter); vAPI.setTimeout(janitor, logBufferObsoleteAfter);
} }
}; };
var api = { let api = {
enabled: false,
ownerId: undefined, ownerId: undefined,
writeOne: function() { writeOne: function(details) {
if ( buffer === null ) { return; } if ( buffer === null ) { return; }
if ( writePtr === buffer.length ) { if ( writePtr === buffer.length ) {
buffer.push(new LogEntry(arguments)); buffer.push(new LogEntry(details));
} else { } else {
buffer[writePtr].init(arguments); buffer[writePtr].init(details);
} }
writePtr += 1; writePtr += 1;
}, },
readAll: function(ownerId) { readAll: function(ownerId) {
this.ownerId = ownerId; this.ownerId = ownerId;
this.enabled = true;
if ( buffer === null ) { if ( buffer === null ) {
buffer = []; buffer = [];
vAPI.setTimeout(janitor, logBufferObsoleteAfter); vAPI.setTimeout(janitor, logBufferObsoleteAfter);
} }
var out = buffer.slice(0, writePtr); let out = buffer.slice(0, writePtr);
writePtr = 0; writePtr = 0;
lastReadTime = Date.now(); lastReadTime = Date.now();
return out; return out;
} },
}; };
return api; return api;

@ -509,8 +509,8 @@ Matrix.prototype.evaluateCellZXY = function(srcHostname, desHostname, type) {
/******************************************************************************/ /******************************************************************************/
Matrix.prototype.evaluateRowZXY = function(srcHostname, desHostname) { Matrix.prototype.evaluateRowZXY = function(srcHostname, desHostname) {
var out = []; let out = [];
for ( var type of typeBitOffsets.keys() ) { for ( let type of typeBitOffsets.keys() ) {
out.push(this.evaluateCellZXY(srcHostname, desHostname, type)); out.push(this.evaluateCellZXY(srcHostname, desHostname, type));
} }
return out; return out;
@ -846,6 +846,33 @@ Matrix.prototype.applyDiff = function(diff, from) {
return changed; return changed;
}; };
Matrix.prototype.copyRuleset = function(entries, from, deep) {
let changed = false;
for ( let entry of entries ) {
let srcHn = entry.srcHn;
for (;;) {
if (
entry.switchName !== undefined &&
switchBitOffsets.has(entry.switchName)
) {
let val = from.evaluateSwitch(entry.switchName, srcHn);
if ( this.setSwitch(entry.switchName, srcHn, val) ) {
changed = true;
}
} else if ( entry.desHn && entry.type ) {
let val = from.evaluateCell(srcHn, entry.desHn, entry.type);
if ( this.setCell(srcHn, entry.desHn, entry.type, val) ) {
changed = true;
}
}
if ( !deep ) { break; }
srcHn = toBroaderHostname(srcHn);
if ( srcHn === '' ) { break; }
}
}
return changed;
};
/******************************************************************************/ /******************************************************************************/
return Matrix; return Matrix;

@ -19,6 +19,8 @@
Home: https://github.com/gorhill/uMatrix Home: https://github.com/gorhill/uMatrix
*/ */
/* globals publicSuffixList */
'use strict'; 'use strict';
/******************************************************************************/ /******************************************************************************/
@ -53,6 +55,14 @@ function onMessage(request, sender, callback) {
var response; var response;
switch ( request.what ) { switch ( request.what ) {
case 'blacklistMatrixCell':
µm.tMatrix.blacklistCell(
request.srcHostname,
request.desHostname,
request.type
);
break;
case 'forceReloadTab': case 'forceReloadTab':
µm.forceReload(request.tabId, request.bypassCache); µm.forceReload(request.tabId, request.bypassCache);
break; break;
@ -62,6 +72,25 @@ function onMessage(request, sender, callback) {
µm.assets.updateStart({ delay: 2000 }); µm.assets.updateStart({ delay: 2000 });
break; break;
case 'getCellColors':
let ruleParts = request.ruleParts;
let tColors = [];
let pColors = [];
for ( let i = 0, n = ruleParts.length; i < n; i += 3 ) {
tColors.push(µm.tMatrix.evaluateCellZXY(
ruleParts[i+0],
ruleParts[i+1],
ruleParts[i+2]
));
pColors.push(µm.pMatrix.evaluateCellZXY(
ruleParts[i+0],
ruleParts[i+1],
ruleParts[i+2]
));
}
response = { tColors, pColors };
break;
case 'getUserSettings': case 'getUserSettings':
response = { response = {
userSettings: µm.userSettings, userSettings: µm.userSettings,
@ -81,6 +110,14 @@ function onMessage(request, sender, callback) {
µm.gotoURL(request); µm.gotoURL(request);
break; break;
case 'graylistMatrixCell':
µm.tMatrix.graylistCell(
request.srcHostname,
request.desHostname,
request.type
);
break;
case 'mustBlock': case 'mustBlock':
response = µm.mustBlock( response = µm.mustBlock(
request.scope, request.scope,
@ -89,6 +126,16 @@ function onMessage(request, sender, callback) {
); );
break; break;
case 'rulesetRevert':
µm.tMatrix.copyRuleset(request.entries, µm.pMatrix, true);
break;
case 'rulesetPersist':
if ( µm.pMatrix.copyRuleset(request.entries, µm.tMatrix, true) ) {
µm.saveMatrix();
}
break;
case 'readRawSettings': case 'readRawSettings':
response = µm.stringFromRawSettings(); response = µm.stringFromRawSettings();
break; break;
@ -115,6 +162,14 @@ function onMessage(request, sender, callback) {
response = µm.changeUserSettings(request.name, request.value); response = µm.changeUserSettings(request.name, request.value);
break; break;
case 'whitelistMatrixCell':
µm.tMatrix.whitelistCell(
request.srcHostname,
request.desHostname,
request.type
);
break;
case 'writeRawSettings': case 'writeRawSettings':
µm.rawSettingsFromString(request.content); µm.rawSettingsFromString(request.content);
break; break;
@ -366,30 +421,6 @@ var onMessage = function(request, sender, callback) {
); );
break; break;
case 'blacklistMatrixCell':
µm.tMatrix.blacklistCell(
request.srcHostname,
request.desHostname,
request.type
);
break;
case 'whitelistMatrixCell':
µm.tMatrix.whitelistCell(
request.srcHostname,
request.desHostname,
request.type
);
break;
case 'graylistMatrixCell':
µm.tMatrix.graylistCell(
request.srcHostname,
request.desHostname,
request.type
);
break;
case 'applyDiffToPermanentMatrix': // aka "persist" case 'applyDiffToPermanentMatrix': // aka "persist"
if ( µm.pMatrix.applyDiff(request.diff, µm.tMatrix) ) { if ( µm.pMatrix.applyDiff(request.diff, µm.tMatrix) ) {
µm.saveMatrix(); µm.saveMatrix();
@ -429,44 +460,44 @@ var µm = µMatrix;
var foundInlineCode = function(tabId, pageStore, details, type) { var foundInlineCode = function(tabId, pageStore, details, type) {
if ( pageStore === null ) { return; } if ( pageStore === null ) { return; }
var pageHostname = pageStore.pageHostname, let srcHn = pageStore.pageHostname,
µmuri = µm.URI.set(details.documentURI), µmuri = µm.URI.set(details.documentURI),
desHn = µmuri.hostname,
frameURL = µmuri.normalizedURI(); frameURL = µmuri.normalizedURI();
var blocked = details.blocked; let blocked = details.blocked;
if ( blocked === undefined ) { if ( blocked === undefined ) {
blocked = µm.mustBlock(pageHostname, µmuri.hostname, type); blocked = µm.mustBlock(srcHn, desHn, type);
} }
var mapTo = { let mapTo = {
css: 'style', css: 'style',
script: 'script' script: 'script'
}; };
// https://github.com/gorhill/httpswitchboard/issues/333 // https://github.com/gorhill/httpswitchboard/issues/333
// Look-up here whether inline scripting is blocked for the frame. // Look-up here whether inline scripting is blocked for the frame.
var url = frameURL + '{inline_' + mapTo[type] + '}'; let desURL = frameURL + '{inline_' + mapTo[type] + '}';
pageStore.recordRequest(type, url, blocked); pageStore.recordRequest(type, desURL, blocked);
µm.logger.writeOne(tabId, 'net', pageHostname, url, type, blocked); µm.logger.writeOne({ tabId, srcHn, desHn, desURL, type, blocked });
}; };
/******************************************************************************/ /******************************************************************************/
var contentScriptLocalStorageHandler = function(tabId, originURL) { var contentScriptLocalStorageHandler = function(tabId, originURL) {
var tabContext = µm.tabContextManager.lookup(tabId); let tabContext = µm.tabContextManager.lookup(tabId);
if ( tabContext === null ) { return; } if ( tabContext === null ) { return; }
var blocked = µm.mustBlock( let srcHn = tabContext.rootHostname,
tabContext.rootHostname, desHn = µm.URI.hostnameFromURI(originURL),
µm.URI.hostnameFromURI(originURL), type = 'cookie',
'cookie' blocked = µm.mustBlock(srcHn, desHn, type);
);
var pageStore = µm.pageStoreFromTabId(tabId); let pageStore = µm.pageStoreFromTabId(tabId);
if ( pageStore !== null ) { if ( pageStore !== null ) {
var requestURL = originURL + '/{localStorage}'; let desURL = originURL + '/{localStorage}';
pageStore.recordRequest('cookie', requestURL, blocked); pageStore.recordRequest(type, desURL, blocked);
µm.logger.writeOne(tabId, 'net', tabContext.rootHostname, requestURL, 'cookie', blocked); µm.logger.writeOne({ tabId, srcHn, desHn, desURL, type, blocked });
} }
var removeStorage = blocked && µm.userSettings.deleteLocalStorage; var removeStorage = blocked && µm.userSettings.deleteLocalStorage;
@ -545,13 +576,13 @@ var onMessage = function(request, sender, callback) {
break; break;
} }
var tabId = sender && sender.tab ? sender.tab.id || 0 : 0, let tabId = sender && sender.tab ? sender.tab.id || 0 : 0,
tabContext = µm.tabContextManager.lookup(tabId), tabContext = µm.tabContextManager.lookup(tabId),
rootHostname = tabContext && tabContext.rootHostname, srcHn = tabContext && tabContext.rootHostname,
pageStore = µm.pageStoreFromTabId(tabId); pageStore = µm.pageStoreFromTabId(tabId);
// Sync // Sync
var response; let response;
switch ( request.what ) { switch ( request.what ) {
case 'contentScriptHasLocalStorage': case 'contentScriptHasLocalStorage':
@ -565,8 +596,8 @@ var onMessage = function(request, sender, callback) {
case 'mustRenderNoscriptTags?': case 'mustRenderNoscriptTags?':
if ( tabContext === null ) { break; } if ( tabContext === null ) { break; }
response = response =
µm.tMatrix.mustBlock(rootHostname, rootHostname, 'script') && µm.tMatrix.mustBlock(srcHn, srcHn, 'script') &&
µm.tMatrix.evaluateSwitchZ('noscript-spoof', rootHostname); µm.tMatrix.evaluateSwitchZ('noscript-spoof', srcHn);
if ( pageStore !== null ) { if ( pageStore !== null ) {
pageStore.hasNoscriptTags = true; pageStore.hasNoscriptTags = true;
} }
@ -578,15 +609,25 @@ var onMessage = function(request, sender, callback) {
case 'securityPolicyViolation': case 'securityPolicyViolation':
if ( request.directive === 'worker-src' ) { if ( request.directive === 'worker-src' ) {
var url = µm.URI.hostnameFromURI(request.blockedURI) !== '' ? let desURL = request.blockedURI;
request.blockedURI : let desHn = µm.URI.hostnameFromURI(desURL);
request.documentURI; if ( desHn === '' ) {
desURL = request.documentURI;
desHn = µm.URI.hostnameFromURI(desURL);
}
if ( pageStore !== null ) { if ( pageStore !== null ) {
pageStore.hasWebWorkers = true; pageStore.hasWebWorkers = true;
pageStore.recordRequest('script', url, request.blocked); pageStore.recordRequest('script', desURL, request.blocked);
} }
if ( tabContext !== null ) { if ( tabContext !== null ) {
µm.logger.writeOne(tabId, 'net', rootHostname, url, 'worker', request.blocked); µm.logger.writeOne({
tabId,
srcHn,
desHn,
desURL,
type: 'worker',
blocked: request.blocked
});
} }
} else if ( request.directive === 'script-src' ) { } else if ( request.directive === 'script-src' ) {
foundInlineCode(tabId, pageStore, request, 'script'); foundInlineCode(tabId, pageStore, request, 'script');
@ -597,7 +638,7 @@ var onMessage = function(request, sender, callback) {
case 'shutdown?': case 'shutdown?':
if ( tabContext !== null ) { if ( tabContext !== null ) {
response = µm.tMatrix.evaluateSwitchZ('matrix-off', rootHostname); response = µm.tMatrix.evaluateSwitchZ('matrix-off', srcHn);
} }
break; break;
@ -936,6 +977,17 @@ var onMessage = function(request, sender, callback) {
var response; var response;
switch ( request.what ) { switch ( request.what ) {
case 'getPublicSuffixListData':
response = publicSuffixList.toSelfie();
break;
case 'getRuleEditorOptions':
response = {
colorBlindFriendly: µm.userSettings.colorBlindFriendly,
popupScopeLevel: µm.userSettings.popupScopeLevel
};
break;
case 'readMany': case 'readMany':
if ( if (
µm.logger.ownerId !== undefined && µm.logger.ownerId !== undefined &&

@ -1,7 +1,7 @@
/******************************************************************************* /*******************************************************************************
uMatrix - a browser extension to black/white list requests. uMatrix - a browser extension to black/white list requests.
Copyright (C) 2014-2018 Raymond Hill Copyright (C) 2014-present Raymond Hill
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,7 +19,7 @@
Home: https://github.com/gorhill/uMatrix Home: https://github.com/gorhill/uMatrix
*/ */
/* global punycode, uDom */ /* global punycode, uDom, uMatrixScopeWidget */
/* jshint esnext: true, bitwise: false */ /* jshint esnext: true, bitwise: false */
'use strict'; 'use strict';
@ -501,22 +501,20 @@ function getCellAction(hostname, type, leaning) {
function handleFilter(button, leaning) { function handleFilter(button, leaning) {
// our parent cell knows who we are // our parent cell knows who we are
var cell = button.ancestors('div.matCell'), let cell = button.ancestors('div.matCell'),
expandos = expandosFromNode(cell), expandos = expandosFromNode(cell),
type = expandos.reqType, type = expandos.reqType,
desHostname = expandos.hostname; desHostname = expandos.hostname;
// https://github.com/gorhill/uMatrix/issues/24 // https://github.com/gorhill/uMatrix/issues/24
// No hostname can happen -- like with blacklist meta row // No hostname can happen -- like with blacklist meta row
if ( desHostname === '' ) { if ( desHostname === '' ) { return; }
return; let request = {
}
var request = {
what: getCellAction(desHostname, type, leaning), what: getCellAction(desHostname, type, leaning),
srcHostname: matrixSnapshot.scope, srcHostname: matrixSnapshot.scope,
desHostname: desHostname, desHostname: desHostname,
type: type type: type
}; };
vAPI.messaging.send('popup.js', request, updateMatrixSnapshot); vAPI.messaging.send('default', request, updateMatrixSnapshot);
} }
function handleWhitelistFilter(button) { function handleWhitelistFilter(button) {
@ -1055,7 +1053,11 @@ var makeMenu = function() {
makeMatrixGroup4(groupStats[4]); makeMatrixGroup4(groupStats[4]);
endMatrixUpdate(); endMatrixUpdate();
initScopeCell(); uMatrixScopeWidget.init(
matrixSnapshot.domain,
matrixSnapshot.hostname,
matrixSnapshot.scope
);
updateMatrixButtons(); updateMatrixButtons();
resizePopup(); resizePopup();
recipeManager.fetch(); recipeManager.fetch();
@ -1098,91 +1100,15 @@ function initMenuEnvironment() {
/******************************************************************************/ /******************************************************************************/
// Create page scopes for the web page function scopeChangeHandler(ev) {
let newScope = ev.detail.scope;
function selectGlobalScope() {
if ( matrixSnapshot.scope === '*' ) { return; }
matrixSnapshot.scope = '*';
document.body.classList.add('globalScope');
matrixSnapshot.tMatrixModifiedTime = undefined;
updateMatrixSnapshot();
dropDownMenuHide();
}
function selectSpecificScope(ev) {
var newScope = ev.target.getAttribute('data-scope');
if ( !newScope || matrixSnapshot.scope === newScope ) { return; } if ( !newScope || matrixSnapshot.scope === newScope ) { return; }
document.body.classList.remove('globalScope');
matrixSnapshot.scope = newScope; matrixSnapshot.scope = newScope;
matrixSnapshot.tMatrixModifiedTime = undefined; matrixSnapshot.tMatrixModifiedTime = undefined;
updateMatrixSnapshot(); updateMatrixSnapshot();
dropDownMenuHide(); dropDownMenuHide();
} }
function initScopeCell() {
// It's possible there is no page URL at this point: some pages cannot
// be filtered by uMatrix.
if ( matrixSnapshot.url === '' ) { return; }
var specificScope = uDom.nodeFromId('specificScope');
while ( specificScope.firstChild !== null ) {
specificScope.removeChild(specificScope.firstChild);
}
// Fill in the scope menu entries
var pos = matrixSnapshot.domain.indexOf('.');
var tld, labels;
if ( pos === -1 ) {
tld = '';
labels = matrixSnapshot.hostname;
} else {
tld = matrixSnapshot.domain.slice(pos + 1);
labels = matrixSnapshot.hostname.slice(0, -tld.length);
}
var beg = 0, span, label;
while ( beg < labels.length ) {
pos = labels.indexOf('.', beg);
if ( pos === -1 ) {
pos = labels.length;
} else {
pos += 1;
}
label = document.createElement('span');
label.appendChild(
document.createTextNode(punycode.toUnicode(labels.slice(beg, pos)))
);
span = document.createElement('span');
span.setAttribute('data-scope', labels.slice(beg) + tld);
span.appendChild(label);
specificScope.appendChild(span);
beg = pos;
}
if ( tld !== '' ) {
label = document.createElement('span');
label.appendChild(document.createTextNode(punycode.toUnicode(tld)));
span = document.createElement('span');
span.setAttribute('data-scope', tld);
span.appendChild(label);
specificScope.appendChild(span);
}
updateScopeCell();
}
function updateScopeCell() {
var specificScope = uDom.nodeFromId('specificScope'),
isGlobal = matrixSnapshot.scope === '*';
document.body.classList.toggle('globalScope', isGlobal);
specificScope.classList.toggle('on', !isGlobal);
uDom.nodeFromId('globalScope').classList.toggle('on', isGlobal);
for ( var node of specificScope.children ) {
node.classList.toggle(
'on',
!isGlobal &&
matrixSnapshot.scope.endsWith(node.getAttribute('data-scope'))
);
}
}
/******************************************************************************/ /******************************************************************************/
function updateMatrixSwitches() { function updateMatrixSwitches() {
@ -1277,7 +1203,7 @@ function revertMatrix() {
// Buttons which are affected by any changes in the matrix // Buttons which are affected by any changes in the matrix
function updateMatrixButtons() { function updateMatrixButtons() {
updateScopeCell(); uMatrixScopeWidget.update(matrixSnapshot.scope);
updateMatrixSwitches(); updateMatrixSwitches();
updatePersistButton(); updatePersistButton();
} }
@ -1285,7 +1211,7 @@ function updateMatrixButtons() {
/******************************************************************************/ /******************************************************************************/
function buttonReloadHandler(ev) { function buttonReloadHandler(ev) {
vAPI.messaging.send('popup.js', { vAPI.messaging.send('default', {
what: 'forceReloadTab', what: 'forceReloadTab',
tabId: matrixSnapshot.tabId, tabId: matrixSnapshot.tabId,
bypassCache: ev.ctrlKey || ev.metaKey || ev.shiftKey bypassCache: ev.ctrlKey || ev.metaKey || ev.shiftKey
@ -1637,8 +1563,7 @@ matrixCellHotspots = uDom('#cellHotspots').detach();
uDom('body') uDom('body')
.on('mouseenter', '.matCell', mouseenterMatrixCellHandler) .on('mouseenter', '.matCell', mouseenterMatrixCellHandler)
.on('mouseleave', '.matCell', mouseleaveMatrixCellHandler); .on('mouseleave', '.matCell', mouseleaveMatrixCellHandler);
uDom('#specificScope').on('click', selectSpecificScope); window.addEventListener('uMatrixScopeWidgetChange', scopeChangeHandler);
uDom('#globalScope').on('click', selectGlobalScope);
uDom('#buttonMtxSwitches').on('click', function(ev) { uDom('#buttonMtxSwitches').on('click', function(ev) {
dropDownMenuShow(ev.target); dropDownMenuShow(ev.target);
}); });

@ -0,0 +1,152 @@
/*******************************************************************************
uMatrix - a browser extension to black/white list requests.
Copyright (C) 2018-present Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uMatrix
*/
/* exported uMatrixScopeWidget */
/* global punycode */
'use strict';
/******************************************************************************/
/******************************************************************************/
let uMatrixScopeWidget = (function() {
// Start of private namespace
// >>>>>>>>
/******************************************************************************/
let currentScope = '';
let listening = false;
let fireChangeEvent = function() {
document.body.setAttribute('data-scope', currentScope);
let ev = new CustomEvent(
'uMatrixScopeWidgetChange',
{
detail: { scope: currentScope }
}
);
window.dispatchEvent(ev);
};
let init = function(domain, hostname, scope, container) {
if ( typeof domain !== 'string' || domain === '' ) { return; }
currentScope = '';
// Reset widget
if ( !container ) {
container = document;
}
let specificScope = container.querySelector('#specificScope');
while ( specificScope.firstChild !== null ) {
specificScope.removeChild(specificScope.firstChild);
}
// Fill in the scope menu entries
let pos = domain.indexOf('.');
let tld, labels;
if ( pos === -1 ) {
tld = '';
labels = hostname;
} else {
tld = domain.slice(pos + 1);
labels = hostname.slice(0, -tld.length);
}
let beg = 0;
while ( beg < labels.length ) {
pos = labels.indexOf('.', beg);
if ( pos === -1 ) {
pos = labels.length;
} else {
pos += 1;
}
let label = document.createElement('span');
label.appendChild(
document.createTextNode(punycode.toUnicode(labels.slice(beg, pos)))
);
let span = document.createElement('span');
span.setAttribute('data-scope', labels.slice(beg) + tld);
span.appendChild(label);
specificScope.appendChild(span);
beg = pos;
}
if ( tld !== '' ) {
let label = document.createElement('span');
label.appendChild(document.createTextNode(punycode.toUnicode(tld)));
let span = document.createElement('span');
span.setAttribute('data-scope', tld);
span.appendChild(label);
specificScope.appendChild(span);
}
if ( listening === false ) {
container.querySelector('#specificScope').addEventListener(
'click',
ev => { update(ev.target.getAttribute('data-scope')); }
);
container.querySelector('#globalScope').addEventListener(
'click',
( ) => { update('*'); }
);
listening = true;
}
update(scope, container);
};
let getScope = function() {
return currentScope;
};
let update = function(scope, container) {
if ( scope === currentScope ) { return; }
currentScope = scope;
if ( !container ) {
container = document;
}
let specificScope = container.querySelector('#specificScope'),
isGlobal = scope === '*';
specificScope.classList.toggle('on', !isGlobal);
container.querySelector('#globalScope').classList.toggle('on', isGlobal);
for ( let node of specificScope.children ) {
node.classList.toggle(
'on',
!isGlobal &&
scope.endsWith(node.getAttribute('data-scope'))
);
}
fireChangeEvent();
};
return { init, getScope, update };
/******************************************************************************/
// <<<<<<<<
// End of private namespace
})();
/******************************************************************************/
/******************************************************************************/

@ -32,29 +32,29 @@
// Intercept and filter web requests according to white and black lists. // Intercept and filter web requests according to white and black lists.
var onBeforeRootFrameRequestHandler = function(details) { var onBeforeRootFrameRequestHandler = function(details) {
var µm = µMatrix; let µm = µMatrix;
var requestURL = details.url; let desURL = details.url;
var requestHostname = µm.URI.hostnameFromURI(requestURL); let desHn = µm.URI.hostnameFromURI(desURL);
var tabId = details.tabId; let tabId = details.tabId;
µm.tabContextManager.push(tabId, requestURL); µm.tabContextManager.push(tabId, desURL);
var tabContext = µm.tabContextManager.mustLookup(tabId); let tabContext = µm.tabContextManager.mustLookup(tabId);
var rootHostname = tabContext.rootHostname; let srcHn = tabContext.rootHostname;
// Disallow request as per matrix? // Disallow request as per matrix?
var block = µm.mustBlock(rootHostname, requestHostname, 'doc'); let blocked = µm.mustBlock(srcHn, desHn, 'doc');
var pageStore = µm.pageStoreFromTabId(tabId); let pageStore = µm.pageStoreFromTabId(tabId);
pageStore.recordRequest('doc', requestURL, block); pageStore.recordRequest('doc', desURL, blocked);
pageStore.perLoadAllowedRequestCount = 0; pageStore.perLoadAllowedRequestCount = 0;
pageStore.perLoadBlockedRequestCount = 0; pageStore.perLoadBlockedRequestCount = 0;
µm.logger.writeOne(tabId, 'net', rootHostname, requestURL, 'doc', block); µm.logger.writeOne({ tabId, srcHn, desHn, desURL, type: 'doc', blocked });
// Not blocked // Not blocked
if ( !block ) { if ( !blocked ) {
let redirectURL = maybeRedirectRootFrame(requestHostname, requestURL); let redirectURL = maybeRedirectRootFrame(desHn, desURL);
if ( redirectURL !== requestURL ) { if ( redirectURL !== desURL ) {
return { redirectUrl: redirectURL }; return { redirectUrl: redirectURL };
} }
µm.cookieHunter.recordPageCookies(pageStore); µm.cookieHunter.recordPageCookies(pageStore);
@ -62,11 +62,7 @@ var onBeforeRootFrameRequestHandler = function(details) {
} }
// Blocked // Blocked
var query = btoa(JSON.stringify({ let query = btoa(JSON.stringify({ url: desURL, hn: desHn, why: '?' }));
url: requestURL,
hn: requestHostname,
why: '?'
}));
vAPI.tabs.replace(tabId, vAPI.getURL('main-blocked.html?details=') + query); vAPI.tabs.replace(tabId, vAPI.getURL('main-blocked.html?details=') + query);
@ -99,19 +95,19 @@ var maybeRedirectRootFrame = function(hostname, url) {
// Intercept and filter web requests according to white and black lists. // Intercept and filter web requests according to white and black lists.
var onBeforeRequestHandler = function(details) { var onBeforeRequestHandler = function(details) {
var µm = µMatrix, let µm = µMatrix,
µmuri = µm.URI, µmuri = µm.URI,
requestURL = details.url, desURL = details.url,
requestScheme = µmuri.schemeFromURI(requestURL); desScheme = µmuri.schemeFromURI(desURL);
if ( µmuri.isNetworkScheme(requestScheme) === false ) { return; } if ( µmuri.isNetworkScheme(desScheme) === false ) { return; }
var requestType = requestTypeNormalizer[details.type] || 'other'; let type = requestTypeNormalizer[details.type] || 'other';
// https://github.com/gorhill/httpswitchboard/issues/303 // https://github.com/gorhill/httpswitchboard/issues/303
// Wherever the main doc comes from, create a receiver page URL: synthetize // Wherever the main doc comes from, create a receiver page URL: synthetize
// one if needed. // one if needed.
if ( requestType === 'doc' && details.parentFrameId === -1 ) { if ( type === 'doc' && details.parentFrameId === -1 ) {
return onBeforeRootFrameRequestHandler(details); return onBeforeRootFrameRequestHandler(details);
} }
@ -122,9 +118,9 @@ var onBeforeRequestHandler = function(details) {
// to scope on unknown scheme? Etc. // to scope on unknown scheme? Etc.
// https://github.com/gorhill/httpswitchboard/issues/191 // https://github.com/gorhill/httpswitchboard/issues/191
// https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275 // https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275
var tabContext = µm.tabContextManager.mustLookup(details.tabId), let tabContext = µm.tabContextManager.mustLookup(details.tabId),
tabId = tabContext.tabId, tabId = tabContext.tabId,
rootHostname = tabContext.rootHostname, srcHn = tabContext.rootHostname,
specificity = 0; specificity = 0;
// https://github.com/gorhill/uMatrix/issues/995 // https://github.com/gorhill/uMatrix/issues/995
@ -137,18 +133,16 @@ var onBeforeRequestHandler = function(details) {
µmuri.isNetworkURI(details.documentUrl) µmuri.isNetworkURI(details.documentUrl)
) { ) {
tabId = µm.tabContextManager.tabIdFromURL(details.documentUrl); tabId = µm.tabContextManager.tabIdFromURL(details.documentUrl);
rootHostname = µmuri.hostnameFromURI( srcHn = µmuri.hostnameFromURI(
µm.normalizePageURL(0, details.documentUrl) µm.normalizePageURL(0, details.documentUrl)
); );
} }
// Filter through matrix // Filter through matrix
var block = µm.tMatrix.mustBlock( let desHn = µmuri.hostnameFromURI(desURL);
rootHostname,
µmuri.hostnameFromURI(requestURL), let blocked = µm.tMatrix.mustBlock(srcHn, desHn, type);
requestType if ( blocked ) {
);
if ( block ) {
specificity = µm.tMatrix.specificityRegister; specificity = µm.tMatrix.specificityRegister;
} }
@ -158,21 +152,23 @@ var onBeforeRequestHandler = function(details) {
// processing has already been performed, and that a synthetic URL has // processing has already been performed, and that a synthetic URL has
// been constructed for logging purpose. Use this synthetic URL if // been constructed for logging purpose. Use this synthetic URL if
// it is available. // it is available.
var pageStore = µm.mustPageStoreFromTabId(tabId); let pageStore = µm.mustPageStoreFromTabId(tabId);
// Enforce strict secure connection? // Enforce strict secure connection?
if ( tabContext.secure && µmuri.isSecureScheme(requestScheme) === false ) { if ( tabContext.secure && µmuri.isSecureScheme(desScheme) === false ) {
pageStore.hasMixedContent = true; pageStore.hasMixedContent = true;
if ( block === false ) { if ( blocked === false ) {
block = µm.tMatrix.evaluateSwitchZ('https-strict', rootHostname); blocked = µm.tMatrix.evaluateSwitchZ('https-strict', srcHn);
} }
} }
pageStore.recordRequest(requestType, requestURL, block); pageStore.recordRequest(type, desURL, blocked);
µm.logger.writeOne(tabId, 'net', rootHostname, requestURL, details.type, block); if ( µm.logger.enabled ) {
µm.logger.writeOne({ tabId, srcHn, desHn, desURL, type, blocked });
}
if ( block ) { if ( blocked ) {
pageStore.cacheBlockedCollapsible(requestType, requestURL, specificity); pageStore.cacheBlockedCollapsible(type, desURL, specificity);
return { 'cancel': true }; return { 'cancel': true };
} }
}; };
@ -184,11 +180,11 @@ var onBeforeRequestHandler = function(details) {
var onBeforeSendHeadersHandler = function(details) { var onBeforeSendHeadersHandler = function(details) {
let µm = µMatrix, let µm = µMatrix,
µmuri = µm.URI, µmuri = µm.URI,
requestURL = details.url, desURL = details.url,
requestScheme = µmuri.schemeFromURI(requestURL); desScheme = µmuri.schemeFromURI(desURL);
// Ignore non-network schemes // Ignore non-network schemes
if ( µmuri.isNetworkScheme(requestScheme) === false ) { return; } if ( µmuri.isNetworkScheme(desScheme) === false ) { return; }
// Re-classify orphan HTTP requests as behind-the-scene requests. There is // Re-classify orphan HTTP requests as behind-the-scene requests. There is
// not much else which can be done, because there are URLs // not much else which can be done, because there are URLs
@ -227,10 +223,10 @@ var onBeforeSendHeadersHandler = function(details) {
if ( headerIndex !== -1 ) { if ( headerIndex !== -1 ) {
let headerValue = requestHeaders[headerIndex].value; let headerValue = requestHeaders[headerIndex].value;
if ( headerValue !== '' ) { if ( headerValue !== '' ) {
var block = µm.userSettings.processHyperlinkAuditing; let blocked = µm.userSettings.processHyperlinkAuditing;
pageStore.recordRequest('other', requestURL + '{Ping-To:' + headerValue + '}', block); pageStore.recordRequest('other', desURL + '{Ping-To:' + headerValue + '}', blocked);
µm.logger.writeOne(tabId, 'net', '', requestURL, 'ping', block); µm.logger.writeOne({ tabId, desURL, type: 'ping', blocked });
if ( block ) { if ( blocked ) {
µm.hyperlinkAuditingFoiledCounter += 1; µm.hyperlinkAuditingFoiledCounter += 1;
return { 'cancel': true }; return { 'cancel': true };
} }
@ -240,8 +236,8 @@ var onBeforeSendHeadersHandler = function(details) {
// If we reach this point, request is not blocked, so what is left to do // If we reach this point, request is not blocked, so what is left to do
// is to sanitize headers. // is to sanitize headers.
let rootHostname = pageStore.pageHostname, let srcHn = pageStore.pageHostname,
requestHostname = µmuri.hostnameFromURI(requestURL), desHn = µmuri.hostnameFromURI(desURL),
modified = false; modified = false;
// Process `Cookie` header. // Process `Cookie` header.
@ -249,7 +245,7 @@ var onBeforeSendHeadersHandler = function(details) {
headerIndex = headerIndexFromName('cookie', requestHeaders); headerIndex = headerIndexFromName('cookie', requestHeaders);
if ( if (
headerIndex !== -1 && headerIndex !== -1 &&
µm.mustBlock(rootHostname, requestHostname, 'cookie') µm.mustBlock(srcHn, desHn, 'cookie')
) { ) {
modified = true; modified = true;
let headerValue = requestHeaders[headerIndex].value; let headerValue = requestHeaders[headerIndex].value;
@ -257,7 +253,12 @@ var onBeforeSendHeadersHandler = function(details) {
µm.cookieHeaderFoiledCounter++; µm.cookieHeaderFoiledCounter++;
if ( requestType === 'doc' ) { if ( requestType === 'doc' ) {
pageStore.perLoadBlockedRequestCount++; pageStore.perLoadBlockedRequestCount++;
µm.logger.writeOne(tabId, 'net', '', headerValue, 'COOKIE', true); µm.logger.writeOne({
tabId,
srcHn,
header: { name: 'COOKIE', value: headerValue },
change: -1
});
} }
} }
@ -285,23 +286,33 @@ var onBeforeSendHeadersHandler = function(details) {
if ( headerIndex !== -1 ) { if ( headerIndex !== -1 ) {
let headerValue = requestHeaders[headerIndex].value; let headerValue = requestHeaders[headerIndex].value;
if ( headerValue !== '' ) { if ( headerValue !== '' ) {
let toDomain = µmuri.domainFromHostname(requestHostname); let toDomain = µmuri.domainFromHostname(desHn);
if ( toDomain !== '' && toDomain !== µmuri.domainFromURI(headerValue) ) { if ( toDomain !== '' && toDomain !== µmuri.domainFromURI(headerValue) ) {
pageStore.has3pReferrer = true; pageStore.has3pReferrer = true;
if ( µm.tMatrix.evaluateSwitchZ('referrer-spoof', rootHostname) ) { if ( µm.tMatrix.evaluateSwitchZ('referrer-spoof', srcHn) ) {
modified = true; modified = true;
let newValue; let newValue;
if ( details.method === 'GET' ) { if ( details.method === 'GET' ) {
newValue = requestHeaders[headerIndex].value = newValue = requestHeaders[headerIndex].value =
requestScheme + '://' + requestHostname + '/'; desScheme + '://' + desHn + '/';
} else { } else {
requestHeaders.splice(headerIndex, 1); requestHeaders.splice(headerIndex, 1);
} }
if ( pageStore.perLoadBlockedReferrerCount === 0 ) { if ( pageStore.perLoadBlockedReferrerCount === 0 ) {
pageStore.perLoadBlockedRequestCount += 1; pageStore.perLoadBlockedRequestCount += 1;
µm.logger.writeOne(tabId, 'net', '', headerValue, 'REFERER', true); µm.logger.writeOne({
tabId,
srcHn,
header: { name: 'REFERER', value: headerValue },
change: -1
});
if ( newValue !== undefined ) { if ( newValue !== undefined ) {
µm.logger.writeOne(tabId, 'net', '', newValue, 'REFERER', false); µm.logger.writeOne({
tabId,
srcHn,
header: { name: 'REFERER', value: newValue },
change: +1
});
} }
} }
pageStore.perLoadBlockedReferrerCount += 1; pageStore.perLoadBlockedReferrerCount += 1;
@ -329,7 +340,7 @@ var onBeforeSendHeadersHandler = function(details) {
var onHeadersReceived = function(details) { var onHeadersReceived = function(details) {
// Ignore schemes other than 'http...' // Ignore schemes other than 'http...'
var µm = µMatrix, let µm = µMatrix,
tabId = details.tabId, tabId = details.tabId,
requestURL = details.url, requestURL = details.url,
requestType = requestTypeNormalizer[details.type] || 'other'; requestType = requestTypeNormalizer[details.type] || 'other';
@ -340,25 +351,25 @@ var onHeadersReceived = function(details) {
µm.tabContextManager.push(tabId, requestURL); µm.tabContextManager.push(tabId, requestURL);
} }
var tabContext = µm.tabContextManager.lookup(tabId); let tabContext = µm.tabContextManager.lookup(tabId);
if ( tabContext === null ) { return; } if ( tabContext === null ) { return; }
var csp = [], let csp = [],
cspReport = [], cspReport = [],
rootHostname = tabContext.rootHostname, srcHn = tabContext.rootHostname,
requestHostname = µm.URI.hostnameFromURI(requestURL); desHn = µm.URI.hostnameFromURI(requestURL);
// Inline script tags. // Inline script tags.
if ( µm.mustAllow(rootHostname, requestHostname, 'script' ) !== true ) { if ( µm.mustBlock(srcHn, desHn, 'script' ) ) {
csp.push(µm.cspNoInlineScript); csp.push(µm.cspNoInlineScript);
} }
// Inline style tags. // Inline style tags.
if ( µm.mustAllow(rootHostname, requestHostname, 'css' ) !== true ) { if ( µm.mustBlock(srcHn, desHn, 'css' ) ) {
csp.push(µm.cspNoInlineStyle); csp.push(µm.cspNoInlineStyle);
} }
if ( µm.tMatrix.evaluateSwitchZ('no-workers', rootHostname) ) { if ( µm.tMatrix.evaluateSwitchZ('no-workers', srcHn) ) {
csp.push(µm.cspNoWorker); csp.push(µm.cspNoWorker);
} else if ( µm.rawSettings.disableCSPReportInjection === false ) { } else if ( µm.rawSettings.disableCSPReportInjection === false ) {
cspReport.push(µm.cspNoWorker); cspReport.push(µm.cspNoWorker);
@ -391,7 +402,12 @@ var onHeadersReceived = function(details) {
value: cspTotal value: cspTotal
}); });
if ( requestType === 'doc' ) { if ( requestType === 'doc' ) {
µm.logger.writeOne(tabId, 'net', '', cspRight, 'CSP', false); µm.logger.writeOne({
tabId,
srcHn,
header: { name: 'CSP', value: cspRight },
change: +1
});
} }
} }

@ -4,6 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="css/common.css"> <link rel="stylesheet" type="text/css" href="css/common.css">
<link rel="stylesheet" type="text/css" href="css/scope-selector.css">
<link rel="stylesheet" type="text/css" href="css/logger-ui.css"> <link rel="stylesheet" type="text/css" href="css/logger-ui.css">
<link rel="shortcut icon" type="image/png" href="img/icon_16.png"> <link rel="shortcut icon" type="image/png" href="img/icon_16.png">
<title data-i18n="loggerPageName"></title> <title data-i18n="loggerPageName"></title>
@ -36,19 +37,45 @@
</table> </table>
</div> </div>
<div id="popupContainer">
<div><span>&#xf068;</span>&ensp;<span>&#xf00d;</span></div>
</div>
<div style="display: none;"> <div style="display: none;">
<div id="emphasizeTemplate"><span><span></span><b></b><span></span></span></div> <div id="emphasizeTemplate"><span><span></span><b></b><span></span></span></div>
<div id="hiddenTemplate"><span style="display:none;"></span></div> <div id="hiddenTemplate"><span style="display:none;"></span></div>
<div id="ruleEditor" class="modalDialog">
<div class="dialog">
<section class="scopeWidget">
<span class="scope" id="specificScope"><span>&nbsp;</span></span><!--
--><span class="scope" id="globalScope" data-scope="*" data-i18n-tip="matrixGlobalScopeTip"><span><span>&#x2217;</span></span></span>
<div class="ruleEditorToolbar">
<button type="button" class="buttonReload fa tip-anchor-right" data-i18n-tip="matrixReloadButton">&#xf021;</button>
</div>
</section>
<section>
<div class="ruleWidgets"></div>
<div class="ruleEditorToolbar">
<button type="button" class="buttonRevertScope fa scopeRel tip-anchor-right" tabindex="-1" data-i18n-tip="matrixRevertButtonTip">&#xf12d;</button>
<button type="button" class="buttonPersist fa scopeRel tip-anchor-right" data-i18n-tip="matrixPersistButtonTip">&#xf023;<span class="badge"></span></button>
</section>
</section>
</div>
</div>
<div id="ruleRowTemplate" style="display: none;">
<div class="ruleRow"><!--
--><span class="ruleCell" data-type="*">&nbsp;</span><!--
--><span class="ruleCell" data-type>&nbsp;</span><!--
--></div>
</div> </div>
<div id="ruleActionPicker"><div class="allowRule"></div><div class="blockRule"></div></div>
</div>
<script src="lib/punycode.js"></script>
<script src="lib/publicsuffixlist.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script> <script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script> <script src="js/udom.js"></script>
<script src="js/i18n.js"></script> <script src="js/i18n.js"></script>
<script src="js/scope-selector.js"></script>
<script src="js/logger-ui.js"></script> <script src="js/logger-ui.js"></script>
</body> </body>

@ -5,6 +5,7 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/common.css" type="text/css"> <link rel="stylesheet" href="css/common.css" type="text/css">
<link rel="stylesheet" href="css/scope-selector.css" type="text/css">
<link rel="stylesheet" href="css/popup.css" type="text/css"> <link rel="stylesheet" href="css/popup.css" type="text/css">
<title>uMatrix panel</title> <title>uMatrix panel</title>
</head> </head>
@ -119,6 +120,7 @@
<script src="js/vapi-client.js"></script> <script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script> <script src="js/udom.js"></script>
<script src="js/i18n.js"></script> <script src="js/i18n.js"></script>
<script src="js/scope-selector.js"></script>
<script src="js/popup.js"></script> <script src="js/popup.js"></script>
</body> </body>

Loading…
Cancel
Save