diff options
Diffstat (limited to 'js/pagestats.js')
-rw-r--r-- | js/pagestats.js | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/js/pagestats.js b/js/pagestats.js new file mode 100644 index 0000000..37e94c7 --- /dev/null +++ b/js/pagestats.js @@ -0,0 +1,274 @@ +/******************************************************************************* + + ηMatrix - a browser extension to black/white list requests. + Copyright (C) 2013-2019 Raymond Hill + Copyright (C) 2019 Alessio Vanni + + 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/}. + + uMatrix Home: https://github.com/gorhill/uMatrix +*/ + +'use strict'; + +/******************************************************************************/ + +µMatrix.pageStoreFactory = (function() { + +/******************************************************************************/ + +var µm = µMatrix; + +/******************************************************************************/ + +var BlockedCollapsibles = function() { + this.boundPruneAsyncCallback = this.pruneAsyncCallback.bind(this); + this.blocked = new Map(); + this.hash = 0; + this.timer = null; +}; + +BlockedCollapsibles.prototype = { + + shelfLife: 10 * 1000, + + add: function(type, url, isSpecific) { + if ( this.blocked.size === 0 ) { this.pruneAsync(); } + var now = Date.now() / 1000 | 0; + // The following "trick" is to encode the specifity into the lsb of the + // time stamp so as to avoid to have to allocate a memory structure to + // store both time stamp and specificity. + if ( isSpecific ) { + now |= 0x00000001; + } else { + now &= 0xFFFFFFFE; + } + this.blocked.set(type + ' ' + url, now); + this.hash = now; + }, + + reset: function() { + this.blocked.clear(); + this.hash = 0; + if ( this.timer !== null ) { + clearTimeout(this.timer); + this.timer = null; + } + }, + + pruneAsync: function() { + if ( this.timer === null ) { + this.timer = vAPI.setTimeout( + this.boundPruneAsyncCallback, + this.shelfLife * 2 + ); + } + }, + + pruneAsyncCallback: function() { + this.timer = null; + var obsolete = Date.now() - this.shelfLife; + for ( var entry of this.blocked ) { + if ( entry[1] <= obsolete ) { + this.blocked.delete(entry[0]); + } + } + if ( this.blocked.size !== 0 ) { this.pruneAsync(); } + } +}; + +/******************************************************************************/ + +// Ref: Given a URL, returns a (somewhat) unique 32-bit value +// Based on: FNV32a +// http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-reference-source +// The rest is custom, suited for uMatrix. + +var PageStore = function(tabContext) { + this.hostnameTypeCells = new Map(); + this.domains = new Set(); + this.blockedCollapsibles = new BlockedCollapsibles(); + this.requestStats = µm.requestStatsFactory(); + this.off = false; + this.init(tabContext); +}; + +PageStore.prototype = { + + collapsibleTypes: new Set([ 'image' ]), + pageStoreJunkyard: [], + + init: function(tabContext) { + this.tabId = tabContext.tabId; + this.rawUrl = tabContext.rawURL; + this.pageUrl = tabContext.normalURL; + this.pageHostname = tabContext.rootHostname; + this.pageDomain = tabContext.rootDomain; + this.title = ''; + this.hostnameTypeCells.clear(); + this.domains.clear(); + this.allHostnamesString = ' '; + this.blockedCollapsibles.reset(); + this.requestStats.reset(); + this.distinctRequestCount = 0; + this.perLoadAllowedRequestCount = 0; + this.perLoadBlockedRequestCount = 0; + this.has3pReferrer = false; + this.hasMixedContent = false; + this.hasNoscriptTags = false; + this.hasWebWorkers = false; + this.incinerationTimer = null; + this.mtxContentModifiedTime = 0; + this.mtxCountModifiedTime = 0; + return this; + }, + + dispose: function() { + this.rawUrl = ''; + this.pageUrl = ''; + this.pageHostname = ''; + this.pageDomain = ''; + this.title = ''; + this.hostnameTypeCells.clear(); + this.domains.clear(); + this.allHostnamesString = ' '; + this.blockedCollapsibles.reset(); + if ( this.incinerationTimer !== null ) { + clearTimeout(this.incinerationTimer); + this.incinerationTimer = null; + } + if ( this.pageStoreJunkyard.length < 8 ) { + this.pageStoreJunkyard.push(this); + } + }, + + cacheBlockedCollapsible: function(type, url, specificity) { + if ( this.collapsibleTypes.has(type) ) { + this.blockedCollapsibles.add( + type, + url, + specificity !== 0 && specificity < 5 + ); + } + }, + + lookupBlockedCollapsibles: function(request, response) { + var tabContext = µm.tabContextManager.lookup(this.tabId); + if ( tabContext === null ) { return; } + + var collapseBlacklisted = µm.userSettings.collapseBlacklisted, + collapseBlocked = µm.userSettings.collapseBlocked, + entry; + + var blockedResources = response.blockedResources; + + if ( + Array.isArray(request.toFilter) && + request.toFilter.length !== 0 + ) { + var roothn = tabContext.rootHostname, + hnFromURI = µm.URI.hostnameFromURI, + tMatrix = µm.tMatrix; + for ( entry of request.toFilter ) { + if ( tMatrix.mustBlock(roothn, hnFromURI(entry.url), entry.type) === false ) { + continue; + } + blockedResources.push([ + entry.type + ' ' + entry.url, + collapseBlocked || + collapseBlacklisted && tMatrix.specificityRegister !== 0 && + tMatrix.specificityRegister < 5 + ]); + } + } + + if ( this.blockedCollapsibles.hash === response.hash ) { return; } + response.hash = this.blockedCollapsibles.hash; + + for ( entry of this.blockedCollapsibles.blocked ) { + blockedResources.push([ + entry[0], + collapseBlocked || collapseBlacklisted && (entry[1] & 1) !== 0 + ]); + } + }, + + recordRequest: function(type, url, block) { + // Store distinct network requests. This is used to: + // - remember which hostname/type were seen + // - count the number of distinct URLs for any given + // hostname-type pair + var hostname = µm.URI.hostnameFromURI(url), + key = hostname + ' ' + type, + uids = this.hostnameTypeCells.get(key); + if ( uids === undefined ) { + this.hostnameTypeCells.set(key, (uids = new Set())); + } else if ( uids.size > 99 ) { + return; + } + var uid = this.uidFromURL(url); + if ( uids.has(uid) ) { return; } + uids.add(uid); + + // 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); + µm.updateBadgeAsync(this.tabId); + + if ( block !== false ) { + this.perLoadBlockedRequestCount++; + } else { + this.perLoadAllowedRequestCount++; + } + + this.distinctRequestCount++; + this.mtxCountModifiedTime = Date.now(); + + if ( this.domains.has(hostname) === false ) { + this.domains.add(hostname); + this.allHostnamesString += hostname + ' '; + this.mtxContentModifiedTime = Date.now(); + } + }, + + uidFromURL: function(uri) { + var hint = 0x811c9dc5, + i = uri.length; + while ( i-- ) { + hint ^= uri.charCodeAt(i) | 0; + hint += (hint<<1) + (hint<<4) + (hint<<7) + (hint<<8) + (hint<<24) | 0; + hint >>>= 0; + } + return hint; + } +}; + +/******************************************************************************/ + +return function pageStoreFactory(tabContext) { + var entry = PageStore.prototype.pageStoreJunkyard.pop(); + if ( entry ) { + return entry.init(tabContext); + } + return new PageStore(tabContext); +}; + +/******************************************************************************/ + +})(); + +/******************************************************************************/ |