2014-10-18 08:01:09 +13:00
|
|
|
/*******************************************************************************
|
|
|
|
|
|
|
|
µMatrix - 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/uMatrix
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* global chrome, µMatrix */
|
2015-04-11 02:45:40 +12:00
|
|
|
/* jshint bitwise: false, boss: true */
|
2014-10-18 08:01:09 +13:00
|
|
|
|
|
|
|
/*******************************************************************************
|
|
|
|
|
|
|
|
A PageRequestStore object is used to store net requests in two ways:
|
|
|
|
|
|
|
|
To record distinct net requests
|
|
|
|
|
|
|
|
**/
|
|
|
|
|
|
|
|
µMatrix.PageRequestStats = (function() {
|
|
|
|
|
2015-05-14 06:53:29 +12:00
|
|
|
'use strict';
|
|
|
|
|
2014-10-18 08:01:09 +13:00
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
// Caching useful global vars
|
|
|
|
|
|
|
|
var µm = µMatrix;
|
|
|
|
var µmuri = null;
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
// Hidden vars
|
|
|
|
|
|
|
|
var typeToCode = {
|
|
|
|
'doc' : 'a',
|
|
|
|
'frame' : 'b',
|
|
|
|
'css' : 'c',
|
|
|
|
'script': 'd',
|
|
|
|
'image' : 'e',
|
|
|
|
'plugin': 'f',
|
|
|
|
'xhr' : 'g',
|
|
|
|
'other' : 'h',
|
|
|
|
'cookie': 'i'
|
|
|
|
};
|
|
|
|
|
|
|
|
var codeToType = {
|
|
|
|
'a': 'doc',
|
|
|
|
'b': 'frame',
|
|
|
|
'c': 'css',
|
|
|
|
'd': 'script',
|
|
|
|
'e': 'image',
|
|
|
|
'f': 'plugin',
|
|
|
|
'g': 'xhr',
|
|
|
|
'h': 'other',
|
|
|
|
'i': 'cookie'
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
// It's just a dict-based "packer"
|
|
|
|
|
|
|
|
var stringPacker = {
|
|
|
|
codeGenerator: 1,
|
|
|
|
codeJunkyard: [],
|
|
|
|
mapStringToEntry: {},
|
|
|
|
mapCodeToString: {},
|
|
|
|
|
|
|
|
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] || '';
|
|
|
|
},
|
|
|
|
|
|
|
|
stringify: function(code) {
|
|
|
|
if ( code <= 0xFFFF ) {
|
|
|
|
return String.fromCharCode(code);
|
|
|
|
}
|
|
|
|
return String.fromCharCode(code >>> 16) + String.fromCharCode(code & 0xFFFF);
|
|
|
|
},
|
|
|
|
|
|
|
|
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.stringify(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;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
var PageRequestStats = function() {
|
|
|
|
this.requests = {};
|
|
|
|
if ( !µmuri ) {
|
|
|
|
µmuri = µm.URI;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
PageRequestStats.prototype.init = function() {
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
var pageRequestStoreJunkyard = [];
|
|
|
|
|
|
|
|
var pageRequestStoreFactory = function() {
|
|
|
|
var pageRequestStore = pageRequestStoreJunkyard.pop();
|
|
|
|
if ( pageRequestStore ) {
|
|
|
|
pageRequestStore.init();
|
|
|
|
} else {
|
|
|
|
pageRequestStore = new PageRequestStats();
|
|
|
|
}
|
|
|
|
return pageRequestStore;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
PageRequestStats.prototype.disposeOne = function(reqKey) {
|
|
|
|
if ( this.requests[reqKey] ) {
|
|
|
|
delete this.requests[reqKey];
|
|
|
|
forgetRequestKey(reqKey);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
PageRequestStats.prototype.dispose = function() {
|
|
|
|
var requests = this.requests;
|
|
|
|
for ( var reqKey in requests ) {
|
|
|
|
if ( requests.hasOwnProperty(reqKey) === false ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
stringPacker.forget(reqKey.slice(3));
|
|
|
|
}
|
2014-10-28 09:19:46 +13:00
|
|
|
this.requests = {};
|
2014-10-18 08:01:09 +13:00
|
|
|
if ( pageRequestStoreJunkyard.length < 8 ) {
|
|
|
|
pageRequestStoreJunkyard.push(this);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
// Request key:
|
|
|
|
// index: 0123
|
|
|
|
// THHN
|
|
|
|
// ^^ ^
|
|
|
|
// || |
|
|
|
|
// || +--- short string code for hostname (dict-based)
|
|
|
|
// |+--- FNV32a hash of whole URI (irreversible)
|
|
|
|
// +--- single char code for type of request
|
|
|
|
|
|
|
|
var makeRequestKey = function(uri, reqType) {
|
|
|
|
// Ref: Given a URL, returns a unique 4-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 µMatrix.
|
|
|
|
var hint = 0x811c9dc5;
|
|
|
|
var i = uri.length;
|
|
|
|
while ( i-- ) {
|
2015-05-14 06:53:29 +12:00
|
|
|
hint ^= uri.charCodeAt(i) | 0;
|
|
|
|
hint += (hint<<1) + (hint<<4) + (hint<<7) + (hint<<8) + (hint<<24) | 0;
|
2014-10-18 08:01:09 +13:00
|
|
|
hint >>>= 0;
|
|
|
|
}
|
|
|
|
var key = typeToCode[reqType] || 'z';
|
|
|
|
return key +
|
2014-10-28 17:14:15 +13:00
|
|
|
String.fromCharCode(hint >>> 22, hint >>> 12 & 0x3FF, hint & 0xFFF) +
|
2014-10-18 08:01:09 +13:00
|
|
|
stringPacker.pack(µmuri.hostnameFromURI(uri));
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
var rememberRequestKey = function(reqKey) {
|
2014-10-28 17:14:15 +13:00
|
|
|
stringPacker.remember(reqKey.slice(4));
|
2014-10-18 08:01:09 +13:00
|
|
|
};
|
|
|
|
|
|
|
|
var forgetRequestKey = function(reqKey) {
|
2014-10-28 17:14:15 +13:00
|
|
|
stringPacker.forget(reqKey.slice(4));
|
2014-10-18 08:01:09 +13:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
// Exported
|
|
|
|
|
|
|
|
var hostnameFromRequestKey = function(reqKey) {
|
2014-10-28 17:14:15 +13:00
|
|
|
return stringPacker.unpack(reqKey.slice(4));
|
2014-10-18 08:01:09 +13:00
|
|
|
};
|
|
|
|
|
|
|
|
PageRequestStats.prototype.hostnameFromRequestKey = hostnameFromRequestKey;
|
|
|
|
|
|
|
|
var typeFromRequestKey = function(reqKey) {
|
|
|
|
return codeToType[reqKey.charAt(0)];
|
|
|
|
};
|
|
|
|
|
|
|
|
PageRequestStats.prototype.typeFromRequestKey = typeFromRequestKey;
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2014-10-25 04:13:46 +13:00
|
|
|
PageRequestStats.prototype.createEntryIfNotExists = function(url, type) {
|
2014-10-18 08:01:09 +13:00
|
|
|
var reqKey = makeRequestKey(url, type);
|
|
|
|
if ( this.requests[reqKey] ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
rememberRequestKey(reqKey);
|
|
|
|
this.requests[reqKey] = Date.now();
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
PageRequestStats.prototype.getRequestKeys = function() {
|
|
|
|
return Object.keys(this.requests);
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
PageRequestStats.prototype.getRequestDict = function() {
|
|
|
|
return this.requests;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
// Export
|
|
|
|
|
|
|
|
return {
|
|
|
|
factory: pageRequestStoreFactory,
|
|
|
|
hostnameFromRequestKey: hostnameFromRequestKey,
|
|
|
|
typeFromRequestKey: typeFromRequestKey
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
µMatrix.PageStore = (function() {
|
|
|
|
|
2015-04-12 09:15:57 +12:00
|
|
|
'use strict';
|
|
|
|
|
2014-10-18 08:01:09 +13:00
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
var µm = µMatrix;
|
|
|
|
var pageStoreJunkyard = [];
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-04-22 08:22:29 +12:00
|
|
|
var pageStoreFactory = function(tabContext) {
|
2014-10-18 08:01:09 +13:00
|
|
|
var entry = pageStoreJunkyard.pop();
|
|
|
|
if ( entry ) {
|
2015-04-22 08:22:29 +12:00
|
|
|
return entry.init(tabContext);
|
2014-10-18 08:01:09 +13:00
|
|
|
}
|
2015-04-22 08:22:29 +12:00
|
|
|
return new PageStore(tabContext);
|
2014-10-18 08:01:09 +13:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-04-22 08:22:29 +12:00
|
|
|
function PageStore(tabContext) {
|
2015-05-14 06:32:16 +12:00
|
|
|
this.requestStats = µm.requestStatsFactory();
|
2014-10-18 08:01:09 +13:00
|
|
|
this.off = false;
|
2015-04-22 08:22:29 +12:00
|
|
|
this.init(tabContext);
|
2014-10-18 08:01:09 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-04-22 08:22:29 +12:00
|
|
|
PageStore.prototype.init = function(tabContext) {
|
|
|
|
this.tabId = tabContext.tabId;
|
|
|
|
this.rawUrl = tabContext.rawURL;
|
|
|
|
this.pageUrl = tabContext.normalURL;
|
|
|
|
this.pageHostname = tabContext.rootHostname;
|
|
|
|
this.pageDomain = tabContext.rootDomain;
|
2015-05-16 15:05:37 +12:00
|
|
|
this.title = '';
|
2014-10-18 08:01:09 +13:00
|
|
|
this.requests = µm.PageRequestStats.factory();
|
|
|
|
this.domains = {};
|
2014-10-28 03:20:19 +13:00
|
|
|
this.allHostnamesString = ' ';
|
2014-10-18 08:01:09 +13:00
|
|
|
this.requestStats.reset();
|
|
|
|
this.distinctRequestCount = 0;
|
|
|
|
this.perLoadAllowedRequestCount = 0;
|
|
|
|
this.perLoadBlockedRequestCount = 0;
|
2015-04-22 08:22:29 +12:00
|
|
|
this.incinerationTimer = null;
|
2015-05-07 05:01:02 +12:00
|
|
|
this.mtxContentModifiedTime = 0;
|
|
|
|
this.mtxCountModifiedTime = 0;
|
2014-10-18 08:01:09 +13:00
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
PageStore.prototype.dispose = function() {
|
|
|
|
this.requests.dispose();
|
2015-04-22 08:22:29 +12:00
|
|
|
this.rawUrl = '';
|
2014-10-18 08:01:09 +13:00
|
|
|
this.pageUrl = '';
|
|
|
|
this.pageHostname = '';
|
|
|
|
this.pageDomain = '';
|
2015-05-16 15:05:37 +12:00
|
|
|
this.title = '';
|
2014-10-18 08:01:09 +13:00
|
|
|
this.domains = {};
|
2014-10-28 03:20:19 +13:00
|
|
|
this.allHostnamesString = ' ';
|
2015-04-22 08:22:29 +12:00
|
|
|
|
|
|
|
if ( this.incinerationTimer !== null ) {
|
|
|
|
clearTimeout(this.incinerationTimer);
|
|
|
|
this.incinerationTimer = null;
|
|
|
|
}
|
|
|
|
|
2014-10-18 08:01:09 +13:00
|
|
|
if ( pageStoreJunkyard.length < 8 ) {
|
|
|
|
pageStoreJunkyard.push(this);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2014-10-25 04:13:46 +13:00
|
|
|
PageStore.prototype.recordRequest = function(type, url, block) {
|
2015-05-15 13:04:49 +12:00
|
|
|
if ( !this.requests.createEntryIfNotExists(url, type, block) ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-10-18 08:01:09 +13:00
|
|
|
// Count blocked/allowed requests
|
|
|
|
this.requestStats.record(type, block);
|
|
|
|
|
|
|
|
// https://github.com/gorhill/httpswitchboard/issues/306
|
|
|
|
// If it is recorded locally, record globally
|
|
|
|
µm.requestStats.record(type, block);
|
2015-05-02 15:54:32 +12:00
|
|
|
µm.updateBadgeAsync(this.tabId);
|
2014-10-18 08:01:09 +13:00
|
|
|
|
|
|
|
if ( block !== false ) {
|
|
|
|
this.perLoadBlockedRequestCount++;
|
|
|
|
} else {
|
|
|
|
this.perLoadAllowedRequestCount++;
|
|
|
|
}
|
|
|
|
|
|
|
|
var hostname = µm.URI.hostnameFromURI(url);
|
|
|
|
|
|
|
|
this.distinctRequestCount++;
|
2015-05-07 05:01:02 +12:00
|
|
|
this.mtxCountModifiedTime = Date.now();
|
|
|
|
|
2014-10-28 03:20:19 +13:00
|
|
|
if ( this.domains.hasOwnProperty(hostname) === false ) {
|
|
|
|
this.domains[hostname] = true;
|
|
|
|
this.allHostnamesString += hostname + ' ';
|
2015-05-07 05:01:02 +12:00
|
|
|
this.mtxContentModifiedTime = Date.now();
|
2014-10-28 03:20:19 +13:00
|
|
|
}
|
2014-10-18 08:01:09 +13:00
|
|
|
|
2014-10-28 17:14:15 +13:00
|
|
|
// console.debug("pagestats.js > PageStore.recordRequest(): %o: %s @ %s", this, type, url);
|
2014-10-18 08:01:09 +13:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
return {
|
|
|
|
factory: pageStoreFactory
|
|
|
|
};
|
|
|
|
|
2015-04-12 09:15:57 +12:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2014-10-18 08:01:09 +13:00
|
|
|
})();
|
|
|
|
|
|
|
|
/******************************************************************************/
|