From 3ff3ae7d7036df5e3a2298300dc12cac7b696ac5 Mon Sep 17 00:00:00 2001 From: gorhill Date: Sat, 8 Oct 2016 10:15:31 -0400 Subject: [PATCH] fix #2053 --- src/js/background.js | 4 +- src/js/dynamic-net-filtering.js | 3 - src/js/messaging.js | 39 +++++----- src/js/pagestore.js | 130 ++++++++++++++++++++++++-------- src/js/storage.js | 4 +- src/js/tab.js | 29 ++----- src/js/traffic.js | 17 ++--- 7 files changed, 135 insertions(+), 91 deletions(-) diff --git a/src/js/background.js b/src/js/background.js index 69ca57b92..ad6dce594 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -89,8 +89,8 @@ return { blockedRequestCount: 0, allowedRequestCount: 0 }, - localSettingsModifyTime: 0, - localSettingsSaveTime: 0, + localSettingsLastModified: 0, + localSettingsLastSaved: 0, // read-only systemSettings: { diff --git a/src/js/dynamic-net-filtering.js b/src/js/dynamic-net-filtering.js index fc89f24cc..018a329f4 100644 --- a/src/js/dynamic-net-filtering.js +++ b/src/js/dynamic-net-filtering.js @@ -217,9 +217,6 @@ Matrix.prototype.hasSameRules = function(other, srcHostname, desHostnames) { // Specific destinations for ( var desHostname in desHostnames ) { - if ( desHostnames.hasOwnProperty(desHostname) === false ) { - continue; - } ruleKey = '* ' + desHostname; if ( (thisRules[ruleKey] || 0) !== (otherRules[ruleKey] || 0) ) { return false; diff --git a/src/js/messaging.js b/src/js/messaging.js index 4d4487219..29738d61c 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -199,22 +199,27 @@ var µb = µBlock; /******************************************************************************/ var getHostnameDict = function(hostnameToCountMap) { - var r = {}, de; - var domainFromHostname = µb.URI.domainFromHostname; - var domain, counts, blockCount, allowCount; - for ( var hostname in hostnameToCountMap ) { - if ( hostnameToCountMap.hasOwnProperty(hostname) === false ) { - continue; + var r = Object.create(null), + domainEntry, + domainFromHostname = µb.URI.domainFromHostname, + domain, blockCount, allowCount, + iter = hostnameToCountMap.entries(), + entry, hostname, counts; + for (;;) { + entry = iter.next(); + if ( entry.done ) { + break; } - if ( r.hasOwnProperty(hostname) ) { + hostname = entry.value[0]; + if ( r[hostname] !== undefined ) { continue; } domain = domainFromHostname(hostname) || hostname; - counts = hostnameToCountMap[domain] || 0; + counts = hostnameToCountMap.get(domain) || 0; blockCount = counts & 0xFFFF; allowCount = counts >>> 16 & 0xFFFF; - if ( r.hasOwnProperty(domain) === false ) { - de = r[domain] = { + if ( r[domain] === undefined ) { + domainEntry = r[domain] = { domain: domain, blockCount: blockCount, allowCount: allowCount, @@ -222,13 +227,13 @@ var getHostnameDict = function(hostnameToCountMap) { totalAllowCount: allowCount }; } else { - de = r[domain]; + domainEntry = r[domain]; } - counts = hostnameToCountMap[hostname] || 0; + counts = entry.value[1]; blockCount = counts & 0xFFFF; allowCount = counts >>> 16 & 0xFFFF; - de.totalBlockCount += blockCount; - de.totalAllowCount += allowCount; + domainEntry.totalBlockCount += blockCount; + domainEntry.totalAllowCount += allowCount; if ( hostname === domain ) { continue; } @@ -268,10 +273,8 @@ var getFirewallRules = function(srcHostname, desHostnames) { r['. * 3p-frame'] = df.evaluateCellZY(srcHostname, '*', '3p-frame').toFilterString(); for ( var desHostname in desHostnames ) { - if ( desHostnames.hasOwnProperty(desHostname) ) { - r['/ ' + desHostname + ' *'] = df.evaluateCellZY('*', desHostname, '*').toFilterString(); - r['. ' + desHostname + ' *'] = df.evaluateCellZY(srcHostname, desHostname, '*').toFilterString(); - } + r['/ ' + desHostname + ' *'] = df.evaluateCellZY('*', desHostname, '*').toFilterString(); + r['. ' + desHostname + ' *'] = df.evaluateCellZY(srcHostname, desHostname, '*').toFilterString(); } return r; }; diff --git a/src/js/pagestore.js b/src/js/pagestore.js index a0bb640b3..e4860cfaa 100644 --- a/src/js/pagestore.js +++ b/src/js/pagestore.js @@ -266,6 +266,10 @@ var pageStoreJunkyardMax = 10; var PageStore = function(tabId) { this.init(tabId); + this.journal = []; + this.journalTimer = null; + this.journalLastCommitted = this.journalLastUncommitted = undefined; + this.journalLastUncommittedURL = undefined; }; /******************************************************************************/ @@ -298,7 +302,7 @@ PageStore.prototype.init = function(tabId) { this.tabHostname = tabContext.rootHostname; this.title = tabContext.rawURL; this.rawURL = tabContext.rawURL; - this.hostnameToCountMap = {}; + this.hostnameToCountMap = new Map(); this.contentLastModified = 0; this.frames = Object.create(null); this.perLoadBlockedRequestCount = 0; @@ -388,10 +392,6 @@ PageStore.prototype.reuse = function(context) { /******************************************************************************/ PageStore.prototype.dispose = function() { - // 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.tabHostname = ''; this.title = ''; this.rawURL = ''; @@ -403,6 +403,12 @@ PageStore.prototype.dispose = function() { } this.disposeFrameStores(); this.netFilteringCache = this.netFilteringCache.dispose(); + if ( this.journalTimer !== null ) { + clearTimeout(this.journalTimer); + this.journalTimer = null; + } + this.journal = []; + this.journalLastUncommittedURL = undefined; if ( pageStoreJunkyard.length < pageStoreJunkyardMax ) { pageStoreJunkyard.push(this); } @@ -519,6 +525,92 @@ PageStore.prototype.temporarilyAllowLargeMediaElements = function() { /******************************************************************************/ +// https://github.com/gorhill/uBlock/issues/2053 +// There is no way around using journaling to ensure we deal properly with +// potentially out of order navigation events vs. network request events. + +PageStore.prototype.journalAddRequest = function(hostname, result) { + if ( hostname === '' ) { return; } + this.journal.push( + hostname, + result.charCodeAt(1) === 0x62 /* 'b' */ ? 0x00000001 : 0x00010000 + ); + if ( this.journalTimer === null ) { + this.journalTimer = vAPI.setTimeout(this.journalProcess.bind(this, true), 1000); + } +}; + +PageStore.prototype.journalAddRootFrame = function(type, url) { + if ( type === 'committed' ) { + this.journalLastCommitted = this.journal.length; + if ( + this.journalLastUncommitted !== undefined && + this.journalLastUncommitted < this.journalLastCommitted && + this.journalLastUncommittedURL === url + ) { + this.journalLastCommitted = this.journalLastUncommitted; + this.journalLastUncommitted = undefined; + } + } else if ( type === 'uncommitted' ) { + this.journalLastUncommitted = this.journal.length; + this.journalLastUncommittedURL = url; + } + if ( this.journalTimer !== null ) { + clearTimeout(this.journalTimer); + } + this.journalTimer = vAPI.setTimeout(this.journalProcess.bind(this, true), 1000); +}; + +PageStore.prototype.journalProcess = function(fromTimer) { + if ( !fromTimer ) { + clearTimeout(this.journalTimer); + } + this.journalTimer = null; + + var journal = this.journal, + i, n = journal.length, + hostname, count, hostnameCounts, + aggregateCounts = 0, + now = Date.now(), + pivot = this.journalLastCommitted || 0; + + // Everything after pivot originates from current page. + for ( i = pivot; i < n; i += 2 ) { + hostname = journal[i]; + hostnameCounts = this.hostnameToCountMap.get(hostname); + if ( hostnameCounts === undefined ) { + hostnameCounts = 0; + this.contentLastModified = now; + } + count = journal[i+1]; + this.hostnameToCountMap.set(hostname, hostnameCounts + count); + aggregateCounts += count; + } + this.perLoadBlockedRequestCount += aggregateCounts & 0xFFFF; + this.perLoadAllowedRequestCount += aggregateCounts >>> 16 & 0xFFFF; + this.journalLastCommitted = undefined; + + // https://github.com/chrisaljoudi/uBlock/issues/905#issuecomment-76543649 + // No point updating the badge if it's not being displayed. + if ( (aggregateCounts & 0xFFFF) && µb.userSettings.showIconBadge ) { + µb.updateBadgeAsync(this.tabId); + } + + // Everything before pivot does not originate from current page -- we still + // need to bump global blocked/allowed counts. + for ( i = 0; i < pivot; i += 2 ) { + aggregateCounts += journal[i+1]; + } + if ( aggregateCounts !== 0 ) { + µb.localSettings.blockedRequestCount += aggregateCounts & 0xFFFF; + µb.localSettings.allowedRequestCount += aggregateCounts >>> 16 & 0xFFFF; + µb.localSettingsLastModified = now; + } + journal.length = 0; +}; + +/******************************************************************************/ + PageStore.prototype.filterRequest = function(context) { var requestType = context.requestType; @@ -625,34 +717,6 @@ PageStore.prototype.filterRequestNoCache = function(context) { return result; }; -/******************************************************************************/ - -PageStore.prototype.logRequest = function(context, result) { - var requestHostname = context.requestHostname; - // rhill 20150206: - // be prepared to handle invalid requestHostname, I've seen this - // happen: http://./ - if ( requestHostname === '' ) { - requestHostname = context.rootHostname; - } - var now = Date.now(); - if ( this.hostnameToCountMap.hasOwnProperty(requestHostname) === false ) { - this.hostnameToCountMap[requestHostname] = 0; - this.contentLastModified = now; - } - var c = result.charAt(1); - if ( c === 'b' ) { - this.hostnameToCountMap[requestHostname] += 0x00000001; - this.perLoadBlockedRequestCount++; - µb.localSettings.blockedRequestCount++; - } else /* if ( c === '' || c === 'a' || c === 'n' ) */ { - this.hostnameToCountMap[requestHostname] += 0x00010000; - this.perLoadAllowedRequestCount++; - µb.localSettings.allowedRequestCount++; - } - µb.localSettingsModifyTime = now; -}; - // https://www.youtube.com/watch?v=drW8p_dTLD4 /******************************************************************************/ diff --git a/src/js/storage.js b/src/js/storage.js index d5838bffa..756373369 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -50,13 +50,13 @@ var saveAfter = 4 * 60 * 1000; var save = function() { - this.localSettingsSaveTime = Date.now(); + this.localSettingsLastSaved = Date.now(); vAPI.storage.set(this.localSettings); }; var onTimeout = function() { var µb = µBlock; - if ( µb.localSettingsModifyTime > µb.localSettingsSaveTime ) { + if ( µb.localSettingsLastModified > µb.localSettingsLastSaved ) { save.call(µb); } vAPI.setTimeout(onTimeout, saveAfter); diff --git a/src/js/tab.js b/src/js/tab.js index 6ca1c7818..d4b311887 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -465,23 +465,10 @@ vAPI.tabs.onNavigation = function(details) { if ( details.frameId !== 0 ) { return; } - - var tabContext = µb.tabContextManager.commit(details.tabId, details.url); - var pageStore = µb.bindTabToPageStats(details.tabId, 'tabChanged'); - - // https://github.com/chrisaljoudi/uBlock/issues/630 - // The hostname of the bound document must always be present in the - // mini-matrix. That's the best place I could find for the fix, all other - // options had bad side-effects or complications. - // TODO: Eventually, we will have to use an API to check whether a scheme - // is supported as I suspect we are going to start to see `ws`, `wss` - // as well soon. - if ( - pageStore && - tabContext.rawURL.startsWith('http') && - pageStore.hostnameToCountMap.hasOwnProperty(tabContext.rootHostname) === false - ) { - pageStore.hostnameToCountMap[tabContext.rootHostname] = 0x00010000; + µb.tabContextManager.commit(details.tabId, details.url); + var pageStore = µb.bindTabToPageStats(details.tabId, 'tabCommitted'); + if ( pageStore ) { + pageStore.journalAddRootFrame('committed', details.url); } }; @@ -778,7 +765,7 @@ vAPI.tabs.onPopupUpdated = (function() { // filtering pane. var pageStore = µb.pageStoreFromTabId(openerTabId); if ( pageStore ) { - pageStore.logRequest(context, result); + pageStore.journalAddRequest(context.requestHostname, result); pageStore.popupBlockedCount += 1; } @@ -827,13 +814,13 @@ vAPI.tabs.registerListeners(); } // https://github.com/chrisaljoudi/uBlock/issues/516 - // Never rebind behind-the-scene scope + // Never rebind behind-the-scene scope. if ( vAPI.isBehindTheSceneTabId(tabId) ) { return pageStore; } - // https://github.com/gorhill/uBlock/issues/516 - // If context if 'beforeRequest', do not rebind, wait for confirmation. + // https://github.com/chrisaljoudi/uBlock/issues/516 + // If context is 'beforeRequest', do not rebind, wait for confirmation. if ( context === 'beforeRequest' ) { return pageStore; } diff --git a/src/js/traffic.js b/src/js/traffic.js index 9c97dab13..38deba25c 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -89,9 +89,7 @@ var onBeforeRequest = function(details) { var result = pageStore.filterRequest(requestContext); - // Possible outcomes: blocked, allowed-passthru, allowed-mirror - - pageStore.logRequest(requestContext, result); + pageStore.journalAddRequest(requestContext.requestHostname, result); if ( µb.logger.isEnabled() ) { µb.logger.writeOne( @@ -117,12 +115,6 @@ var onBeforeRequest = function(details) { // Blocked - // https://github.com/chrisaljoudi/uBlock/issues/905#issuecomment-76543649 - // No point updating the badge if it's not being displayed. - if ( µb.userSettings.showIconBadge ) { - µb.updateBadgeAsync(tabId); - } - // https://github.com/gorhill/uBlock/issues/949 // Redirect blocked request? var url = µb.redirectEngine.toURL(requestContext); @@ -222,7 +214,8 @@ var onBeforeRootFrameRequest = function(details) { // Log var pageStore = µb.bindTabToPageStats(tabId, 'beforeRequest'); if ( pageStore ) { - pageStore.logRequest(context, result); + pageStore.journalAddRootFrame('uncommitted', requestURL); + pageStore.journalAddRequest(requestHostname, result); } if ( µb.logger.isEnabled() ) { @@ -299,7 +292,7 @@ var onBeforeBeacon = function(details) { context.requestType = details.type; // "g" in "gb:" stands for "global setting" var result = µb.userSettings.hyperlinkAuditingDisabled ? 'gb:' : ''; - pageStore.logRequest(context, result); + pageStore.journalAddRequest(context.requestHostname, result); if ( µb.logger.isEnabled() ) { µb.logger.writeOne( tabId, @@ -343,7 +336,7 @@ var onBeforeBehindTheSceneRequest = function(details) { result = pageStore.filterRequestNoCache(context); } - pageStore.logRequest(context, result); + pageStore.journalAddRequest(context.requestHostname, result); if ( µb.logger.isEnabled() ) { µb.logger.writeOne(