You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

415 lines
14 KiB

/*******************************************************************************
µ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
*/
/* global messaging, uDom */
/******************************************************************************/
(function() {
/******************************************************************************/
messaging.start('info.js');
var targetUrl = 'all';
var maxRequests = 500;
var cachedUserSettings = {};
/******************************************************************************/
// Get a list of latest net requests
function updateRequestData(callback) {
var onResponseReceived = function(r) {
var requests = [];
for ( var pageURL in r ) {
if ( r.hasOwnProperty(pageURL) === false ) {
continue;
}
requests = requests.concat(r[pageURL]);
}
requests = requests
.sort(function(a,b){return b.when-a.when;})
.slice(0, maxRequests);
callback(requests);
};
var request = {
what: 'getRequestLogs',
pageURL: targetUrl !== 'all' ? targetUrl : null
};
messaging.ask(request, onResponseReceived);
}
/******************************************************************************/
function clearRequestData() {
var request = {
what: 'clearRequestLogs',
pageURL: targetUrl !== 'all' ? targetUrl : null
};
messaging.tell(request);
}
/******************************************************************************/
function renderNumber(value) {
if ( isNaN(value) ) {
return '0';
}
return value.toLocaleString();
}
/******************************************************************************/
function renderNumbers(set) {
var keys = Object.keys(set);
var i = keys.length;
var key;
while ( i-- ) {
key = keys[i];
uDom(key).text(renderNumber(set[key]));
}
}
/******************************************************************************/
var renderLocalized = function(id, map) {
var uElem = uDom('#' + id);
var msg = chrome.i18n.getMessage(id);
for ( var k in map ) {
if ( map.hasOwnProperty(k) === false ) {
continue;
}
msg = msg.replace('{{' + k + '}}', map[k]);
}
uElem.html(msg);
};
/******************************************************************************/
function renderPageUrls() {
var onResponseReceived = function(r) {
var i, n;
var select = uDom('#selectPageUrls');
// Remove whatever was put there in a previous call
uDom('#selectPageUrls > option').remove();
var builtinOptions = uDom('#selectPageUrlsTemplate > option');
n = builtinOptions.length;
for ( i = 0; i < n; i++ ) {
option = builtinOptions.at(i).clone();
if ( option.val() === targetUrl ) {
option.attr('selected', true);
}
select.append(option);
}
var pageURLs = r.pageURLs.sort();
var pageURL, option;
n = pageURLs.length;
for ( i = 0; i < n; i++ ) {
pageURL = pageURLs[i];
// Behind-the-scene entry is always present, no need to recreate it
if ( pageURL === r.behindTheSceneURL ) {
continue;
}
option = uDom('<option>');
option.val(pageURL);
option.text(pageURL);
if ( pageURL === targetUrl ) {
option.attr('selected', true);
}
select.append(option);
}
// Deselect whatever is currently selected
//uDom('#selectPageUrls > option:selected').prop('selected', false);
// Select whatever needs to be selected
//uDom('#selectPageUrls > option[value="'+targetUrl+'"]').prop('selected', true);
};
messaging.ask({ what: 'getPageURLs' }, onResponseReceived);
}
/******************************************************************************/
function renderStats() {
var onResponseReceived = function(r) {
if ( !r.pageNetStats ) {
targetUrl = 'all';
}
var requestStats = targetUrl === 'all' ? r.globalNetStats : r.pageNetStats;
var blockedStats = requestStats.blocked;
var allowedStats = requestStats.allowed;
renderLocalized('statsPageCookieHeadersFoiled', { count: renderNumber(r.cookieHeaderFoiledCounter) });
renderLocalized('statsPageRefererHeadersFoiled', { count: renderNumber(r.refererHeaderFoiledCounter) });
renderLocalized('statsPageHyperlinkAuditingFoiled', { count: renderNumber(r.hyperlinkAuditingFoiledCounter) });
renderLocalized('statsPageCookiesRemoved', { count: renderNumber(r.cookieRemovedCounter) });
renderLocalized('statsPageLocalStoragesCleared', { count: renderNumber(r.localStorageRemovedCounter) });
renderLocalized('statsPageBrowserCacheCleared', { count: renderNumber(r.browserCacheClearedCounter) });
renderNumbers({
'#blockedAllCount': requestStats.blocked.all,
'#blockedMainFrameCount': blockedStats.doc,
'#blockedCookieCount': blockedStats.cookie,
'#blockedStylesheetCount': blockedStats.css,
'#blockedImageCount': blockedStats.image,
'#blockedObjectCount': blockedStats.plugin,
'#blockedScriptCount': blockedStats.script,
'#blockedXHRCount': blockedStats.xhr,
'#blockedSubFrameCount': blockedStats.frame,
'#blockedOtherCount': blockedStats.other,
'#allowedAllCount': allowedStats.all,
'#allowedMainFrameCount': allowedStats.doc,
'#allowedCookieCount': allowedStats.cookie,
'#allowedStylesheetCount': allowedStats.css,
'#allowedImageCount': allowedStats.image,
'#allowedObjectCount': allowedStats.plugin,
'#allowedScriptCount': allowedStats.script,
'#allowedXHRCount': allowedStats.xhr,
'#allowedSubFrameCount': allowedStats.frame,
'#allowedOtherCount': allowedStats.other
});
// because some i18n messages may contain links
uDom('a').attr('target', '_blank');
};
messaging.ask({
what: 'getStats',
pageURL: targetUrl === 'all' ? null : targetUrl
},
onResponseReceived
);
}
/******************************************************************************/
function renderRequestRow(row, request) {
row.attr('id', '');
row.css('display', '');
row.removeClass();
if ( request.block !== false ) {
row.addClass('blocked-true');
} else {
row.addClass('blocked-false');
}
row.addClass('type-' + request.type);
var cells = row.descendants('td');
// when
var when = new Date(request.when);
cells.at(0).text(when.toLocaleTimeString());
// request type
cells.at(1).text(request.type);
// Well I got back full control since not using Tempo.js, I can now
// generate smarter hyperlinks, that is, not hyperlinking fake
// request URLs, which are recognizable with their curly braces inside.
var a = cells.at(2).descendants('a');
if ( request.url.search('{') < 0 ) {
a.attr('href', request.url);
a.css('display', '');
} else {
a.css('display', 'none');
}
// request URL
cells.at(3).text(request.url);
}
/******************************************************************************/
var renderRequests = function(requests) {
var table = uDom('#requestsTable');
var i, row;
var rowTemplate = table.descendants('#requestRowTemplate').first();
// Reuse whatever rows is already in there.
var rows = table.descendants('tr:not(.ro)');
var n = Math.min(requests.length, rows.length);
for ( i = 0; i < n; i++ ) {
renderRequestRow(rows.at(i), requests[i]);
}
// Unhide reused rows
rows.subset(0, n).removeClass('unused');
// Hide extra rows
rows.subset(n).addClass('unused');
// Create new rows to receive what is left
n = requests.length;
for ( ; i < n; i++ ) {
row = rowTemplate.clone();
renderRequestRow(row, requests[i]);
row.insertBefore(rowTemplate);
}
syncWithFilters();
};
/******************************************************************************/
var updateRequests = function() {
updateRequestData(renderRequests);
};
/******************************************************************************/
var clearRequests = function() {
clearRequestData();
renderRequests([]);
};
/******************************************************************************/
function changeUserSettings(name, value) {
cachedUserSettings[name] = value;
messaging.tell({
what: 'userSettings',
name: name,
value: value
});
}
/******************************************************************************/
function changeValueHandler(elem, setting, min, max) {
var oldVal = cachedUserSettings[setting];
var newVal = Math.round(parseFloat(elem.val()));
if ( typeof newVal !== 'number' ) {
newVal = oldVal;
} else {
newVal = Math.max(newVal, min);
newVal = Math.min(newVal, max);
}
elem.val(newVal);
if ( newVal !== oldVal ) {
changeUserSettings(setting, newVal);
}
}
/******************************************************************************/
function changeFilterHandler() {
// Save new state of filters in user settings
// Initialize request filters as per user settings:
// https://github.com/gorhill/httpswitchboard/issues/49
var statsFilters = cachedUserSettings.statsFilters;
uDom('input[id^="show-"][type="checkbox"]').forEach(function(input) {
statsFilters[input.attr('id')] = !!input.prop('checked');
});
changeUserSettings('statsFilters', statsFilters);
syncWithFilters();
}
/******************************************************************************/
// Synchronize list of net requests with filter states
function syncWithFilters() {
var blocked = ['blocked','allowed'];
var type = ['doc','cookie','css','image','plugin','script','xhr','frame','other'];
var i = blocked.length;
var j;
var display, selector;
while ( i-- ) {
j = type.length;
while ( j-- ) {
display = uDom('#show-' + blocked[i]).prop('checked') &&
uDom('#show-' + type[j]).prop('checked') ? '' : 'none';
selector = '.blocked-' + (blocked[i] === 'blocked') + '.type-' + type[j];
uDom(selector).css('display', display);
}
}
}
/******************************************************************************/
var renderTransientTimer;
function renderTransientData(internal) {
// This is in case this function is not called from timeout event
if ( internal && renderTransientTimer ) {
clearTimeout(renderTransientTimer);
}
renderPageUrls();
renderStats();
renderTransientTimer = setTimeout(renderTransientData, 10000); // every 10s
}
/******************************************************************************/
function targetUrlChangeHandler() {
targetUrl = this[this.selectedIndex].value;
renderStats();
updateRequests();
}
/******************************************************************************/
function prepareToDie() {
changeValueHandler(uDom('#max-logged-requests'), 'maxLoggedRequests', 0, 999);
}
/******************************************************************************/
var installEventHandlers = function() {
uDom('#refreshRequests').on('click', updateRequests);
uDom('#clearRequests').on('click', clearRequests);
uDom('input[id^="show-"][type="checkbox"]').on('change', changeFilterHandler);
uDom('#selectPageUrls').on('change', targetUrlChangeHandler);
uDom('#max-logged-requests').on('change', function(){ changeValueHandler(uDom(this), 'maxLoggedRequests', 0, 999); });
// https://github.com/gorhill/httpswitchboard/issues/197
window.addEventListener('beforeunload', prepareToDie);
};
/******************************************************************************/
uDom.onLoad(function(){
// Initialize request filters as per user settings:
// https://github.com/gorhill/httpswitchboard/issues/49
var onResponseReceived = function(userSettings) {
// cache a copy
cachedUserSettings = userSettings;
// init ui as per user settings
uDom('#max-logged-requests').val(userSettings.maxLoggedRequests);
var statsFilters = userSettings.statsFilters;
uDom('input[id^="show-"][type="checkbox"]').forEach(function(input) {
var filter = statsFilters[input.attr('id')];
input.prop('checked', filter === undefined || filter === true);
});
installEventHandlers();
};
messaging.ask({ what: 'getUserSettings' }, onResponseReceived);
renderTransientData(true);
updateRequests();
});
/******************************************************************************/
})();