+
You can change the maximum number of most recent HTTP requests to log (currently set at per page) on the Settings page.
Show: Pages
diff --git a/js/async.js b/js/async.js
index 51b31e9..0a5ef1e 100644
--- a/js/async.js
+++ b/js/async.js
@@ -188,11 +188,7 @@ function onMessageHandler(request, sender, callback) {
case 'userSettings':
if ( typeof request.name === 'string' && request.name !== '' ) {
- if ( HTTPSB.userSettings[request.name] !== undefined && request.value !== undefined ) {
- HTTPSB.userSettings[request.name] = request.value;
- }
- response = HTTPSB.userSettings[request.name];
- saveUserSettings();
+ response = changeUserSettings(request.name, request.value);
}
break;
@@ -212,7 +208,7 @@ function onMessageHandler(request, sender, callback) {
}
}
- if ( callback ) {
+ if ( response && callback ) {
callback(response);
}
}
diff --git a/js/background.js b/js/background.js
index 802de91..c3ff50b 100644
--- a/js/background.js
+++ b/js/background.js
@@ -32,7 +32,10 @@ var HTTPSB = {
displayTextSize: '13px',
popupHideBlacklisted: false,
popupCollapseDomains: false,
- popupCollapseSpecificDomains: {}
+ popupCollapseSpecificDomains: {},
+ maxLoggedRequests: 250,
+ statsFilters: {
+ }
},
runtimeId: 1,
@@ -60,8 +63,11 @@ var HTTPSB = {
// tabs are used to redirect stats collection to a specific url stats
// structure.
- pageUrlToTabId: { },
- tabIdToPageUrl: { },
+ pageUrlToTabId: {},
+ tabIdToPageUrl: {},
+
+ // Power switch to disengage HTTPSB
+ off: false,
// page url => permission scope
temporaryScopes: null,
@@ -69,7 +75,7 @@ var HTTPSB = {
// Current entries from remote blacklists --
// just hostnames, '*/' is implied, this saves significantly on memory.
- blacklistReadonly: { },
+ blacklistReadonly: {},
// https://github.com/gorhill/httpswitchboard/issues/19
excludeRegex: /^https?:\/\/chrome\.google\.com\/(extensions|webstore)/,
diff --git a/js/cookies.js b/js/cookies.js
index a0d23a9..c566147 100644
--- a/js/cookies.js
+++ b/js/cookies.js
@@ -34,12 +34,13 @@ var cookieHunter = {
// rhill 2013-10-19: pageStats could be nil, for example, this can
// happens if a file:// ... makes an xmlHttpRequest
if ( pageStats ) {
- this.queuePageRecord[pageUrlFromPageStats(pageStats)] = pageStats;
+ var pageURL = pageUrlFromPageStats(pageStats);
+ cookieHunter.queuePageRecord[pageURL] = pageStats;
asyncJobQueue.add(
'cookieHunterPageRecord',
null,
function() { cookieHunter.processPageRecord(); },
- 500,
+ 1000,
false);
}
},
@@ -51,7 +52,8 @@ var cookieHunter = {
// rhill 2013-10-19: pageStats could be nil, for example, this can
// happens if a file:// ... makes an xmlHttpRequest
if ( pageStats ) {
- this.queuePageRemove[pageUrlFromPageStats(pageStats)] = pageStats;
+ var pageURL = pageUrlFromPageStats(pageStats);
+ cookieHunter.queuePageRemove[pageURL] = pageStats;
asyncJobQueue.add(
'cookieHunterPageRemove',
null,
@@ -63,40 +65,38 @@ var cookieHunter = {
// Candidate for removal
remove: function(cookie) {
- this.queueRemove[cookie.url + '|' + cookie.name] = cookie;
+ cookieHunter.queueRemove[cookie.url + '|' + cookie.name] = cookie;
},
processPageRecord: function() {
// record cookies from a specific page
- var pageUrls = Object.keys(this.queuePageRecord);
+ var pageUrls = Object.keys(cookieHunter.queuePageRecord);
var i = pageUrls.length;
while ( i-- ) {
- this._processPageRecord(pageUrls[i]);
+ cookieHunter._processPageRecord(pageUrls[i]);
}
},
_processPageRecord: function(pageUrl) {
- var me = this;
chrome.cookies.getAll({}, function(cookies) {
- me._hunt(me.queuePageRecord[pageUrl], cookies, true);
- delete me.queuePageRecord[pageUrl];
+ cookieHunter._hunt(cookieHunter.queuePageRecord[pageUrl], cookies, true);
+ delete cookieHunter.queuePageRecord[pageUrl];
});
},
processPageRemove: function() {
// erase cookies from a specific page
- var pageUrls = Object.keys(this.queuePageRemove);
+ var pageUrls = Object.keys(cookieHunter.queuePageRemove);
var i = pageUrls.length;
while ( i-- ) {
- this._processPageRemove(pageUrls[i]);
+ cookieHunter._processPageRemove(pageUrls[i]);
}
},
_processPageRemove: function(pageUrl) {
- var me = this;
chrome.cookies.getAll({}, function(cookies) {
- me._hunt(me.queuePageRemove[pageUrl], cookies, false);
- delete me.queuePageRemove[pageUrl];
+ cookieHunter._hunt(cookieHunter.queuePageRemove[pageUrl], cookies, false);
+ delete cookieHunter.queuePageRemove[pageUrl];
});
},
@@ -108,7 +108,6 @@ var cookieHunter = {
if ( !httpsb.userSettings.deleteCookies ) {
return;
}
- var me = this;
chrome.cookies.getAll({}, function(cookies) {
// quickProfiler.start();
var i = cookies.length;
@@ -121,7 +120,7 @@ var cookieHunter = {
cookieUrl = (cookie.secure ? 'https://' : 'http://') + domain + cookie.path;
// be mindful of https://github.com/gorhill/httpswitchboard/issues/19
if ( !httpsb.excludeRegex.test(cookieUrl) ) {
- me.remove({
+ cookieHunter.remove({
url: cookieUrl,
domain: cookie.domain,
name: cookie.name
@@ -139,14 +138,14 @@ var cookieHunter = {
// Remove only some of the cookies which are candidate for removal:
// who knows, maybe a user has 1000s of cookies sitting in his
// browser...
- var cookieKeys = Object.keys(this.queueRemove);
+ var cookieKeys = Object.keys(cookieHunter.queueRemove);
if ( cookieKeys.length > 50 ) {
cookieKeys = cookieKeys.sort(function(){return Math.random() < Math.random();}).splice(0, 50);
}
var cookieKey, cookie;
while ( cookieKey = cookieKeys.pop() ) {
- cookie = this.queueRemove[cookieKey];
- delete this.queueRemove[cookieKey];
+ cookie = cookieHunter.queueRemove[cookieKey];
+ delete cookieHunter.queueRemove[cookieKey];
// Just in case setting was changed after cookie was put in queue.
if ( !httpsb.userSettings.deleteCookies ) {
continue;
@@ -154,7 +153,7 @@ var cookieHunter = {
// Ensure cookie is not allowed on ALL current web pages: It can
// happen that a cookie is blacklisted on one web page while
// being whitelisted on another (because of per-page permissions).
- if ( this._dontRemoveCookie(cookie) ) {
+ if ( cookieHunter._dontRemoveCookie(cookie) ) {
// console.debug('HTTP Switchboard > cookieHunter.processRemove(): Will NOT remove cookie %s/{%s}', cookie.url, cookie.name);
continue;
}
@@ -204,7 +203,7 @@ var cookieHunter = {
// Leave alone cookies from behind-the-scene requests if
// behind-the-scene processing is disabled.
if ( block && deleteCookies && (pageUrl !== httpsb.behindTheSceneURL || httpsb.userSettings.processBehindTheSceneRequests) ) {
- this.remove({
+ cookieHunter.remove({
url: rootUrl + cookie.path,
domain: cookie.domain,
name: cookie.name
diff --git a/js/httpsb.js b/js/httpsb.js
index 8512332..6e0319a 100644
--- a/js/httpsb.js
+++ b/js/httpsb.js
@@ -131,6 +131,9 @@ HTTPSB.scopePageExists = function(url) {
/******************************************************************************/
HTTPSB.evaluate = function(src, type, hostname) {
+ if ( this.off ) {
+ return 'gpt';
+ }
return this.temporaryScopes.evaluate(src, type, hostname);
};
diff --git a/js/info.js b/js/info.js
index f3e1cf7..41ed13b 100644
--- a/js/info.js
+++ b/js/info.js
@@ -52,12 +52,12 @@ function requestDetails(url, type, when, blocked) {
function updateRequestData() {
var requests = [];
var pageUrls = targetUrl === 'All' ?
- Object.keys(gethttpsb().pageStats) :
- [targetUrl];
+ Object.keys(gethttpsb().pageStats) :
+ [targetUrl];
var iPageUrl, nPageUrls, pageUrl;
var reqKeys, iReqKey, nReqKeys, reqKey;
var pageStats, pageRequests;
- var i, entry;
+ var pos, reqURL, reqType, entry;
nPageUrls = pageUrls.length;
for ( iPageUrl = 0; iPageUrl < nPageUrls; iPageUrl++ ) {
@@ -68,18 +68,24 @@ function updateRequestData() {
continue;
}
pageRequests = pageStats.requests;
- reqKeys = Object.keys(pageRequests);
+ reqKeys = pageRequests.getLoggedRequests();
nReqKeys = reqKeys.length;
for ( iReqKey = 0; iReqKey < nReqKeys; iReqKey++ ) {
reqKey = reqKeys[iReqKey];
- entry = pageRequests[reqKey];
- i = reqKey.indexOf('#');
- // Using parseFloat because of
- // http://jsperf.com/performance-of-parseint
+ if ( !reqKey ) {
+ continue;
+ }
+ pos = reqKey.indexOf('#');
+ reqURL = reqKey.slice(0, pos);
+ reqType = reqKey.slice(pos + 1);
+ entry = pageRequests.getLoggedRequestEntry(reqURL, reqType);
+ if ( !entry ) {
+ continue;
+ }
requests.push(new requestDetails(
- reqKey.slice(0, i),
+ reqURL,
entry.when,
- reqKey.slice(i+1),
+ reqType,
entry.blocked
));
}
@@ -205,7 +211,8 @@ function renderStats() {
'#allowedScriptCount': allowedStats.script,
'#allowedXHRCount': allowedStats.xmlhttprequest,
'#allowedSubFrameCount': allowedStats.sub_frame,
- '#allowedOtherCount': allowedStats.other
+ '#allowedOtherCount': allowedStats.other,
+ '#maxLoggedRequests': httpsb.userSettings.maxLoggedRequests
});
}
@@ -240,7 +247,7 @@ function renderRequestRow(row, request) {
$(cells[3]).text(request.url);
}
-/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+/*----------------------------------------------------------------------------*/
function renderRequests() {
var table = $('#requestsTable tbody');
@@ -275,6 +282,27 @@ function renderRequests() {
/******************************************************************************/
+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 = gethttpsb().userSettings.statsFilters;
+ $('input[id^="show-"][type="checkbox"]').each(function() {
+ var input = $(this);
+ statsFilters[input.attr('id')] = !!input.prop('checked');
+ });
+
+ chrome.runtime.sendMessage({
+ what: 'userSettings',
+ name: 'statsFilters',
+ value: statsFilters
+ });
+
+ syncWithFilters();
+}
+
+/******************************************************************************/
+
// Synchronize list of net requests with filter states
function syncWithFilters() {
@@ -322,9 +350,18 @@ function initAll() {
$('#version').html(gethttpsb().manifest.version);
$('a:not([target])').prop('target', '_blank');
+ // Initialize request filters as per user settings:
+ // https://github.com/gorhill/httpswitchboard/issues/49
+ $('input[id^="show-"][type="checkbox"]').each(function() {
+ var statsFilters = gethttpsb().userSettings.statsFilters;
+ var input = $(this);
+ var filter = statsFilters[input.attr('id')];
+ input.prop('checked', filter === undefined || filter === true);
+ });
+
// Event handlers
$('#refresh-requests').on('click', renderRequests);
- $('input[id^="show-"][type="checkbox"]').on('change', syncWithFilters);
+ $('input[id^="show-"][type="checkbox"]').on('change', changeFilterHandler);
$('#selectPageUrls').on('change', targetUrlChangeHandler);
renderTransientData(true);
diff --git a/js/popup.js b/js/popup.js
index c980f0b..8684c45 100644
--- a/js/popup.js
+++ b/js/popup.js
@@ -275,27 +275,27 @@ function initMatrixStats() {
// collect all hostnames and ancestors from net traffic
var background = getBackgroundPage();
+ var uriTools = background.uriTools;
var pageUrl = pageStats.pageUrl;
- var url, hostname, reqType, nodes, node, reqKey;
- var reqKeys = Object.keys(pageStats.requests);
+ var hostname, reqType, nodes, node, reqKey;
+ var reqKeys = pageStats.requests.getRequestKeys();
var iReqKeys = reqKeys.length;
HTTPSBPopup.matrixHasRows = iReqKeys > 0;
while ( iReqKeys-- ) {
reqKey = reqKeys[iReqKeys];
- url = background.urlFromReqKey(reqKey);
- hostname = background.uriTools.hostnameFromURI(url);
+ hostname = pageStats.requests.hostnameFromRequestKey(reqKey);
// rhill 2013-10-23: hostname can be empty if the request is a data url
// https://github.com/gorhill/httpswitchboard/issues/26
if ( hostname === '' ) {
- hostname = background.uriTools.hostnameFromURI(pageUrl);
+ hostname = uriTools.hostnameFromURI(pageUrl);
}
- reqType = background.typeFromReqKey(reqKey);
+ reqType = pageStats.requests.typeFromRequestKey(reqKey);
// we want a row for self and ancestors
- nodes = background.uriTools.allHostnamesFromHostname(hostname);
+ nodes = uriTools.allHostnamesFromHostname(hostname);
while ( true ) {
node = nodes.shift();
@@ -1268,9 +1268,12 @@ function bindToTabHandler(tabs) {
return;
}
- // Important! Before calling makeMenu()
var background = getBackgroundPage();
var httpsb = getHTTPSB();
+
+ $('body').toggleClass('powerOff', httpsb.off);
+
+ // Important! Before calling makeMenu()
HTTPSBPopup.tabId = tabs[0].id;
HTTPSBPopup.pageURL = background.pageUrlFromTabId(HTTPSBPopup.tabId);
HTTPSBPopup.scopeURL = httpsb.normalizeScopeURL(HTTPSBPopup.pageURL);
@@ -1301,6 +1304,22 @@ function bindToTabHandler(tabs) {
/******************************************************************************/
+function togglePower(force) {
+ var httpsb = getHTTPSB();
+ var off;
+ if ( typeof force === 'boolean' ) {
+ off = force;
+ } else {
+ off = !httpsb.off;
+ }
+ httpsb.off = off;
+ $('body').toggleClass('powerOff', off);
+ updateMatrixStats();
+ updateMatrixColors();
+}
+
+/******************************************************************************/
+
// make menu only when popup html is fully loaded
function initAll() {
@@ -1387,6 +1406,7 @@ function initAll() {
$('#buttonRuleManager').text(chrome.i18n.getMessage('ruleManagerPageName'));
$('#buttonInfo').text(chrome.i18n.getMessage('statsPageName'));
$('#buttonSettings').text(chrome.i18n.getMessage('settingsPageName'));
+ $('#buttonPower').on('click', togglePower);
$('#matList').on('click', '.g3Meta', function() {
var separator = $(this);
diff --git a/js/settings.js b/js/settings.js
index 5694da1..4439e06 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -47,18 +47,22 @@ function initAll() {
$('input[name="displayTextSize"]').attr('checked', function(){
return $(this).attr('value') === httpsb.userSettings.displayTextSize;
});
+ $('#strict-blocking').attr('checked', httpsb.userSettings.strictBlocking);
$('#delete-blacklisted-cookies').attr('checked', httpsb.userSettings.deleteCookies);
$('#delete-blacklisted-localstorage').attr('checked', httpsb.userSettings.deleteLocalStorage);
$('#cookie-removed-counter').html(httpsb.cookieRemovedCounter);
$('#localstorage-removed-counter').html(httpsb.localStorageRemovedCounter);
$('#process-behind-the-scene').attr('checked', httpsb.userSettings.processBehindTheSceneRequests);
- $('#strict-blocking').attr('checked', httpsb.userSettings.strictBlocking);
+ $('#max-logged-requests').val(httpsb.userSettings.maxLoggedRequests);
// Handle user interaction
$('input[name="displayTextSize"]').on('change', function(){
changeUserSettings('displayTextSize', $(this).attr('value'));
});
+ $('#strict-blocking').on('change', function(){
+ changeUserSettings('strictBlocking', $(this).is(':checked'));
+ });
$('#delete-blacklisted-cookies').on('change', function(){
changeUserSettings('deleteCookies', $(this).is(':checked'));
});
@@ -68,8 +72,19 @@ function initAll() {
$('#process-behind-the-scene').on('change', function(){
changeUserSettings('processBehindTheSceneRequests', $(this).is(':checked'));
});
- $('#strict-blocking').on('change', function(){
- changeUserSettings('strictBlocking', $(this).is(':checked'));
+ $('#max-logged-requests').on('change', function(){
+ var oldVal = gethttpsb().userSettings.maxLoggedRequests;
+ var newVal = Math.round(parseFloat($(this).val()));
+ if ( typeof newVal !== 'number' ) {
+ newVal = oldVal;
+ } else {
+ newVal = Math.max(newVal, 0);
+ newVal = Math.min(newVal, 999);
+ }
+ $(this).val(newVal);
+ if ( newVal !== oldVal ) {
+ changeUserSettings('maxLoggedRequests', newVal);
+ }
});
$('.whatisthis').on('click', function() {
diff --git a/js/strpacker.js b/js/strpacker.js
new file mode 100644
index 0000000..8a73efd
--- /dev/null
+++ b/js/strpacker.js
@@ -0,0 +1,107 @@
+/*******************************************************************************
+
+ httpswitchboard - a Chromium browser extension to black/white list requests.
+ Copyright (C) 2013 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/httpswitchboard
+*/
+
+/******************************************************************************/
+
+// It's just a dict-based "packer"
+
+var stringPacker = {
+ codeGenerator: 1,
+ codeJunkyard: [],
+ mapStringToEntry: {},
+ mapCodeToString: {},
+ base64Chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
+
+ Entry: function(code) {
+ this.count = 0;
+ this.code = code;
+ },
+
+ remember: function(code) {
+ if ( code === '' ) {
+ return;
+ }
+ var s = this.mapCodeToString[code];
+ if ( s ) {
+ var entry = this.mapStringToEntry[s];
+ entry.count++;
+ }
+ },
+
+ forget: function(code) {
+ if ( code === '' ) {
+ return;
+ }
+ var s = this.mapCodeToString[code];
+ if ( s ) {
+ var entry = this.mapStringToEntry[s];
+ entry.count--;
+ if ( !entry.count ) {
+ // console.debug('stringPacker > releasing code "%s" (aka "%s")', code, s);
+ this.codeJunkyard.push(entry);
+ delete this.mapCodeToString[code];
+ delete this.mapStringToEntry[s];
+ }
+ }
+ },
+
+ pack: function(s) {
+ var entry = this.entryFromString(s);
+ if ( !entry ) {
+ return '';
+ }
+ return entry.code;
+ },
+
+ unpack: function(packed) {
+ return this.mapCodeToString[packed] || '';
+ },
+
+ base64: function(code) {
+ var s = '';
+ var base64Chars = this.base64Chars;
+ while ( code ) {
+ s += String.fromCharCode(base64Chars.charCodeAt(code & 63));
+ code >>>= 6;
+ }
+ return s;
+ },
+
+ entryFromString: function(s) {
+ if ( s === '' ) {
+ return null;
+ }
+ var entry = this.mapStringToEntry[s];
+ if ( !entry ) {
+ entry = this.codeJunkyard.pop();
+ if ( !entry ) {
+ entry = new this.Entry(this.base64(this.codeGenerator++));
+ } else {
+ // console.debug('stringPacker > recycling code "%s" (aka "%s")', entry.code, s);
+ entry.count = 0;
+ }
+ this.mapStringToEntry[s] = entry;
+ this.mapCodeToString[entry.code] = s;
+ }
+ return entry;
+ }
+};
+
diff --git a/js/tab.js b/js/tab.js
index 6218cdb..d47415c 100644
--- a/js/tab.js
+++ b/js/tab.js
@@ -21,29 +21,256 @@
/******************************************************************************/
-RequestStatsEntry.prototype.junkyard = [];
+PageStatsRequestEntry.junkyard = [];
-RequestStatsEntry.prototype.factory = function() {
- var entry = RequestStatsEntry.prototype.junkyard.pop();
+/*----------------------------------------------------------------------------*/
+
+PageStatsRequestEntry.factory = function() {
+ var entry = PageStatsRequestEntry.junkyard.pop();
if ( entry ) {
return entry;
}
- return new RequestStatsEntry();
+ return new PageStatsRequestEntry();
};
-RequestStatsEntry.prototype.dispose = function() {
+/*----------------------------------------------------------------------------*/
+
+PageStatsRequestEntry.prototype.dispose = function() {
// Let's not grab and hold onto too much memory..
- if ( RequestStatsEntry.prototype.junkyard.length < 200 ) {
- RequestStatsEntry.prototype.junkyard.push(this);
+ if ( PageStatsRequestEntry.junkyard.length < 200 ) {
+ PageStatsRequestEntry.junkyard.push(this);
+ }
+};
+
+/******************************************************************************/
+
+PageStatsRequests.factory = function() {
+ var requests = new PageStatsRequests();
+ requests.ringBuffer = new Array(HTTPSB.userSettings.maxLoggedRequests);
+ return requests;
+};
+
+/*----------------------------------------------------------------------------*/
+
+// Request key:
+// index: 01234567...
+// HHHHHHTN...
+// ^ ^^
+// | ||
+// | |+--- short string code for hostname (dict-based)
+// | +--- single char code for type of request
+// +--- FNV32a hash of whole URI (irreversible)
+
+PageStatsRequests.makeRequestKey = function(uri, reqType) {
+ // Ref: Given a URL, returns a unique 7-character long hash string
+ // Based on: FNV32a
+ // http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-reference-source
+ // The rest is custom, suited for HTTPSB.
+ var hint = 0x811c9dc5;
+ var i = uri.length;
+ while ( i-- ) {
+ hint ^= uri.charCodeAt(i);
+ hint += hint<<1 + hint<<4 + hint<<7 + hint<<8 + hint<<24;
+ }
+ hint = hint >>> 0;
+
+ // convert 32-bit hash to str
+ var hstr = '';
+ i = 6;
+ while ( i-- ) {
+ hstr += PageStatsRequests.charCodes.charAt(hint & 0x3F);
+ hint >>= 6;
+ }
+
+ // append code for type
+ hstr += PageStatsRequests.typeToCode[reqType] || 'z';
+
+ // append code for hostname
+ hstr += stringPacker.pack(uriTools.hostnameFromURI(uri));
+
+ return hstr;
+};
+
+PageStatsRequests.charCodes = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
+PageStatsRequests.typeToCode = {
+ 'main_frame' : 'a',
+ 'sub_frame' : 'b',
+ 'stylesheet' : 'c',
+ 'script' : 'd',
+ 'image' : 'e',
+ 'object' : 'f',
+ 'xmlhttprequest': 'g',
+ 'other' : 'h',
+ 'cookie' : 'i'
+};
+PageStatsRequests.codeToType = {
+ 'a': 'main_frame',
+ 'b': 'sub_frame',
+ 'c': 'stylesheet',
+ 'd': 'script',
+ 'e': 'image',
+ 'f': 'object',
+ 'g': 'xmlhttprequest',
+ 'h': 'other',
+ 'i': 'cookie'
+};
+
+/*----------------------------------------------------------------------------*/
+
+PageStatsRequests.rememberRequestKey = function(reqKey) {
+ stringPacker.remember(reqKey.slice(7));
+};
+
+/*----------------------------------------------------------------------------*/
+
+PageStatsRequests.forgetRequestKey = function(reqKey) {
+ stringPacker.forget(reqKey.slice(7));
+};
+
+/*----------------------------------------------------------------------------*/
+
+PageStatsRequests.hostnameFromRequestKey = function(reqKey) {
+ return stringPacker.unpack(reqKey.slice(7));
+};
+PageStatsRequests.prototype.hostnameFromRequestKey = PageStatsRequests.hostnameFromRequestKey;
+
+/*----------------------------------------------------------------------------*/
+
+PageStatsRequests.typeFromRequestKey = function(reqKey) {
+ return PageStatsRequests.codeToType[reqKey.charAt(6)];
+};
+PageStatsRequests.prototype.typeFromRequestKey = PageStatsRequests.typeFromRequestKey;
+
+/*----------------------------------------------------------------------------*/
+
+PageStatsRequests.prototype.createEntryIfNotExists = function(url, type, block) {
+ this.logRequest(url, type);
+ var reqKey = PageStatsRequests.makeRequestKey(url, type);
+ var entry = this.requests[reqKey];
+ if ( entry ) {
+ entry.when = Date.now();
+ entry.blocked = block;
+ return false;
+ }
+ PageStatsRequests.rememberRequestKey(reqKey);
+ entry = PageStatsRequestEntry.factory();
+ entry.when = Date.now();
+ entry.blocked = block;
+ this.requests[reqKey] = entry;
+ return true;
+};
+
+/*----------------------------------------------------------------------------*/
+
+PageStatsRequests.prototype.resizeLogBuffer = function(size) {
+ if ( size === this.ringBuffer.length ) {
+ return;
+ }
+ if ( !size ) {
+ this.ringBuffer = new Array(0);
+ this.ringBufferPointer = 0;
+ return;
+ }
+ var newBuffer = new Array(size);
+ var copySize = Math.min(size, this.ringBuffer.length);
+ var newBufferPointer = (copySize % size) | 0;
+ var isrc = this.ringBufferPointer;
+ var ides = newBufferPointer;
+ while ( copySize-- ) {
+ isrc--;
+ if ( isrc < 0 ) {
+ isrc = this.ringBuffer.length - 1;
+ }
+ ides--;
+ if ( ides < 0 ) {
+ ides = size - 1;
+ }
+ newBuffer[ides] = this.ringBuffer[isrc];
+ }
+ this.ringBuffer = newBuffer;
+ this.ringBufferPointer = newBufferPointer;
+};
+
+/*----------------------------------------------------------------------------*/
+
+PageStatsRequests.prototype.logRequest = function(url, type) {
+ var buffer = this.ringBuffer;
+ var len = buffer.length;
+ if ( !len ) {
+ return;
}
+ var pointer = this.ringBufferPointer;
+ buffer[pointer] = url + '#' + type;
+ this.ringBufferPointer = ((pointer + 1) % len) | 0;
+};
+
+/*----------------------------------------------------------------------------*/
+
+PageStatsRequests.prototype.getLoggedRequests = function() {
+ var buffer = this.ringBuffer;
+ if ( !buffer.length ) {
+ return [];
+ }
+ // [0 - pointer] = most recent
+ // [pointer - length] = least recent
+ // thus, ascending order:
+ // [pointer - length] + [0 - pointer]
+ var pointer = this.ringBufferPointer;
+ return buffer.slice(pointer).concat(buffer.slice(0, pointer)).reverse();
+};
+
+/*----------------------------------------------------------------------------*/
+
+PageStatsRequests.prototype.getLoggedRequestEntry = function(reqURL, reqType) {
+ return this.requests[PageStatsRequests.makeRequestKey(reqURL, reqType)];
+};
+
+/*----------------------------------------------------------------------------*/
+
+PageStatsRequests.prototype.getRequestKeys = function() {
+ return Object.keys(this.requests);
+};
+
+/*----------------------------------------------------------------------------*/
+
+PageStatsRequests.prototype.getEntry = function(reqKey) {
+ return this.requests[reqKey];
+};
+
+/*----------------------------------------------------------------------------*/
+
+PageStatsRequests.prototype.disposeOne = function(reqKey) {
+ if ( this.requests[reqKey] ) {
+ this.requests[reqKey].dispose();
+ delete this.requests[reqKey];
+ PageStatsRequests.forgetRequestKey(reqKey);
+ }
+};
+
+/*----------------------------------------------------------------------------*/
+
+PageStatsRequests.prototype.dispose = function() {
+ var requests = this.requests;
+ for ( var reqKey in requests ) {
+ if ( requests.hasOwnProperty(reqKey) ) {
+ stringPacker.forget(reqKey.slice(7));
+ requests[reqKey].dispose();
+ delete requests[reqKey];
+ }
+ }
+ var i = this.ringBuffer.length;
+ while ( i-- ) {
+ this.ringBuffer[i] = '';
+ }
+ this.ringBufferPointer = 0;
};
/******************************************************************************/
-PageStatsEntry.prototype.junkyard = [];
+PageStatsEntry.junkyard = [];
-PageStatsEntry.prototype.factory = function(pageUrl) {
- var entry = PageStatsEntry.prototype.junkyard.pop();
+PageStatsEntry.factory = function(pageUrl) {
+ var entry = PageStatsEntry.junkyard.pop();
if ( entry ) {
return entry.init(pageUrl);
}
@@ -54,7 +281,7 @@ PageStatsEntry.prototype.factory = function(pageUrl) {
PageStatsEntry.prototype.init = function(pageUrl) {
this.pageUrl = pageUrl;
- this.requests = {};
+ this.requests = PageStatsRequests.factory();
this.domains = {};
this.state = {};
this.requestStats.reset();
@@ -68,26 +295,17 @@ PageStatsEntry.prototype.init = function(pageUrl) {
/******************************************************************************/
PageStatsEntry.prototype.dispose = function() {
- // Iterate through all requests and return them to the junkyard for
- // later reuse.
- var reqKeys = Object.keys(this.requests);
- var i = reqKeys.length;
- var reqKey;
- while ( i-- ) {
- reqKey = reqKeys[i];
- this.requests[reqKey].dispose();
- delete this.requests[reqKey];
- }
+ this.requests.dispose();
+
// rhill 2013-11-07: Even though at init time these are reset, I still
// need to release the memory taken by these, which can amount to
// sizeable enough chunks (especially requests, through the request URL
// used as a key).
this.pageUrl = '';
- this.requests = {};
this.domains = {};
this.state = {};
- PageStatsEntry.prototype.junkyard.push(this);
+ PageStatsEntry.junkyard.push(this);
};
/******************************************************************************/
@@ -120,21 +338,11 @@ PageStatsEntry.prototype.recordRequest = function(type, url, block) {
} else {
this.perLoadAllowedRequestCount++;
}
- // var packedUrl = urlPacker.remember(url) + '#' + type;
- var reqKey = url + '#' + type;
- var requestStatsEntry = this.requests[reqKey];
- if ( requestStatsEntry ) {
- requestStatsEntry.when = Date.now();
- requestStatsEntry.blocked = block;
+ if ( !this.requests.createEntryIfNotExists(url, type, block) ) {
return;
}
- requestStatsEntry = RequestStatsEntry.prototype.factory();
- requestStatsEntry.when = Date.now();
- requestStatsEntry.blocked = block;
- this.requests[reqKey] = requestStatsEntry;
-
this.distinctRequestCount++;
this.domains[hostname] = true;
@@ -160,6 +368,8 @@ PageStatsEntry.prototype.getPageURL = function() {
// notifying me, and this causes internal cached state to be out of sync.
PageStatsEntry.prototype.updateBadge = function(tabId) {
+ var httpsb = HTTPSB;
+
// Icon
var iconPath;
var total = this.perLoadAllowedRequestCount + this.perLoadBlockedRequestCount;
@@ -173,27 +383,30 @@ PageStatsEntry.prototype.updateBadge = function(tabId) {
}
chrome.browserAction.setIcon({ tabId: tabId, path: iconPath });
- // Badge text
- var count = this.distinctRequestCount;
- var iconStr = count.toFixed(0);
- if ( count >= 1000 ) {
- if ( count < 10000 ) {
- iconStr = iconStr.slice(0,1) + '.' + iconStr.slice(1,-2) + 'K';
- } else if ( count < 1000000 ) {
- iconStr = iconStr.slice(0,-3) + 'K';
- } else if ( count < 10000000 ) {
- iconStr = iconStr.slice(0,1) + '.' + iconStr.slice(1,-5) + 'M';
- } else {
- iconStr = iconStr.slice(0,-6) + 'M';
+ // Badge text & color
+ var badgeStr, badgeColor;
+ if ( httpsb.off ) {
+ badgeStr = '!!!';
+ badgeColor = '#F00';
+ } else {
+ var count = this.distinctRequestCount;
+ badgeStr = count.toFixed(0);
+ if ( count >= 1000 ) {
+ if ( count < 10000 ) {
+ badgeStr = badgeStr.slice(0,1) + '.' + badgeStr.slice(1,-2) + 'K';
+ } else if ( count < 1000000 ) {
+ badgeStr = badgeStr.slice(0,-3) + 'K';
+ } else if ( count < 10000000 ) {
+ badgeStr = badgeStr.slice(0,1) + '.' + badgeStr.slice(1,-5) + 'M';
+ } else {
+ badgeStr = badgeStr.slice(0,-6) + 'M';
+ }
}
+ badgeColor = httpsb.scopePageExists(this.pageUrl) ? '#66F' : '#000';
}
- chrome.browserAction.setBadgeText({ tabId: tabId, text: iconStr });
- // Badge color
- chrome.browserAction.setBadgeBackgroundColor({
- tabId: tabId,
- color: HTTPSB.scopePageExists(this.pageUrl) ? '#66F' : '#000'
- });
+ chrome.browserAction.setBadgeText({ tabId: tabId, text: badgeStr });
+ chrome.browserAction.setBadgeBackgroundColor({ tabId: tabId, color: badgeColor });
};
/******************************************************************************/
@@ -242,8 +455,8 @@ function garbageCollectStalePageStatsCallback() {
// Prune content of chromium-behind-the-scene virtual tab
var pageStats = httpsb.pageStats[httpsb.behindTheSceneURL];
if ( pageStats ) {
- var reqKeys = Object.keys(pageStats.requests);
- if ( reqKeys > httpsb.behindTheSceneMaxReq ) {
+ var reqKeys = pageStats.requests.getRequestKeys();
+ if ( reqKeys.length > httpsb.behindTheSceneMaxReq ) {
reqKeys = reqKeys.sort(function(a,b){
var ra = pageStats.requests[a];
var rb = pageStats.requests[b];
@@ -252,11 +465,8 @@ function garbageCollectStalePageStatsCallback() {
return 0;
}).slice(httpsb.behindTheSceneMaxReq);
var iReqKey = reqKeys.length;
- var reqKey;
while ( iReqKey-- ) {
- reqKey = reqKeys[iReqKey];
- pageStats.requests[reqKey].dispose();
- delete pageStats.requests[reqKey];
+ pageStats.requests.disposeOne(reqKeys[iReqKey]);
}
}
}
@@ -287,7 +497,7 @@ function createPageStats(pageUrl) {
var httpsb = HTTPSB;
var pageStats = httpsb.pageStats[pageUrl];
if ( !pageStats ) {
- pageStats = PageStatsEntry.prototype.factory(pageUrl);
+ pageStats = PageStatsEntry.factory(pageUrl);
httpsb.pageStats[pageUrl] = pageStats;
} else if ( pageStats.pageUrl !== pageUrl ) {
pageStats.init(pageUrl);
@@ -454,18 +664,16 @@ function computeTabState(tabId) {
// It is a critical error for a tab to not be defined here
var httpsb = HTTPSB;
var pageUrl = pageStats.pageUrl;
- var reqKeys = Object.keys(pageStats.requests);
+ var reqKeys = pageStats.requests.getRequestKeys();
var i = reqKeys.length;
var computedState = {};
- var url, domain, type;
- var reqKey;
+ var hostname, type, reqKey;
while ( i-- ) {
reqKey = reqKeys[i];
- url = urlFromReqKey(reqKey);
- domain = uriTools.hostnameFromURI(url);
- type = typeFromReqKey(reqKey);
- if ( httpsb.blacklisted(pageUrl, type, domain) ) {
- computedState[type + '|' + domain] = true;
+ hostname = PageStatsRequests.hostnameFromRequestKey(reqKey);
+ type = PageStatsRequests.typeFromRequestKey(reqKey);
+ if ( httpsb.blacklisted(pageUrl, type, hostname) ) {
+ computedState[type + '|' + hostname] = true;
}
}
return computedState;
diff --git a/js/traffic.js b/js/traffic.js
index bade023..5dbbb4d 100644
--- a/js/traffic.js
+++ b/js/traffic.js
@@ -99,7 +99,7 @@ background: #c00; \
// Intercept and filter web requests according to white and black lists.
-function webRequestHandler(details) {
+function beforeRequestHandler(details) {
var httpsb = HTTPSB;
var tabId = details.tabId;
@@ -180,7 +180,7 @@ function webRequestHandler(details) {
}
}
- // quickProfiler.stop('webRequestHandler | evaluate&record');
+ // quickProfiler.stop('beforeRequestHandler | evaluate&record');
// If it is a frame and scripts are blacklisted for the
// hostname, disable scripts for this hostname, necessary since inline
@@ -197,7 +197,7 @@ function webRequestHandler(details) {
// whitelisted?
if ( !block ) {
- // console.debug('webRequestHandler > allowing %s from %s', type, hostname);
+ // console.debug('beforeRequestHandler > allowing %s from %s', type, hostname);
// If the request is not blocked, this means the response could contain
// cookies. Thus, we go cookie hunting for this page url and record all
@@ -206,17 +206,20 @@ function webRequestHandler(details) {
// rhill 2013-11-07: Senseless to do this for behind-the-scene
// requests.
- if ( tabId !== httpsb.behindTheSceneTabId ) {
+ // rhill 2013-12-03: Do this here only for root frames. This is also
+ // done in `onHeadersReceived` when a `Set-cookie` directive is
+ // received.
+ if ( isRootFrame && tabId !== httpsb.behindTheSceneTabId ) {
cookieHunter.record(pageStats);
}
- // quickProfiler.stop('webRequestHandler');
+ // quickProfiler.stop('beforeRequestHandler');
// console.log("HTTPSB > %s @ url=%s", details.type, details.url);
return;
}
// blacklisted
- // console.debug('webRequestHandler > blocking %s from %s', type, hostname);
+ // console.debug('beforeRequestHandler > blocking %s from %s', type, hostname);
// If it's a blacklisted frame, redirect to frame.html
// rhill 2013-11-05: The root frame contains a link to noop.css, this
@@ -240,7 +243,7 @@ function webRequestHandler(details) {
return { "redirectUrl": dataURI };
}
- // quickProfiler.stop('webRequestHandler');
+ // quickProfiler.stop('beforeRequestHandler');
return { "cancel": true };
}
@@ -249,7 +252,7 @@ function webRequestHandler(details) {
// This is to handle cookies leaving the browser.
-function webHeaderRequestHandler(details) {
+function beforeSendHeadersHandler(details) {
// Ignore traffic outside tabs
if ( details.tabId < 0 ) {
@@ -260,7 +263,7 @@ function webHeaderRequestHandler(details) {
var hostname = uriTools.hostnameFromURI(details.url);
var blacklistCookie = HTTPSB.blacklisted(pageUrlFromTabId(details.tabId), 'cookie', hostname);
var headers = details.requestHeaders;
- var i = details.requestHeaders.length;
+ var i = headers.length;
while ( i-- ) {
if ( headers[i].name.toLowerCase() !== 'cookie' ) {
continue;
@@ -272,7 +275,35 @@ function webHeaderRequestHandler(details) {
}
if ( blacklistCookie ) {
- return { requestHeaders: details.requestHeaders };
+ return { requestHeaders: headers };
+ }
+}
+
+/******************************************************************************/
+
+// This is to handle cookies arriving in the browser.
+
+function headersReceivedHandler(details) {
+
+ // Ignore traffic outside tabs
+ var tabId = details.tabId;
+ if ( tabId < 0 || tabId === HTTPSB.behindTheSceneTabId ) {
+ return;
+ }
+
+ var pageStats = pageStatsFromTabId(tabId);
+ if ( !pageStats ) {
+ return;
+ }
+
+ // Any `set-cookie` directive in there?
+ var headers = details.responseHeaders;
+ var i = headers.length;
+ while ( i-- ) {
+ if ( headers[i].name.toLowerCase() === 'set-cookie' ) {
+ cookieHunter.record(pageStats);
+ break;
+ }
}
}
@@ -294,7 +325,7 @@ function startWebRequestHandler(from) {
}
chrome.webRequest.onBeforeRequest.addListener(
- webRequestHandler,
+ beforeRequestHandler,
{
"urls": [
"http://*/*",
@@ -316,7 +347,7 @@ function startWebRequestHandler(from) {
);
chrome.webRequest.onBeforeSendHeaders.addListener(
- webHeaderRequestHandler,
+ beforeSendHeadersHandler,
{
'urls': [
"http://*/*",
@@ -326,5 +357,16 @@ function startWebRequestHandler(from) {
['blocking', 'requestHeaders']
);
+ chrome.webRequest.onHeadersReceived.addListener(
+ headersReceivedHandler,
+ {
+ 'urls': [
+ "http://*/*",
+ "https://*/*"
+ ]
+ },
+ ['responseHeaders']
+ );
+
HTTPSB.webRequestHandler = true;
}
diff --git a/js/types.js b/js/types.js
index 6d8be85..6e1ccec 100644
--- a/js/types.js
+++ b/js/types.js
@@ -60,15 +60,20 @@ function PermissionScopes(httpsb) {
this.scopes['*'] = new PermissionScope(httpsb);
}
-function RequestStatsEntry() {
+function PageStatsRequestEntry() {
this.when = 0;
this.blocked = false;
}
+function PageStatsRequests() {
+ this.requests = {};
+ this.ringBuffer = null;
+ this.ringBufferPointer = 0;
+}
+
function PageStatsEntry(pageUrl) {
this.pageUrl = '';
- this.requests = {};
- this.packedRequests = null;
+ this.requests = PageStatsRequests.factory();
this.domains = {};
this.state = {};
this.visible = false;
@@ -77,6 +82,7 @@ function PageStatsEntry(pageUrl) {
this.distinctRequestCount = 0;
this.perLoadAllowedRequestCount = 0;
this.perLoadBlockedRequestCount = 0;
+ this.off = false;
this.init(pageUrl);
}
diff --git a/js/uritools.js b/js/uritools.js
index 5cc8e52..dcf444b 100644
--- a/js/uritools.js
+++ b/js/uritools.js
@@ -153,7 +153,7 @@ var uriTools = {
var pos = s.indexOf('@');
if ( pos >= 0 ) {
- s = s.slice(0, pos+ 1);
+ s = s.slice(0, pos + 1);
}
// authority = host [ ":" port ]
@@ -185,12 +185,8 @@ var uriTools = {
/*--------------------------------------------------------------------*/
- fragment: function(fragment) {
- if ( fragment === undefined ) {
- return this._fragment;
- }
- this._fragment = fragment;
- return this;
+ query: function() {
+ return this._query;
},
/*--------------------------------------------------------------------*/
@@ -233,6 +229,35 @@ var uriTools = {
/*--------------------------------------------------------------------*/
+ directory: function() {
+ if ( this._hostname !== '' ) {
+ var pos = this._path.lastIndexOf('/');
+ if ( pos === 0 ) {
+ return '';
+ }
+ return this._path.slice(1, pos);
+ }
+ return '';
+ },
+
+ /*--------------------------------------------------------------------*/
+
+ filename: function() {
+ if ( this._hostname !== '' ) {
+ var pos = this._path.lastIndexOf('/');
+ return this._path.slice(pos + 1);
+ }
+ return this._path;
+ },
+
+ /*--------------------------------------------------------------------*/
+
+ fragment: function() {
+ return this._fragment;
+ },
+
+ /*--------------------------------------------------------------------*/
+
// Normalize the way HTTPSB expects it
normalizeURI: function(uri) {
@@ -428,6 +453,7 @@ var uriTools = {
},
/*--------------------------------------------------------------------*/
+
_scheme: '',
_hostname: '',
_ipv4: undefined,
@@ -473,5 +499,3 @@ var uriTools = {
uriTools.authorityBit = (uriTools.userBit | uriTools.passwordBit | uriTools.hostnameBit | uriTools.portBit);
uriTools.normalizeBits = (uriTools.schemeBit | uriTools.hostnameBit | uriTools.pathBit | uriTools.queryBit);
-/******************************************************************************/
-
diff --git a/js/urlpacker.js b/js/urlpacker.js
index eab7b77..384bd5c 100644
--- a/js/urlpacker.js
+++ b/js/urlpacker.js
@@ -24,104 +24,181 @@
// Experimental
function UrlPackerEntry(code) {
- this.count = 1;
+ this.count = 0;
this.code = code;
}
-var urlPacker = {
- uri: new URI(),
- codeGenerator: 0,
- codeJunkyard: [],
- fragmentToCode: {},
- codeToFragment: {},
- codeDigits: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
-
- remember: function(url) {
- this.uri.href(url);
- var scheme = this.uri.scheme();
- var hostname = this.uri.hostname();
- var directory = this.uri.directory();
- var leaf = this.uri.filename() + this.uri.search();
- var entry;
- var packedScheme;
- if ( scheme !== '' ) {
- entry = this.fragmentToCode[scheme];
- if ( !entry ) {
- entry = this.codeJunkyard.pop();
- packedScheme = this.strFromCode(this.codeGenerator++);
- if ( !entry ) {
- entry = new UrlPackerEntry(packedScheme);
- } else {
- entry.code = packedScheme;
- entry.count = 1;
- }
- this.fragmentToCode[scheme] = entry;
- this.codeToFragment[packedScheme] = scheme;
- } else {
- packedScheme = entry.code;
- entry.count++;
- }
- } else {
- packedScheme = '';
+var uriPacker = {
+ codeGenerator: 1,
+ codeJunkyard: [], // once "released", candidates for "recycling"
+ mapSegmentToCode: {},
+ mapCodeToSegment: {},
+ base64Chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
+
+ remember: function(packedURL) {
+ // {scheme}/{hostname}/{directory}/filename?query#{fragment}
+ // {scheme}
+ var end = packedURL.indexOf('/');
+ this.acquireCode(packedURL.slice(0, end));
+ // {hostname}
+ var beg = end + 1;
+ end = packedURL.indexOf('/', beg);
+ this.acquireCode(packedURL.slice(beg, end));
+ // {directory}
+ beg = end + 1;
+ end = packedURL.indexOf('/', beg);
+ this.acquireCode(packedURL.slice(beg, end));
+ // {fragment}
+ beg = end + 1;
+ end = packedURL.indexOf('#', beg);
+ this.acquireCode(packedURL.slice(end + 1));
+ },
+
+ forget: function(packedURL) {
+ // {scheme}/{hostname}/{directory}/filename?query#{fragment}
+ // {scheme}
+ var end = packedURL.indexOf('/');
+ this.releaseCode(packedURL.slice(0, end));
+ // {hostname}
+ var beg = end + 1;
+ end = packedURL.indexOf('/', beg);
+ this.releaseCode(packedURL.slice(beg, end));
+ // {directory}
+ beg = end + 1;
+ end = packedURL.indexOf('/', beg);
+ this.releaseCode(packedURL.slice(beg, end));
+ // {fragment}
+ beg = end + 1;
+ end = packedURL.indexOf('#', beg);
+ this.releaseCode(packedURL.slice(end + 1));
+ },
+
+ pack: function(url) {
+ var ut = uriTools;
+ ut.uri(url);
+ return this.codeFromSegment(ut.scheme()) + '/' +
+ this.codeFromSegment(ut.hostname()) + '/' +
+ this.codeFromSegment(ut.directory()) + '/' +
+ ut.filename() + '?' + ut.query() + '#' +
+ this.codeFromSegment(ut.fragment());
+ },
+
+ unpack: function(packedURL) {
+ // {scheme}/{hostname}/{directory}/filename?query#{fragment}
+ // {scheme}
+ var end = packedURL.indexOf('/');
+ var uri = this.mapCodeToSegment[packedURL.slice(0, end)] + ':';
+ // {hostname}
+ var beg = end + 1;
+ end = packedURL.indexOf('/', beg);
+ var segment = this.mapCodeToSegment[packedURL.slice(beg, end)];
+ if ( segment ) {
+ uri += '//' + segment + '/';
}
- var packedHostname;
- if ( hostname !== '' ) {
- entry = this.fragmentToCode[hostname];
- if ( !entry ) {
- entry = this.codeJunkyard.pop();
- packedHostname = this.strFromCode(this.codeGenerator++);
- if ( !entry ) {
- entry = new UrlPackerEntry(packedHostname);
- } else {
- entry.code = packedHostname;
- entry.count = 1;
- }
- this.fragmentToCode[hostname] = entry;
- this.codeToFragment[packedHostname] = hostname;
- } else {
- packedHostname = entry.code;
- entry.count++;
- }
- } else {
- packedHostname = '';
+ // {directory}
+ beg = end + 1;
+ end = packedURL.indexOf('/', beg);
+ segment = this.mapCodeToSegment[packedURL.slice(beg, end)];
+ if ( segment ) {
+ uri += segment;
}
- var packedDirectory;
- if ( directory !== '' ) {
- entry = this.fragmentToCode[directory];
- if ( !entry ) {
- packedDirectory = this.strFromCode(this.codeGenerator++);
- entry = this.codeJunkyard.pop();
- if ( !entry ) {
- entry = new UrlPackerEntry(packedDirectory);
- } else {
- entry.code = packedDirectory;
- entry.count = 1;
- }
- this.fragmentToCode[directory] = entry;
- this.codeToFragment[packedDirectory] = directory;
- } else {
- packedDirectory = entry.code;
- entry.count++;
- }
- } else {
- packedDirectory = '';
+ // filename
+ beg = end + 1;
+ end = packedURL.indexOf('?', beg);
+ segment = packedURL.slice(beg, end);
+ if ( segment !== '' ) {
+ uri += segment;
+ }
+ // query
+ beg = end + 1;
+ end = packedURL.indexOf('#', beg);
+ segment = packedURL.slice(beg, end);
+ if ( segment !== '' ) {
+ uri += '?' + segment;
}
- // Return assembled packed fragments
- return packedScheme + '/' + packedHostname + '/' + packedDirectory + '/' + leaf;
+ // {fragment}
+ beg = end + 1;
+ segment = this.mapCodeToSegment[packedURL.slice(beg)];
+ if ( segment ) {
+ uri += '#' + segment;
+ }
+ return uri;
},
- forget: function() {
+ unpackHostname: function(packedURL) {
+ // {scheme}/{hostname}/{directory}/filename?query#{fragment}
+ var beg = packedURL.indexOf('/') + 1;
+ var end = packedURL.indexOf('/', beg);
+ var code = packedURL.slice(beg, end);
+ if ( code ) {
+ return this.mapCodeToSegment[code];
+ }
+ return '';
+ },
+
+ unpackFragment: function(packedURL) {
+ // {scheme}/{hostname}/{directory}/filename?query#{fragment}
+ var beg = packedURL.lastIndexOf('#') + 1;
+ var code = packedURL.slice(beg);
+ if ( code ) {
+ return this.mapCodeToSegment[code];
+ }
+ return '';
},
- strFromCode: function(code) {
+ base64: function(code) {
var s = '';
- var codeDigits = this.codeDigits;
+ var base64Chars = this.base64Chars;
while ( code ) {
- s = s + String.fromCharCode(codeDigits.charCodeAt(code & 63));
- code = code >> 6;
+ s += String.fromCharCode(base64Chars.charCodeAt(code & 63));
+ code >>>= 6;
}
return s;
},
+ codeFromSegment: function(segment) {
+ if ( segment === '' ) {
+ return '';
+ }
+ var entry = this.mapSegmentToCode[segment];
+ if ( !entry ) {
+ entry = this.codeJunkyard.pop();
+ if ( !entry ) {
+ entry = new UrlPackerEntry(this.base64(this.codeGenerator++));
+ } else {
+ console.debug('uriPacker > recycling code "%s" (aka "%s")', entry.code, segment);
+ entry.count = 0;
+ }
+ var code = entry.code;
+ this.mapSegmentToCode[segment] = entry;
+ this.mapCodeToSegment[code] = segment;
+ return code;
+ }
+ return entry.code;
+ },
+
+ acquireCode: function(code) {
+ if ( code === '' ) {
+ return;
+ }
+ var segment = this.mapCodeToSegment[code];
+ var entry = this.mapSegmentToCode[segment];
+ entry.count++;
+ },
+
+ releaseCode: function(code) {
+ if ( code === '' ) {
+ return;
+ }
+ var segment = this.mapCodeToSegment[code];
+ var entry = this.mapSegmentToCode[segment];
+ entry.count--;
+ if ( !entry.count ) {
+ console.debug('uriPacker > releasing code "%s" (aka "%s")', code, segment);
+ this.codeJunkyard.push(entry);
+ delete this.mapCodeToSegment[code];
+ delete this.mapSegmentToCode[segment];
+ }
+ }
};
diff --git a/js/usersettings.js b/js/usersettings.js
new file mode 100644
index 0000000..24c054e
--- /dev/null
+++ b/js/usersettings.js
@@ -0,0 +1,59 @@
+/*******************************************************************************
+
+ httpswitchboard - a Chromium browser extension to black/white list requests.
+ Copyright (C) 2013 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/httpswitchboard
+*/
+
+/******************************************************************************/
+
+function changeUserSettings(name, value) {
+ if ( typeof name !== 'string' || name === '' ) {
+ return;
+ }
+
+ // Do not allow an unknown user setting to be created
+ if ( HTTPSB.userSettings[name] === undefined ) {
+ return;
+ }
+
+ if ( value === undefined ) {
+ return HTTPSB.userSettings[name];
+ }
+
+ switch ( name ) {
+
+ // Need to visit each pageStats object to resize ring buffer
+ case 'maxLoggedRequests':
+ value = Math.max(Math.min(value, 500), 0);
+ HTTPSB.userSettings[name] = value;
+ var pageStats = HTTPSB.pageStats;
+ for ( var pageUrl in pageStats ) {
+ if ( pageStats.hasOwnProperty(pageUrl) ) {
+ pageStats[pageUrl].requests.resizeLogBuffer(value);
+ }
+ }
+ break;
+
+ default:
+ HTTPSB.userSettings[name] = value;
+ break;
+ }
+
+ saveUserSettings();
+}
+
diff --git a/js/utils.js b/js/utils.js
index 6991f6f..de55835 100644
--- a/js/utils.js
+++ b/js/utils.js
@@ -55,58 +55,3 @@ function setJavascript(hostname, state) {
});
}
-/******************************************************************************/
-
-// Ref: Given a URL, returns a unique 7-character long hash string
-
-function requestHash(url, reqtype) {
-
- // FNV32a
- // http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-reference-source
- var hint = 0x811c9dc5;
- var i = s.length;
- while ( i-- ) {
- hint ^= s.charCodeAt(i);
- hint += hint<<1 + hint<<4 + hint<<7 + hint<<8 + hint<<24;
- }
- hint = hint >>> 0;
-
- var hstr = requestHash.typeToCode[reqtype] || 'z';
- var i = 6;
- while ( i-- ) {
- hstr += requestHash.charCodes.charAt(hint & 0x3F);
- hint >>= 6;
- }
- return hstr;
-}
-
-requestHash.charCodes = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
-
-requestHash.typeToCode = {
- 'main_frame' : 'a',
- 'sub_frame' : 'b',
- 'stylesheet' : 'c',
- 'script' : 'd',
- 'image' : 'e',
- 'object' : 'f',
- 'xmlhttprequest': 'g',
- 'other' : 'h',
- 'cookie' : 'i'
-};
-requestHash.codeToType = {
- 'a': 'main_frame',
- 'b': 'sub_frame',
- 'c': 'stylesheet',
- 'd': 'script',
- 'e': 'image',
- 'f': 'object',
- 'g': 'xmlhttprequest',
- 'h': 'other',
- 'i': 'cookie',
- 'z': 'unknown'
-};
-
-requestHash.typeFromHash = function(hstr) {
- return requestHash.codeToType[hstr.charAt(0)];
-};
-
diff --git a/manifest.json b/manifest.json
index 3282cf4..260beae 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "__MSG_extName__",
- "version": "0.6.1",
+ "version": "0.6.2",
"description": "__MSG_extShortDesc__",
"icons": {
"128": "icon_128.png"
diff --git a/popup.html b/popup.html
index 8c448e6..78f9555 100644
--- a/popup.html
+++ b/popup.html
@@ -13,7 +13,7 @@
font: 13px httpsb,sans-serif;
background-color: white;
min-width: 32em;
- min-height: 10em;
+ min-height: 15em;
}
body.scope-is-page {
background-color: #f2f2ff;
@@ -53,9 +53,15 @@
.scope-is-page #buttonToggleScope {
background-image: url('img/remove-page-permissions.png');
}
+body.powerOff #buttonToggleScope {
+ display: none;
+ }
#buttonRevert {
background-image: url('img/clear-temporary-rules.png');
}
+body.powerOff #buttonRevert {
+ display: none;
+ }
#buttonDropdown {
background-image: url('img/dropdown.png');
}
@@ -83,6 +89,9 @@
background-repeat: no-repeat;
background-position: left 4px center;
}
+body.powerOff #buttonPower {
+ background-image: url('img/system-shutdown-symbolic-off.png');
+}
.paneContent {
padding-top: 5.5em;
@@ -176,12 +185,18 @@
.matrix .g3Meta.g3Collapsed ~ .matSection {
display: none;
}
+body.powerOff .matrix .g3Meta.g3Collapsed ~ .matSection {
+ display: block;
+ }
.matrix .g3Meta ~ .matRow.ro {
display: none;
}
.matrix .g3Meta.g3Collapsed ~ .matRow.ro {
display: block;
}
+body.powerOff .matrix .g3Meta.g3Collapsed ~ .matRow.ro {
+ display: none;
+ }
.matrix .matGroup .g3Meta + *,.matrix .matGroup .g3Meta + * + * {
margin-top: 0;
padding-top: 0;
@@ -199,6 +214,10 @@
.matRow.rw .matCell {
cursor: pointer;
}
+body.powerOff .matRow.rw .matCell {
+ cursor: auto;
+ opacity: 0.6;
+ }
.rpt {
color: black;
background-color: #f8d0d0;
@@ -283,6 +302,10 @@
.matCell.gdt #cellMenu {
display: block;
}
+body.powerOff .matCell #cellMenu {
+ display: none;
+ }
+
#persist, #unpersist {
margin: 1px;
border: 0;
@@ -344,6 +367,9 @@
#blacklist {
top: 50%;
}
+body.powerOff #whitelist, body.powerOff #blacklist {
+ display: none;
+ }
.rw .matCell.rpt #whitelist:hover {
background-color: #080;
opacity: 0.25;
@@ -385,6 +411,7 @@
background: transparent url('img/domain-collapse.png') no-repeat;
opacity: 0.25;
z-index: 10000;
+ cursor: pointer;
}
.matSection.collapsed #domainOnly {
background: transparent url('img/domain-expand.png') no-repeat;
@@ -428,7 +455,7 @@
Statistics...
Settings...
-
On/off [nyi]
+
HTTPSB on/off