1
0
Fork 0
mirror of https://github.com/gorhill/uMatrix.git synced 2024-06-02 02:14:52 +12:00
uMatrix/src/js/popup.js

1166 lines
37 KiB
JavaScript
Raw Normal View History

2014-10-18 08:01:09 +13:00
/*******************************************************************************
µMatrix - a Chromium browser extension to black/white list requests.
Copyright (C) 2014 Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uMatrix
*/
2014-10-20 17:53:13 +13:00
/* global punycode, uDom, messaging */
/* jshint esnext: true, bitwise: false */
2014-10-18 08:01:09 +13:00
/******************************************************************************/
/******************************************************************************/
(function() {
/******************************************************************************/
/******************************************************************************/
2014-10-20 17:53:13 +13:00
// Must be consistent with definitions in matrix.js
const Pale = 0x00;
const Dark = 0x80;
const Transparent = 0;
const Red = 1;
const Green = 2;
const Gray = 3;
const DarkRed = Dark | Red;
const PaleRed = Pale | Red;
const DarkGreen = Dark | Green;
const PaleGreen = Pale | Green;
const DarkGray = Dark | Gray;
const PaleGray = Pale | Gray;
2014-10-18 08:01:09 +13:00
var µMatrix = chrome.extension.getBackgroundPage().µMatrix;
var matrixSnapshot = {};
var groupsSnapshot = [];
var allHostnamesSnapshot = 'do not leave this initial string empty';
var targetTabId;
var targetPageURL;
var targetPageHostname;
var targetPageDomain;
var targetScope = '*';
var matrixCellHotspots = null;
var matrixHeaderPrettyNames = {
'all': '',
'cookie': '',
'css': '',
'image': '',
'plugin': '',
'script': '',
'xhr': '',
'frame': '',
'other': ''
};
2014-10-18 08:01:09 +13:00
/******************************************************************************/
/******************************************************************************/
// https://github.com/gorhill/httpswitchboard/issues/345
messaging.start('popup.js');
var onMessage = function(msg) {
if ( msg.what === 'urlStatsChanged' ) {
if ( targetPageURL === msg.pageURL ) {
makeMenu();
}
}
};
messaging.listen(onMessage);
/******************************************************************************/
/******************************************************************************/
function getPageStats() {
return µMatrix.pageStatsFromTabId(targetTabId);
}
/******************************************************************************/
function getUserSetting(setting) {
return µMatrix.userSettings[setting];
}
function setUserSetting(setting, value) {
messaging.tell({
what: 'userSettings',
name: setting,
value: value
});
}
/******************************************************************************/
2014-10-20 17:53:13 +13:00
function updateMatrixSnapshot() {
var snapshotReady = function(response) {
matrixSnapshot = response;
updateMatrixColors();
updateMatrixBehavior();
updateMatrixButtons();
};
queryMatrixSnapshot(snapshotReady);
2014-10-18 08:01:09 +13:00
}
/******************************************************************************/
// For display purpose, create four distinct groups of rows:
// 1st: page domain's related
// 2nd: whitelisted
// 3rd: graylisted
// 4th: blacklisted
function getGroupStats() {
// Try to not reshuffle groups around while popup is opened if
// no new hostname added.
2014-10-20 17:53:13 +13:00
var latestDomainListSnapshot = Object.keys(matrixSnapshot.rows).sort().join();
if ( latestDomainListSnapshot === allHostnamesSnapshot ) {
return groupsSnapshot;
2014-10-18 08:01:09 +13:00
}
allHostnamesSnapshot = latestDomainListSnapshot;
2014-10-18 08:01:09 +13:00
// First, group according to whether at least one node in the domain
// hierarchy is white or blacklisted
var pageDomain = targetPageDomain;
2014-10-20 17:53:13 +13:00
var rows = matrixSnapshot.rows;
var columnOffsets = matrixSnapshot.headers;
var anyTypeOffset = columnOffsets['*'];
var hostname, domain;
var row, color, groupIndex;
var domainToGroupMap = {};
// First pass: put each domain in a group
for ( hostname in rows ) {
if ( rows.hasOwnProperty(hostname) === false ) {
continue;
}
// '*' is for header, ignore since header is always at the top
2014-10-18 08:01:09 +13:00
if ( hostname === '*' ) {
continue;
}
2014-10-20 17:53:13 +13:00
row = rows[hostname];
domain = row.domain;
// 1st-party always into group 0
if ( domain === pageDomain ) {
domainToGroupMap[domain] = 0;
2014-10-18 08:01:09 +13:00
continue;
}
2014-10-20 17:53:13 +13:00
color = row.temporary[anyTypeOffset];
groupIndex = domainToGroupMap[domain] || 2; // anything overrides 2
// group 1: whitelisted (does not override 0)
if ( color === DarkGreen ) {
domainToGroupMap[domain] = 1;
continue;
}
// group 3: blacklisted (does not override 0, 1)
if ( color === DarkRed ) {
if ( groupIndex === 2 ) {
domainToGroupMap[domain] = 3;
2014-10-18 08:01:09 +13:00
}
2014-10-20 17:53:13 +13:00
continue;
2014-10-18 08:01:09 +13:00
}
2014-10-20 17:53:13 +13:00
// group 2: graylisted (does not override 0, 1, 3)
if ( groupIndex !== 2 ) {
continue;
2014-10-18 08:01:09 +13:00
}
2014-10-20 17:53:13 +13:00
domainToGroupMap[domain] = 2;
}
// Second pass: put each domain in a group
var groups = [ {}, {}, {}, {} ];
var group;
for ( hostname in rows ) {
if ( rows.hasOwnProperty(hostname) === false ) {
continue;
2014-10-18 08:01:09 +13:00
}
2014-10-20 17:53:13 +13:00
if ( hostname === '*' ) {
continue;
2014-10-18 08:01:09 +13:00
}
2014-10-20 17:53:13 +13:00
row = rows[hostname];
domain = row.domain;
groupIndex = domainToGroupMap[domain];
group = groups[groupIndex];
if ( group.hasOwnProperty(domain) === false ) {
group[domain] = {};
2014-10-18 08:01:09 +13:00
}
2014-10-20 17:53:13 +13:00
group[domain][hostname] = true;
2014-10-18 08:01:09 +13:00
}
groupsSnapshot = groups;
2014-10-18 08:01:09 +13:00
return groups;
}
/******************************************************************************/
// helpers
function getTemporaryColor(hostname, type) {
2014-10-20 17:53:13 +13:00
return matrixSnapshot.rows[hostname].temporary[matrixSnapshot.headers[type]];
2014-10-18 08:01:09 +13:00
}
function getPermanentColor(hostname, type) {
2014-10-20 17:53:13 +13:00
return matrixSnapshot.rows[hostname].permanent[matrixSnapshot.headers[type]];
2014-10-18 08:01:09 +13:00
}
function getCellClass(hostname, type) {
2014-10-20 17:53:13 +13:00
return 't' + getTemporaryColor(hostname, type).toString(16) +
' p' + getPermanentColor(hostname, type).toString(16);
2014-10-18 08:01:09 +13:00
}
function getNextAction(hostname, type, leaning) {
2014-10-20 17:53:13 +13:00
var temporaryColor = getTemporaryColor(hostname, type);
var hue = temporaryColor & 0x03;
var saturation = temporaryColor & 0x80;
2014-10-18 08:01:09 +13:00
// special case: root toggle only between two states
if ( type === '*' && hostname === '*' ) {
2014-10-20 17:53:13 +13:00
return hue === Green ? 'blacklist' : 'whitelist';
2014-10-18 08:01:09 +13:00
}
// Lean toward whitelisting?
if ( leaning === 'whitelisting' ) {
2014-10-20 17:53:13 +13:00
if ( saturation !== Dark ) {
2014-10-18 08:01:09 +13:00
return 'whitelist';
}
return 'graylist';
}
// Lean toward blacklisting
2014-10-20 17:53:13 +13:00
if ( saturation !== Dark ) {
2014-10-18 08:01:09 +13:00
return 'blacklist';
}
return 'graylist';
}
/******************************************************************************/
// This is required for when we update the matrix while it is open:
// the user might have collapsed/expanded one or more domains, and we don't
// want to lose all his hardwork.
function getCollapseState(domain) {
var states = getUserSetting('popupCollapseSpecificDomains');
if ( states !== undefined && states[domain] !== undefined ) {
return states[domain];
}
return getUserSetting('popupCollapseDomains');
}
2014-10-19 09:49:06 +13:00
function toggleCollapseState(elem) {
if ( elem.ancestors('#matHead.collapsible').length > 0 ) {
toggleMainCollapseState(elem);
2014-10-18 08:01:09 +13:00
} else {
2014-10-19 09:49:06 +13:00
toggleSpecificCollapseState(elem);
2014-10-18 08:01:09 +13:00
}
}
2014-10-19 09:49:06 +13:00
function toggleMainCollapseState(uelem) {
var matHead = uelem.ancestors('#matHead.collapsible').toggleClass('collapsed');
2014-10-18 18:09:09 +13:00
var collapsed = matHead.hasClassName('collapsed');
uDom('#matList .matSection.collapsible').toggleClass('collapsed', collapsed);
2014-10-18 08:01:09 +13:00
setUserSetting('popupCollapseDomains', collapsed);
var specificCollapseStates = getUserSetting('popupCollapseSpecificDomains') || {};
var domains = Object.keys(specificCollapseStates);
var i = domains.length;
var domain;
while ( i-- ) {
domain = domains[i];
if ( specificCollapseStates[domain] === collapsed ) {
delete specificCollapseStates[domain];
}
}
setUserSetting('popupCollapseSpecificDomains', specificCollapseStates);
}
2014-10-19 09:49:06 +13:00
function toggleSpecificCollapseState(uelem) {
2014-10-18 08:01:09 +13:00
// Remember collapse state forever, but only if it is different
// from main collapse switch.
2014-10-19 09:49:06 +13:00
var section = uelem.ancestors('.matSection.collapsible').toggleClass('collapsed');
2014-10-18 08:01:09 +13:00
var domain = section.prop('domain');
2014-10-18 18:09:09 +13:00
var collapsed = section.hasClassName('collapsed');
2014-10-18 08:01:09 +13:00
var mainCollapseState = getUserSetting('popupCollapseDomains');
var specificCollapseStates = getUserSetting('popupCollapseSpecificDomains') || {};
if ( collapsed !== mainCollapseState ) {
specificCollapseStates[domain] = collapsed;
setUserSetting('popupCollapseSpecificDomains', specificCollapseStates);
} else if ( specificCollapseStates[domain] !== undefined ) {
delete specificCollapseStates[domain];
setUserSetting('popupCollapseSpecificDomains', specificCollapseStates);
}
}
/******************************************************************************/
// Update color of matrix cells(s)
// Color changes when rules change
function updateMatrixColors() {
2014-10-19 09:49:06 +13:00
var cells = uDom('.matrix .matRow.rw > .matCell').removeClass();
2014-10-18 08:01:09 +13:00
var i = cells.length;
var cell;
while ( i-- ) {
2014-10-19 10:11:10 +13:00
cell = cells.nodeAt(i);
2014-10-19 09:49:06 +13:00
cell.className = 'matCell ' + getCellClass(cell.hostname, cell.reqType);
2014-10-18 08:01:09 +13:00
}
}
/******************************************************************************/
// Update behavior of matrix:
// - Whether a section is collapsible or not. It is collapsible if:
// - It has at least one subdomain AND
// - There is no explicit rule anywhere in the subdomain cells AND
// - It is not part of group 3 (blacklisted hostnames)
function updateMatrixBehavior() {
2014-10-19 09:49:06 +13:00
matrixList = matrixList || uDom('#matList');
var sections = matrixList.descendants('.matSection');
2014-10-18 08:01:09 +13:00
var i = sections.length;
var section, subdomainRows, j, subdomainRow;
while ( i-- ) {
2014-10-19 10:11:10 +13:00
section = sections.at(i);
2014-10-19 09:49:06 +13:00
subdomainRows = section.descendants('.l2:not(.g3)');
2014-10-18 08:01:09 +13:00
j = subdomainRows.length;
while ( j-- ) {
2014-10-19 10:11:10 +13:00
subdomainRow = subdomainRows.at(j);
2014-10-19 09:49:06 +13:00
subdomainRow.toggleClass('collapsible', subdomainRow.descendants('.gd,.rd').length === 0);
2014-10-18 08:01:09 +13:00
}
section.toggleClass('collapsible', subdomainRows.filter('.collapsible').length > 0);
}
}
/******************************************************************************/
// handle user interaction with filters
function handleFilter(button, leaning) {
var µm = µMatrix;
// our parent cell knows who we are
2014-10-19 09:49:06 +13:00
var cell = button.ancestors('div.matCell');
2014-10-18 08:01:09 +13:00
var type = cell.prop('reqType');
var hostname = cell.prop('hostname');
var nextAction = getNextAction(hostname, type, leaning);
if ( nextAction === 'blacklist' ) {
µm.blacklistTemporarily(targetScope, hostname, type);
} else if ( nextAction === 'whitelist' ) {
µm.whitelistTemporarily(targetScope, hostname, type);
} else {
µm.graylistTemporarily(targetScope, hostname, type);
}
2014-10-20 17:53:13 +13:00
updateMatrixSnapshot();
2014-10-18 08:01:09 +13:00
}
function handleWhitelistFilter(button) {
handleFilter(button, 'whitelisting');
}
function handleBlacklistFilter(button) {
handleFilter(button, 'blacklisting');
}
/******************************************************************************/
var matrixRowPool = [];
var matrixSectionPool = [];
var matrixGroupPool = [];
var matrixRowTemplate = null;
var matrixList = null;
var startMatrixUpdate = function() {
2014-10-19 09:49:06 +13:00
matrixList = matrixList || uDom('#matList');
2014-10-18 08:01:09 +13:00
matrixList.detach();
2014-10-19 09:49:06 +13:00
var rows = matrixList.descendants('.matRow');
2014-10-18 08:01:09 +13:00
rows.detach();
matrixRowPool = matrixRowPool.concat(rows.toArray());
2014-10-19 09:49:06 +13:00
var sections = matrixList.descendants('.matSection');
2014-10-18 08:01:09 +13:00
sections.detach();
matrixSectionPool = matrixSectionPool.concat(sections.toArray());
2014-10-19 09:49:06 +13:00
var groups = matrixList.descendants('.matGroup');
2014-10-18 08:01:09 +13:00
groups.detach();
matrixGroupPool = matrixGroupPool.concat(groups.toArray());
};
var endMatrixUpdate = function() {
// https://github.com/gorhill/httpswitchboard/issues/246
// If the matrix has no rows, we need to insert a dummy one, invisible,
// to ensure the extension pop-up is properly sized. This is needed because
// the header pane's `position` property is `fixed`, which means it doesn't
// affect layout size, hence the matrix header row will be truncated.
2014-10-20 17:53:13 +13:00
if ( matrixSnapshot.rowCount <= 1 ) {
2014-10-18 08:01:09 +13:00
matrixList.append(createMatrixRow().css('visibility', 'hidden'));
}
updateMatrixBehavior();
matrixList.css('display', '');
2014-10-19 09:49:06 +13:00
matrixList.appendTo('.paneContent');
2014-10-18 08:01:09 +13:00
};
var createMatrixGroup = function() {
var group = matrixGroupPool.pop();
if ( group ) {
2014-10-19 09:49:06 +13:00
return uDom(group).removeClass().addClass('matGroup');
2014-10-18 08:01:09 +13:00
}
2014-10-19 09:49:06 +13:00
return uDom('<div>').addClass('matGroup');
2014-10-18 08:01:09 +13:00
};
var createMatrixSection = function() {
var section = matrixSectionPool.pop();
if ( section ) {
2014-10-19 09:49:06 +13:00
return uDom(section).removeClass().addClass('matSection');
2014-10-18 08:01:09 +13:00
}
2014-10-19 09:49:06 +13:00
return uDom('<div>').addClass('matSection');
2014-10-18 08:01:09 +13:00
};
var createMatrixRow = function() {
var row = matrixRowPool.pop();
if ( row ) {
row.style.visibility = '';
2014-10-19 09:49:06 +13:00
row = uDom(row);
row.descendants('.matCell').removeClass().addClass('matCell');
2014-10-18 08:01:09 +13:00
row.removeClass().addClass('matRow');
return row;
}
if ( matrixRowTemplate === null ) {
2014-10-19 09:49:06 +13:00
matrixRowTemplate = uDom('#templates .matRow');
2014-10-18 08:01:09 +13:00
}
return matrixRowTemplate.clone();
};
/******************************************************************************/
function renderMatrixHeaderRow() {
2014-10-18 18:09:09 +13:00
var matHead = uDom('#matHead.collapsible');
2014-10-18 08:01:09 +13:00
matHead.toggleClass('collapsed', getUserSetting('popupCollapseDomains'));
2014-10-19 09:49:06 +13:00
var cells = matHead.descendants('.matCell');
2014-10-19 10:11:10 +13:00
cells.at(0)
2014-10-18 18:09:09 +13:00
.prop('reqType', '*')
.prop('hostname', '*')
.addClass(getCellClass('*', '*'));
2014-10-19 10:11:10 +13:00
cells.at(1)
2014-10-18 18:09:09 +13:00
.prop('reqType', 'cookie')
.prop('hostname', '*')
.addClass(getCellClass('*', 'cookie'));
2014-10-19 10:11:10 +13:00
cells.at(2)
2014-10-18 18:09:09 +13:00
.prop('reqType', 'css')
.prop('hostname', '*')
.addClass(getCellClass('*', 'css'));
2014-10-19 10:11:10 +13:00
cells.at(3)
2014-10-18 18:09:09 +13:00
.prop('reqType', 'image')
.prop('hostname', '*')
.addClass(getCellClass('*', 'image'));
2014-10-19 10:11:10 +13:00
cells.at(4)
2014-10-18 18:09:09 +13:00
.prop('reqType', 'plugin')
.prop('hostname', '*')
.addClass(getCellClass('*', 'plugin'));
2014-10-19 10:11:10 +13:00
cells.at(5)
2014-10-18 18:09:09 +13:00
.prop('reqType', 'script')
.prop('hostname', '*')
.addClass(getCellClass('*', 'script'));
2014-10-19 10:11:10 +13:00
cells.at(6)
2014-10-18 18:09:09 +13:00
.prop('reqType', 'xhr')
.prop('hostname', '*')
.addClass(getCellClass('*', 'xhr'));
2014-10-19 10:11:10 +13:00
cells.at(7)
2014-10-18 18:09:09 +13:00
.prop('reqType', 'frame')
.prop('hostname', '*')
.addClass(getCellClass('*', 'frame'));
2014-10-19 10:11:10 +13:00
cells.at(8)
2014-10-18 18:09:09 +13:00
.prop('reqType', 'other')
.prop('hostname', '*')
.addClass(getCellClass('*', 'other'));
uDom('#matHead .matRow').css('display', '');
2014-10-18 08:01:09 +13:00
}
/******************************************************************************/
function renderMatrixCellDomain(cell, domain) {
2014-10-19 10:11:10 +13:00
var contents = cell.prop('reqType', '*')
2014-10-18 18:09:09 +13:00
.prop('hostname', domain)
2014-10-18 08:01:09 +13:00
.addClass(getCellClass(domain, '*'))
.contents();
2014-10-19 10:11:10 +13:00
contents.nodeAt(0).textContent = '\u202A' + punycode.toUnicode(domain);
contents.nodeAt(1).textContent = ' ';
2014-10-18 08:01:09 +13:00
}
function renderMatrixCellSubdomain(cell, domain, subomain) {
2014-10-19 10:11:10 +13:00
var contents = cell.prop('reqType', '*')
2014-10-18 18:09:09 +13:00
.prop('hostname', subomain)
2014-10-18 08:01:09 +13:00
.addClass(getCellClass(subomain, '*'))
.contents();
2014-10-19 10:11:10 +13:00
contents.nodeAt(0).textContent = '\u202A' + punycode.toUnicode(subomain.slice(0, subomain.lastIndexOf(domain)-1)) + '.';
contents.nodeAt(1).textContent = punycode.toUnicode(domain);
2014-10-18 08:01:09 +13:00
}
function renderMatrixMetaCellDomain(cell, domain) {
2014-10-19 10:11:10 +13:00
var contents = cell.prop('reqType', '*')
2014-10-18 18:09:09 +13:00
.prop('hostname', domain)
2014-10-18 08:01:09 +13:00
.addClass(getCellClass(domain, '*'))
.contents();
2014-10-19 10:11:10 +13:00
contents.nodeAt(0).textContent = '\u202A\u2217.' + punycode.toUnicode(domain);
contents.nodeAt(1).textContent = ' ';
2014-10-18 08:01:09 +13:00
}
2014-10-20 17:53:13 +13:00
function renderMatrixCellType(cell, hostname, type, count) {
2014-10-19 10:11:10 +13:00
cell.prop('reqType', type)
2014-10-18 18:09:09 +13:00
.prop('hostname', hostname)
2014-10-20 17:53:13 +13:00
.prop('count', count)
2014-10-18 08:01:09 +13:00
.addClass(getCellClass(hostname, type));
2014-10-20 17:53:13 +13:00
if ( count ) {
cell.text(count);
2014-10-18 08:01:09 +13:00
} else {
2014-10-19 10:11:10 +13:00
cell.text('\u00A0');
2014-10-18 08:01:09 +13:00
}
}
2014-10-20 17:53:13 +13:00
function renderMatrixCellTypes(cells, hostname, countName) {
var counts = matrixSnapshot.rows[hostname][countName];
var countIndices = matrixSnapshot.headers;
renderMatrixCellType(cells.at(1), hostname, 'cookie', counts[countIndices.cookie]);
renderMatrixCellType(cells.at(2), hostname, 'css', counts[countIndices.css]);
renderMatrixCellType(cells.at(3), hostname, 'image', counts[countIndices.image]);
renderMatrixCellType(cells.at(4), hostname, 'plugin', counts[countIndices.plugin]);
renderMatrixCellType(cells.at(5), hostname, 'script', counts[countIndices.script]);
renderMatrixCellType(cells.at(6), hostname, 'xhr', counts[countIndices.xhr]);
renderMatrixCellType(cells.at(7), hostname, 'frame', counts[countIndices.frame]);
renderMatrixCellType(cells.at(8), hostname, 'other', counts[countIndices.other]);
2014-10-18 08:01:09 +13:00
}
/******************************************************************************/
function makeMatrixRowDomain(domain) {
var matrixRow = createMatrixRow().addClass('rw');
2014-10-19 09:49:06 +13:00
var cells = matrixRow.descendants('.matCell');
2014-10-19 10:11:10 +13:00
renderMatrixCellDomain(cells.at(0), domain);
2014-10-20 17:53:13 +13:00
renderMatrixCellTypes(cells, domain, 'counts');
2014-10-18 08:01:09 +13:00
return matrixRow;
}
function makeMatrixRowSubdomain(domain, subdomain) {
var matrixRow = createMatrixRow().addClass('rw');
2014-10-19 09:49:06 +13:00
var cells = matrixRow.descendants('.matCell');
2014-10-19 10:11:10 +13:00
renderMatrixCellSubdomain(cells.at(0), domain, subdomain);
2014-10-20 17:53:13 +13:00
renderMatrixCellTypes(cells, subdomain, 'counts');
2014-10-18 08:01:09 +13:00
return matrixRow;
}
2014-10-20 17:53:13 +13:00
function makeMatrixMetaRowDomain(domain) {
2014-10-18 08:01:09 +13:00
var matrixRow = createMatrixRow().addClass('rw');
2014-10-19 09:49:06 +13:00
var cells = matrixRow.descendants('.matCell');
2014-10-19 10:11:10 +13:00
renderMatrixMetaCellDomain(cells.at(0), domain);
2014-10-20 17:53:13 +13:00
renderMatrixCellTypes(cells, domain, 'totals');
2014-10-18 08:01:09 +13:00
return matrixRow;
}
/******************************************************************************/
function renderMatrixMetaCellType(cell, count) {
2014-10-20 17:53:13 +13:00
cell.addClass('t1');
2014-10-18 08:01:09 +13:00
if ( count ) {
2014-10-19 10:11:10 +13:00
cell.text(count);
2014-10-18 08:01:09 +13:00
}
}
2014-10-20 17:53:13 +13:00
function makeMatrixMetaRow(totals) {
var typeOffsets = matrixSnapshot.headers;
2014-10-19 10:11:10 +13:00
var matrixRow = createMatrixRow().at(0).addClass('ro');
2014-10-19 09:49:06 +13:00
var cells = matrixRow.descendants('.matCell');
2014-10-20 17:53:13 +13:00
var contents = cells.at(0).addClass('t81').contents();
2014-10-19 10:11:10 +13:00
contents.nodeAt(0).textContent = ' ';
2014-10-20 17:53:13 +13:00
contents.nodeAt(1).textContent = '\u202A' + totals[typeOffsets['*']] + ' blacklisted hostname(s)';
renderMatrixMetaCellType(cells.at(1), totals[typeOffsets.cookie]);
renderMatrixMetaCellType(cells.at(2), totals[typeOffsets.css]);
renderMatrixMetaCellType(cells.at(3), totals[typeOffsets.image]);
renderMatrixMetaCellType(cells.at(4), totals[typeOffsets.plugin]);
renderMatrixMetaCellType(cells.at(5), totals[typeOffsets.script]);
renderMatrixMetaCellType(cells.at(6), totals[typeOffsets.xhr]);
renderMatrixMetaCellType(cells.at(7), totals[typeOffsets.frame]);
renderMatrixMetaCellType(cells.at(8), totals[typeOffsets.other]);
2014-10-19 09:49:06 +13:00
return matrixRow;
2014-10-18 08:01:09 +13:00
}
/******************************************************************************/
function computeMatrixGroupMetaStats(group) {
2014-10-20 17:53:13 +13:00
var types = Object.keys(matrixSnapshot.headers);
var i = types.length, j;
var totals = new Array(i);
2014-10-18 08:01:09 +13:00
var domains = Object.keys(group);
while ( i-- ) {
2014-10-20 17:53:13 +13:00
totals[i] = 0;
j = domains.length;
2014-10-18 08:01:09 +13:00
while ( j-- ) {
2014-10-20 17:53:13 +13:00
totals[i] += matrixSnapshot.rows[domains[j]].totals[i];
2014-10-18 08:01:09 +13:00
}
}
// TODO: column 0 is supposed to be count of blacklisted hostnames
2014-10-20 17:53:13 +13:00
return totals;
2014-10-18 08:01:09 +13:00
}
/******************************************************************************/
// Compare hostname helper, to order hostname in a logical manner:
// top-most < bottom-most, take into account whether IP address or
// named hostname
function hostnameCompare(a,b) {
// Normalize: most significant parts first
if ( !a.match(/^\d+(\.\d+){1,3}$/) ) {
var aa = a.split('.');
a = aa.slice(-2).concat(aa.slice(0,-2).reverse()).join('.');
}
if ( !b.match(/^\d+(\.\d+){1,3}$/) ) {
var bb = b.split('.');
b = bb.slice(-2).concat(bb.slice(0,-2).reverse()).join('.');
}
return a.localeCompare(b);
}
/******************************************************************************/
function makeMatrixGroup0SectionDomain(domain) {
return makeMatrixRowDomain(domain)
.addClass('g0 l1');
}
function makeMatrixGroup0SectionSubomain(domain, subdomain) {
return makeMatrixRowSubdomain(domain, subdomain)
.addClass('g0 l2');
}
2014-10-20 17:53:13 +13:00
function makeMatrixGroup0SectionMetaDomain(domain) {
return makeMatrixMetaRowDomain(domain).addClass('g0 l1 meta');
2014-10-18 08:01:09 +13:00
}
function makeMatrixGroup0Section(hostnames) {
var domain = hostnames[0];
var domainDiv = createMatrixSection()
.toggleClass('collapsed', getCollapseState(domain))
.prop('domain', domain);
if ( hostnames.length > 1 ) {
2014-10-20 17:53:13 +13:00
makeMatrixGroup0SectionMetaDomain(domain)
2014-10-18 08:01:09 +13:00
.appendTo(domainDiv);
}
makeMatrixGroup0SectionDomain(domain)
.appendTo(domainDiv);
for ( var i = 1; i < hostnames.length; i++ ) {
makeMatrixGroup0SectionSubomain(domain, hostnames[i])
.appendTo(domainDiv);
}
return domainDiv;
}
function makeMatrixGroup0(group) {
var domains = Object.keys(group).sort(hostnameCompare);
if ( domains.length ) {
2014-10-20 17:53:13 +13:00
var groupDiv = createMatrixGroup().addClass('g0');
makeMatrixGroup0Section(Object.keys(group[domains[0]]).sort(hostnameCompare))
2014-10-18 08:01:09 +13:00
.appendTo(groupDiv);
for ( var i = 1; i < domains.length; i++ ) {
2014-10-20 17:53:13 +13:00
makeMatrixGroup0Section(Object.keys(group[domains[i]]).sort(hostnameCompare))
2014-10-18 08:01:09 +13:00
.appendTo(groupDiv);
}
groupDiv.appendTo(matrixList);
}
}
/******************************************************************************/
function makeMatrixGroup1SectionDomain(domain) {
return makeMatrixRowDomain(domain)
.addClass('g1 l1');
}
function makeMatrixGroup1SectionSubomain(domain, subdomain) {
return makeMatrixRowSubdomain(domain, subdomain)
.addClass('g1 l2');
}
2014-10-20 17:53:13 +13:00
function makeMatrixGroup1SectionMetaDomain(domain) {
return makeMatrixMetaRowDomain(domain).addClass('g1 l1 meta');
2014-10-18 08:01:09 +13:00
}
function makeMatrixGroup1Section(hostnames) {
var domain = hostnames[0];
var domainDiv = createMatrixSection()
.toggleClass('collapsed', getCollapseState(domain))
.prop('domain', domain);
if ( hostnames.length > 1 ) {
2014-10-20 17:53:13 +13:00
makeMatrixGroup1SectionMetaDomain(domain).appendTo(domainDiv);
2014-10-18 08:01:09 +13:00
}
makeMatrixGroup1SectionDomain(domain)
.appendTo(domainDiv);
for ( var i = 1; i < hostnames.length; i++ ) {
makeMatrixGroup1SectionSubomain(domain, hostnames[i])
.appendTo(domainDiv);
}
return domainDiv;
}
function makeMatrixGroup1(group) {
var domains = Object.keys(group).sort(hostnameCompare);
if ( domains.length) {
var groupDiv = createMatrixGroup()
.addClass('g1');
2014-10-20 17:53:13 +13:00
makeMatrixGroup1Section(Object.keys(group[domains[0]]).sort(hostnameCompare))
2014-10-18 08:01:09 +13:00
.appendTo(groupDiv);
for ( var i = 1; i < domains.length; i++ ) {
2014-10-20 17:53:13 +13:00
makeMatrixGroup1Section(Object.keys(group[domains[i]]).sort(hostnameCompare))
2014-10-18 08:01:09 +13:00
.appendTo(groupDiv);
}
groupDiv.appendTo(matrixList);
}
}
/******************************************************************************/
function makeMatrixGroup2SectionDomain(domain) {
return makeMatrixRowDomain(domain)
.addClass('g2 l1');
}
function makeMatrixGroup2SectionSubomain(domain, subdomain) {
return makeMatrixRowSubdomain(domain, subdomain)
.addClass('g2 l2');
}
2014-10-20 17:53:13 +13:00
function makeMatrixGroup2SectionMetaDomain(domain) {
return makeMatrixMetaRowDomain(domain).addClass('g2 l1 meta');
2014-10-18 08:01:09 +13:00
}
function makeMatrixGroup2Section(hostnames) {
var domain = hostnames[0];
var domainDiv = createMatrixSection()
.toggleClass('collapsed', getCollapseState(domain))
.prop('domain', domain);
if ( hostnames.length > 1 ) {
2014-10-20 17:53:13 +13:00
makeMatrixGroup2SectionMetaDomain(domain).appendTo(domainDiv);
2014-10-18 08:01:09 +13:00
}
makeMatrixGroup2SectionDomain(domain)
.appendTo(domainDiv);
for ( var i = 1; i < hostnames.length; i++ ) {
makeMatrixGroup2SectionSubomain(domain, hostnames[i])
.appendTo(domainDiv);
}
return domainDiv;
}
function makeMatrixGroup2(group) {
var domains = Object.keys(group).sort(hostnameCompare);
if ( domains.length) {
var groupDiv = createMatrixGroup()
.addClass('g2');
2014-10-20 17:53:13 +13:00
makeMatrixGroup2Section(Object.keys(group[domains[0]]).sort(hostnameCompare))
2014-10-18 08:01:09 +13:00
.appendTo(groupDiv);
for ( var i = 1; i < domains.length; i++ ) {
2014-10-20 17:53:13 +13:00
makeMatrixGroup2Section(Object.keys(group[domains[i]]).sort(hostnameCompare))
2014-10-18 08:01:09 +13:00
.appendTo(groupDiv);
}
groupDiv.appendTo(matrixList);
}
}
/******************************************************************************/
function makeMatrixGroup3SectionDomain(domain) {
return makeMatrixRowDomain(domain)
.addClass('g3 l1');
}
function makeMatrixGroup3SectionSubomain(domain, subdomain) {
return makeMatrixRowSubdomain(domain, subdomain)
.addClass('g3 l2');
}
function makeMatrixGroup3Section(hostnames) {
var domain = hostnames[0];
var domainDiv = createMatrixSection()
.prop('domain', domain);
makeMatrixGroup3SectionDomain(domain)
.appendTo(domainDiv);
for ( var i = 1; i < hostnames.length; i++ ) {
makeMatrixGroup3SectionSubomain(domain, hostnames[i])
.appendTo(domainDiv);
}
return domainDiv;
}
function makeMatrixGroup3(group) {
var domains = Object.keys(group).sort(hostnameCompare);
if ( domains.length === 0 ) {
return;
}
var groupDiv = createMatrixGroup().addClass('g3');
2014-10-18 08:01:09 +13:00
createMatrixSection()
.addClass('g3Meta')
.toggleClass('g3Collapsed', !!getUserSetting('popupHideBlacklisted'))
.appendTo(groupDiv);
makeMatrixMetaRow(computeMatrixGroupMetaStats(group), 'g3')
.appendTo(groupDiv);
2014-10-20 17:53:13 +13:00
makeMatrixGroup3Section(Object.keys(group[domains[0]]).sort(hostnameCompare))
2014-10-18 08:01:09 +13:00
.appendTo(groupDiv);
for ( var i = 1; i < domains.length; i++ ) {
2014-10-20 17:53:13 +13:00
makeMatrixGroup3Section(Object.keys(group[domains[i]]).sort(hostnameCompare))
2014-10-18 08:01:09 +13:00
.appendTo(groupDiv);
}
groupDiv.appendTo(matrixList);
}
/******************************************************************************/
2014-10-20 17:53:13 +13:00
var makeMenu = function() {
2014-10-18 08:01:09 +13:00
var groupStats = getGroupStats();
if ( Object.keys(groupStats).length === 0 ) {
return;
}
// https://github.com/gorhill/httpswitchboard/issues/31
if ( matrixCellHotspots ) {
matrixCellHotspots.detach();
}
renderMatrixHeaderRow();
startMatrixUpdate();
makeMatrixGroup0(groupStats[0]);
makeMatrixGroup1(groupStats[1]);
makeMatrixGroup2(groupStats[2]);
makeMatrixGroup3(groupStats[3]);
endMatrixUpdate();
initScopeCell();
updateMatrixButtons();
2014-10-20 17:53:13 +13:00
};
2014-10-18 08:01:09 +13:00
/******************************************************************************/
// Do all the stuff that needs to be done before building menu et al.
function initMenuEnvironment() {
var prettyNames = matrixHeaderPrettyNames;
2014-10-18 08:01:09 +13:00
var keys = Object.keys(prettyNames);
var i = keys.length;
var cell, key, text;
while ( i-- ) {
key = keys[i];
2014-10-19 09:49:06 +13:00
cell = uDom('#matHead .matCell[data-filter-type="'+ key +'"]');
2014-10-18 08:01:09 +13:00
text = chrome.i18n.getMessage(key + 'PrettyName');
cell.text(text);
prettyNames[key] = text;
}
}
/******************************************************************************/
// Create page scopes for the web page
function createGlobalScope() {
targetScope = '*';
2014-10-19 14:42:06 +13:00
setUserSetting('scopeLevel', '*');
2014-10-20 17:53:13 +13:00
updateMatrixSnapshot();
2014-10-18 08:01:09 +13:00
dropDownMenuHide();
}
function createDomainScope() {
targetScope = targetPageDomain;
2014-10-19 14:42:06 +13:00
setUserSetting('scopeLevel', 'domain');
2014-10-20 17:53:13 +13:00
updateMatrixSnapshot();
2014-10-18 08:01:09 +13:00
dropDownMenuHide();
}
function createSiteScope() {
targetScope = targetPageHostname;
2014-10-19 14:42:06 +13:00
setUserSetting('scopeLevel', 'site');
2014-10-20 17:53:13 +13:00
updateMatrixSnapshot();
2014-10-18 08:01:09 +13:00
dropDownMenuHide();
}
2014-10-19 09:49:06 +13:00
function getClassFromTargetScope() {
if ( targetScope === '*' ) {
return 'tScopeGlobal';
2014-10-18 08:01:09 +13:00
}
2014-10-19 09:49:06 +13:00
if ( targetScope === targetPageDomain ) {
return 'tScopeNarrow';
}
return 'tScopeNarrow';
2014-10-18 08:01:09 +13:00
}
function initScopeCell() {
// It's possible there is no page URL at this point: some pages cannot
// be filtered by µMatrix.
if ( !targetPageURL ) {
return;
}
// Fill in the scope menu entries
if ( targetPageDomain === '' || targetPageDomain === targetPageHostname ) {
2014-10-19 09:49:06 +13:00
uDom('#scopeKeyDomain').css('display', 'none');
2014-10-18 08:01:09 +13:00
} else {
2014-10-19 09:49:06 +13:00
uDom('#scopeKeyDomain').text(targetPageDomain);
2014-10-18 08:01:09 +13:00
}
2014-10-19 09:49:06 +13:00
uDom('#scopeKeySite').text(targetPageHostname);
2014-10-19 14:42:06 +13:00
var scopeLevel = getUserSetting('scopeLevel');
if ( scopeLevel === 'site' ) {
targetScope = targetPageHostname;
} else if ( scopeLevel === 'domain' ) {
targetScope = targetPageDomain;
} else {
targetScope = '*';
}
2014-10-18 08:01:09 +13:00
updateScopeCell();
}
function updateScopeCell() {
2014-10-18 18:09:09 +13:00
uDom('body')
2014-10-19 09:49:06 +13:00
.removeClass('tScopeGlobal tScopeNarrow')
.addClass(getClassFromTargetScope());
2014-10-18 18:09:09 +13:00
uDom('#scopeCell').text(targetScope.replace('*', '\u2217'));
2014-10-18 08:01:09 +13:00
}
/******************************************************************************/
function updateMtxbutton() {
var µm = µMatrix;
var masterSwitch = matrixSnapshot.tSwitch;
2014-10-18 08:01:09 +13:00
var pageStats = getPageStats();
var count = pageStats ? pageStats.requestStats.blocked.all : '';
2014-10-19 09:49:06 +13:00
var button = uDom('#buttonMtxFiltering');
2014-10-18 08:01:09 +13:00
button.toggleClass('disabled', !masterSwitch);
2014-10-19 09:49:06 +13:00
button.descendants('span.badge').text(µm.formatCount(count));
button.attr('data-tip', button.attr('data-tip').replace('{{count}}', count));
uDom('body').toggleClass('powerOff', !masterSwitch);
2014-10-18 08:01:09 +13:00
}
function toggleMtxFiltering() {
var µm = µMatrix;
µm.toggleTemporaryMtxFiltering(targetScope);
2014-10-20 17:53:13 +13:00
updateMatrixSnapshot();
2014-10-18 08:01:09 +13:00
}
/******************************************************************************/
function updatePersistButton() {
var diffCount = matrixSnapshot.diff.length;
2014-10-19 09:49:06 +13:00
var button = uDom('#buttonPersist');
2014-10-18 08:01:09 +13:00
button.contents()
.filter(function(){return this.nodeType===3;})
2014-10-19 09:49:06 +13:00
.first()
.text(diffCount > 0 ? '\uf13e' : '\uf023');
button.descendants('span.badge').text(diffCount > 0 ? diffCount : '');
var disabled = diffCount === 0;
2014-10-18 08:01:09 +13:00
button.toggleClass('disabled', disabled);
2014-10-18 18:09:09 +13:00
uDom('#buttonRevertScope').toggleClass('disabled', disabled);
2014-10-18 08:01:09 +13:00
}
/******************************************************************************/
function persistMatrix() {
var request = {
what: 'applyDiffToPermanentMatrix',
diff: matrixSnapshot.diff
};
messaging.ask(request, updateMatrixSnapshot);
2014-10-18 08:01:09 +13:00
}
/******************************************************************************/
// rhill 2014-03-12: revert completely ALL changes related to the
// current page, including scopes.
function revertMatrix() {
var request = {
what: 'applyDiffToTemporaryMatrix',
diff: matrixSnapshot.diff
};
messaging.ask(request, updateMatrixSnapshot);
2014-10-18 08:01:09 +13:00
}
/******************************************************************************/
// Buttons which are affected by any changes in the matrix
function updateMatrixButtons() {
updateScopeCell();
updateMtxbutton();
updatePersistButton();
}
/******************************************************************************/
function revertAll() {
var request = {
what: 'revertTemporaryMatrix'
};
messaging.ask(request, updateMatrixSnapshot);
2014-10-18 08:01:09 +13:00
}
/******************************************************************************/
function buttonReloadHandler() {
messaging.tell({
what: 'forceReloadTab',
pageURL: targetPageURL
});
}
/******************************************************************************/
function mouseenterMatrixCellHandler() {
matrixCellHotspots.appendTo(this);
}
function mouseleaveMatrixCellHandler() {
matrixCellHotspots.detach();
}
/******************************************************************************/
function gotoExtensionURL() {
2014-10-18 18:09:09 +13:00
var url = this.getAttribute('data-extension-url');
2014-10-18 08:01:09 +13:00
if ( url ) {
messaging.tell({ what: 'gotoExtensionURL', url: url });
}
}
/******************************************************************************/
function gotoExternalURL() {
2014-10-18 18:09:09 +13:00
var url = this.getAttribute('data-external-url');
2014-10-18 08:01:09 +13:00
if ( url ) {
messaging.tell({ what: 'gotoURL', url: url });
}
}
/******************************************************************************/
function dropDownMenuShow() {
2014-10-19 09:49:06 +13:00
uDom(this).next('.dropdown-menu').addClass('show');
2014-10-18 08:01:09 +13:00
}
function dropDownMenuHide() {
2014-10-18 18:09:09 +13:00
uDom('.dropdown-menu').removeClass('show');
2014-10-18 08:01:09 +13:00
}
/******************************************************************************/
// Because chrome.tabs.query() is async
2014-10-20 17:53:13 +13:00
var onMatrixSnapshotReady = function(response) {
matrixSnapshot = response;
targetTabId = response.tabId;
targetPageURL = response.url;
targetPageHostname = response.hostname;
targetPageDomain = response.domain;
2014-10-18 08:01:09 +13:00
// Now that tabId and pageURL are set, we can build our menu
initMenuEnvironment();
makeMenu();
// After popup menu is built, check whether there is a non-empty matrix
if ( !targetPageURL ) {
2014-10-19 09:49:06 +13:00
uDom('#matHead').remove();
uDom('#toolbarLeft').remove();
2014-10-18 08:01:09 +13:00
// https://github.com/gorhill/httpswitchboard/issues/191
2014-10-18 18:09:09 +13:00
uDom('#noNetTrafficPrompt').text(chrome.i18n.getMessage('matrixNoNetTrafficPrompt'));
uDom('#noNetTrafficPrompt').css('display', '');
2014-10-18 08:01:09 +13:00
}
};
2014-10-20 17:53:13 +13:00
/******************************************************************************/
var queryMatrixSnapshot = function(callback) {
var request = {
what: 'matrixSnapshot',
tabId: targetTabId
};
var onTabsReceived = function(tabs) {
if ( tabs.length === 0 ) {
return;
}
request.tabId = targetTabId = tabs[0].id;
messaging.ask(request, callback);
};
if ( targetTabId === undefined ) {
chrome.tabs.query({ active: true, currentWindow: true }, onTabsReceived);
} else {
messaging.ask(request, callback);
2014-10-20 17:53:13 +13:00
}
};
2014-10-18 08:01:09 +13:00
/******************************************************************************/
// Make menu only when popup html is fully loaded
2014-10-19 09:49:06 +13:00
uDom.onLoad(function() {
queryMatrixSnapshot(onMatrixSnapshotReady);
2014-10-18 08:01:09 +13:00
// Below is UI stuff which is not key to make the menu, so this can
// be done without having to wait for a tab to be bound to the menu.
// Matrix appearance
2014-10-19 09:49:06 +13:00
uDom('body').css('font-size', getUserSetting('displayTextSize'));
uDom('body').toggleClass('colorblind', getUserSetting('colorBlindFriendly') === true);
2014-10-18 08:01:09 +13:00
// We reuse for all cells the one and only cell hotspots.
2014-10-19 09:49:06 +13:00
uDom('#whitelist').on('click', function() {
handleWhitelistFilter(uDom(this));
2014-10-18 08:01:09 +13:00
return false;
});
2014-10-19 09:49:06 +13:00
uDom('#blacklist').on('click', function() {
handleBlacklistFilter(uDom(this));
2014-10-18 08:01:09 +13:00
return false;
});
2014-10-19 09:49:06 +13:00
uDom('#domainOnly').on('click', function() {
toggleCollapseState(uDom(this));
2014-10-18 08:01:09 +13:00
return false;
});
2014-10-19 09:49:06 +13:00
matrixCellHotspots = uDom('#cellHotspots').detach();
uDom('body')
2014-10-18 08:01:09 +13:00
.on('mouseenter', '.matCell', mouseenterMatrixCellHandler)
.on('mouseleave', '.matCell', mouseleaveMatrixCellHandler);
2014-10-19 09:49:06 +13:00
uDom('#scopeKeyGlobal').on('click', createGlobalScope);
uDom('#scopeKeyDomain').on('click', createDomainScope);
uDom('#scopeKeySite').on('click', createSiteScope);
uDom('#buttonMtxFiltering').on('click', toggleMtxFiltering);
uDom('#buttonPersist').on('click', persistMatrix);
uDom('#buttonRevertScope').on('click', revertMatrix);
2014-10-19 09:49:06 +13:00
uDom('#buttonRevertAll').on('click', revertAll);
uDom('#buttonReload').on('click', buttonReloadHandler);
uDom('.extensionURL').on('click', gotoExtensionURL);
uDom('.externalURL').on('click', gotoExternalURL);
uDom('body').on('click', '.dropdown-menu-button', dropDownMenuShow);
uDom('body').on('click', '.dropdown-menu-capture', dropDownMenuHide);
uDom('#matList').on('click', '.g3Meta', function() {
2014-10-18 18:09:09 +13:00
var collapsed = uDom(this)
.toggleClass('g3Collapsed')
.hasClass('g3Collapsed');
setUserSetting('popupHideBlacklisted', collapsed);
2014-10-18 08:01:09 +13:00
});
});
/******************************************************************************/
})();