diff --git a/src/js/background.js b/src/js/background.js index 1c53b25..95a8b60 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -101,7 +101,7 @@ return { // urls stats are kept on the back burner while waiting to be reactivated // in a tab or another. - pageStats: {}, + pageStats: {}, // TODO: rename // A map of redirects, to allow reverse lookup of redirects from landing // page, so that redirection can be reported to the user. diff --git a/src/js/pagestats.js b/src/js/pagestats.js index bfc9f5e..0c29cf3 100644 --- a/src/js/pagestats.js +++ b/src/js/pagestats.js @@ -269,18 +269,18 @@ var makeRequestKey = function(uri, reqType) { } var key = typeToCode[reqType] || 'z'; return key + - String.fromCharCode(hint >>> 16, hint & 0xFFFF) + + String.fromCharCode(hint >>> 22, hint >>> 12 & 0x3FF, hint & 0xFFF) + stringPacker.pack(µmuri.hostnameFromURI(uri)); }; /******************************************************************************/ var rememberRequestKey = function(reqKey) { - stringPacker.remember(reqKey.slice(3)); + stringPacker.remember(reqKey.slice(4)); }; var forgetRequestKey = function(reqKey) { - stringPacker.forget(reqKey.slice(3)); + stringPacker.forget(reqKey.slice(4)); }; /******************************************************************************/ @@ -288,7 +288,7 @@ var forgetRequestKey = function(reqKey) { // Exported var hostnameFromRequestKey = function(reqKey) { - return stringPacker.unpack(reqKey.slice(3)); + return stringPacker.unpack(reqKey.slice(4)); }; PageRequestStats.prototype.hostnameFromRequestKey = hostnameFromRequestKey; @@ -436,7 +436,6 @@ var pageStoreFactory = function(pageUrl) { /******************************************************************************/ function PageStore(pageUrl) { - this.visible = false; this.requestStats = new WebRequestStats(); this.off = false; this.init(pageUrl); @@ -458,6 +457,8 @@ PageStore.prototype.init = function(pageUrl) { this.distinctRequestCount = 0; this.perLoadAllowedRequestCount = 0; this.perLoadBlockedRequestCount = 0; + this.boundCount = 0; + this.obsoleteAfter = 0; return this; }; @@ -465,18 +466,12 @@ PageStore.prototype.init = function(pageUrl) { PageStore.prototype.dispose = function() { 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.pageHostname = ''; this.pageDomain = ''; this.domains = {}; this.allHostnamesString = ' '; this.state = {}; - if ( pageStoreJunkyard.length < 8 ) { pageStoreJunkyard.push(this); } @@ -487,7 +482,7 @@ PageStore.prototype.dispose = function() { PageStore.prototype.recordRequest = function(type, url, block) { // TODO: this makes no sense, I forgot why I put this here. if ( !this ) { - // console.error('HTTP Switchboard> PageStore.recordRequest(): no pageStats'); + // console.error('pagestats.js > PageStore.recordRequest(): no pageStats'); return; } @@ -541,7 +536,7 @@ PageStore.prototype.recordRequest = function(type, url, block) { } µm.urlStatsChanged(this.pageUrl); - // console.debug("HTTP Switchboard> PageStore.recordRequest(): %o: %s @ %s", this, type, url); + // console.debug("pagestats.js > PageStore.recordRequest(): %o: %s @ %s", this, type, url); }; /******************************************************************************/ diff --git a/src/js/start.js b/src/js/start.js index 71a87f4..ffbfd39 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -29,9 +29,13 @@ // normal way forbid binding behind the scene tab. // https://github.com/gorhill/httpswitchboard/issues/67 -µMatrix.createPageStats(µMatrix.behindTheSceneURL); -µMatrix.pageUrlToTabId[µMatrix.behindTheSceneURL] = µMatrix.behindTheSceneTabId; -µMatrix.tabIdToPageUrl[µMatrix.behindTheSceneTabId] = µMatrix.behindTheSceneURL; +(function() { + var µm = µMatrix; + var pageStore = µm.createPageStore(µm.behindTheSceneURL); + µm.pageUrlToTabId[µm.behindTheSceneURL] = µm.behindTheSceneTabId; + µm.tabIdToPageUrl[µm.behindTheSceneTabId] = µm.behindTheSceneURL; + pageStore.boundCount += 1; +})(); /******************************************************************************/ diff --git a/src/js/tab.js b/src/js/tab.js index 41ce8d4..97bea6a 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -25,27 +25,34 @@ // Create a new page url stats store (if not already present) -µMatrix.createPageStats = function(pageUrl) { +µMatrix.createPageStore = function(pageURL) { // https://github.com/gorhill/httpswitchboard/issues/303 // At this point, the URL has been page-URL-normalized // do not create stats store for urls which are of no interest - if ( pageUrl.search(/^https?:\/\//) !== 0 ) { - return undefined; + if ( pageURL.search(/^https?/) !== 0 ) { + return; } - var pageStats = this.pageStats[pageUrl]; - if ( !pageStats ) { - pageStats = this.PageStore.factory(pageUrl); + var pageStore = null; + if ( this.pageStats.hasOwnProperty(pageURL) ) { + pageStore = this.pageStats[pageURL]; + } + if ( pageStore === null ) { + pageStore = this.PageStore.factory(pageURL); // These counters are used so that icon presents an overview of how // much allowed/blocked. - pageStats.perLoadAllowedRequestCount = - pageStats.perLoadBlockedRequestCount = 0; - this.pageStats[pageUrl] = pageStats; - } else if ( pageStats.pageUrl !== pageUrl ) { - pageStats.init(pageUrl); + pageStore.perLoadAllowedRequestCount = + pageStore.perLoadBlockedRequestCount = 0; + this.pageStats[pageURL] = pageStore; } - return pageStats; + // TODO: revisit code, need to account for those web pages for which the + // URL changes with the content only updated + if ( pageStore.pageUrl !== pageURL ) { + pageStore.init(pageURL); + } + + return pageStore; }; /******************************************************************************/ @@ -54,7 +61,7 @@ // Some kind of trick going on here: // Any scheme other than 'http' and 'https' is remapped into a fake // URL which trick the rest of µMatrix into being able to process an -// otherwise unmanageable scheme. µMatrix needs web page to have a proper +// otherwise unmanageable scheme. µMatrix needs web pages to have a proper // hostname to work properly, so just like the 'chromium-behind-the-scene' // fake domain name, we map unknown schemes into a fake '{scheme}-scheme' // hostname. This way, for a specific scheme you can create scope with @@ -89,15 +96,19 @@ // Normalize to a page-URL. pageURL = this.normalizePageURL(pageURL); - var pageStats = this.createPageStats(pageURL); + if ( this.tabIdToPageUrl[tabId] === pageURL ) { + return this.pageStats[pageURL]; + } - // console.debug('tab.js > µMatrix.bindTabToPageStats(): dispatching traffic in tab id %d to url stats store "%s"', tabId, pageUrl); + var pageStore = this.createPageStore(pageURL); + + // console.debug('tab.js > bindTabToPageStats(): dispatching traffic in tab id %d to page store "%s"', tabId, pageUrl); // rhill 2013-11-24: Never ever rebind chromium-behind-the-scene // virtual tab. // https://github.com/gorhill/httpswitchboard/issues/67 if ( tabId === this.behindTheSceneTabId ) { - return pageStats; + return pageStore; } this.unbindTabFromPageStats(tabId); @@ -105,23 +116,33 @@ // rhill 2014-02-08: Do not create an entry if no page store // exists (like when visiting about:blank) // https://github.com/gorhill/httpswitchboard/issues/186 - if ( !pageStats ) { + if ( !pageStore ) { return null; } - pageStats.visible = true; this.pageUrlToTabId[pageURL] = tabId; this.tabIdToPageUrl[tabId] = pageURL; + pageStore.boundCount += 1; - return pageStats; + return pageStore; }; +/******************************************************************************/ + µMatrix.unbindTabFromPageStats = function(tabId) { - var pageUrl = this.tabIdToPageUrl[tabId]; - if ( pageUrl ) { - delete this.pageUrlToTabId[pageUrl]; + if ( this.tabIdToPageUrl.hasOwnProperty(tabId) === false ) { + return; + } + var pageURL = this.tabIdToPageUrl[tabId]; + if ( this.pageStats.hasOwnProperty(pageURL) ) { + var pageStore = this.pageStats[pageURL]; + pageStore.boundCount -= 1; + if ( pageStore.boundCount === 0 ) { + pageStore.obsoleteAfter = Date.now() + (5 * 60 * 60 * 1000); + } } delete this.tabIdToPageUrl[tabId]; + delete this.pageUrlToTabId[pageURL]; }; /******************************************************************************/ @@ -355,59 +376,43 @@ // Garbage collect stale url stats entries (function() { - var gcOrphanPageStats = function(tabs) { - var µm = µMatrix; - var visibleTabs = {}; - tabs.map(function(tab) { - visibleTabs[tab.id] = true; - }); - var pageUrls = Object.keys(µm.pageStats); - var i = pageUrls.length; - var pageUrl, tabId, pageStats; - while ( i-- ) { - pageUrl = pageUrls[i]; - // Do not dispose of chromium-behind-the-scene virtual tab, - // GC is done differently on this one (i.e. just pruning). - if ( pageUrl === µm.behindTheSceneURL ) { + var µm = µMatrix; + var gcPageStats = function() { + var pageStore; + var now = Date.now(); + for ( var pageURL in µm.pageStats ) { + if ( µm.pageStats.hasOwnProperty(pageURL) === false ) { continue; } - tabId = µm.tabIdFromPageUrl(pageUrl); - pageStats = µm.pageStats[pageUrl]; - if ( !visibleTabs[tabId] && !pageStats.visible ) { - // console.debug('HTTP Switchboard> tab.js: page stats garbage collector letting go of "%s"', pageUrl); - µm.cookieHunter.removePageCookies(pageStats); - µm.pageStats[pageUrl].dispose(); - delete µm.pageStats[pageUrl]; + pageStore = µm.pageStats[pageURL]; + if ( pageStore.boundCount !== 0 ) { + continue; } - pageStats.visible = !!visibleTabs[tabId]; - if ( !pageStats.visible ) { - µm.unbindTabFromPageStats(tabId); + if ( pageStore.obsoleteAfter > now ) { + continue; } + µm.cookieHunter.removePageCookies(pageStore); + µm.pageStats[pageURL].dispose(); + delete µm.pageStats[pageURL]; } - }; - - var gcPageStats = function() { - var µm = µMatrix; - - // Get rid of stale pageStats, those not bound to a tab for more than - // {duration placeholder}. - chrome.tabs.query({ 'url': '' }, gcOrphanPageStats); // Prune content of chromium-behind-the-scene virtual tab // When `suggest-as-you-type` is on in Chromium, this can lead to a // LOT of uninteresting behind the scene requests. - var pageStats = µm.pageStats[µm.behindTheSceneURL]; - if ( pageStats ) { - var reqKeys = pageStats.requests.getRequestKeys(); - if ( reqKeys.length > µm.behindTheSceneMaxReq ) { - reqKeys = reqKeys.sort(function(a,b){ - return pageStats.requests[b] - pageStats.requests[a]; - }).slice(µm.behindTheSceneMaxReq); - var iReqKey = reqKeys.length; - while ( iReqKey-- ) { - pageStats.requests.disposeOne(reqKeys[iReqKey]); - } - } + pageStore = µm.pageStats[µm.behindTheSceneURL]; + if ( !pageStore ) { + return; + } + var reqKeys = pageStore.requests.getRequestKeys(); + if ( reqKeys.length <= µm.behindTheSceneMaxReq ) { + return; + } + reqKeys = reqKeys.sort(function(a,b){ + return pageStore.requests[b] - pageStore.requests[a]; + }).slice(µm.behindTheSceneMaxReq); + var iReqKey = reqKeys.length; + while ( iReqKey-- ) { + pageStore.requests.disposeOne(reqKeys[iReqKey]); } }; @@ -417,7 +422,7 @@ 'gcPageStats', null, gcPageStats, - 8 * 60 * 1000, + 5 * 60 * 1000, true ); })(); diff --git a/src/js/uritools.js b/src/js/uritools.js index 2216e40..acfd240 100644 --- a/src/js/uritools.js +++ b/src/js/uritools.js @@ -227,7 +227,7 @@ URI.assemble = function(bits) { URI.schemeFromURI = function(uri) { var matches = reSchemeFromURI.exec(uri); - if ( !matches ) { + if ( matches === null ) { return ''; } return matches[0].slice(0, -1).toLowerCase();