diff --git a/src/js/pagestore.js b/src/js/pagestore.js index 06570cb90..cecb31eeb 100644 --- a/src/js/pagestore.js +++ b/src/js/pagestore.js @@ -30,48 +30,43 @@ To create a log of net requests **/ -/******************************************************************************/ -/******************************************************************************/ +{ -µBlock.PageStore = (function() { +// start of private namespace +// >>>>> /******************************************************************************/ const µb = µBlock; -/******************************************************************************/ /******************************************************************************/ // To mitigate memory churning const netFilteringCacheJunkyard = []; const netFilteringCacheJunkyardMax = 10; -/******************************************************************************/ +const NetFilteringResultCache = class { + constructor() { + this.init(); + } -const NetFilteringResultCache = function() { - this.init(); -}; - -NetFilteringResultCache.prototype = { - shelfLife: 15000, - - init: function() { + init() { this.blocked = new Map(); this.results = new Map(); this.hash = 0; this.timer = undefined; return this; - }, + } - dispose: function() { + dispose() { this.empty(); if ( netFilteringCacheJunkyard.length < netFilteringCacheJunkyardMax ) { netFilteringCacheJunkyard.push(this); } return null; - }, + } - rememberResult: function(fctxt, result) { + rememberResult(fctxt, result) { if ( fctxt.tabId <= 0 ) { return; } if ( this.results.size === 0 ) { this.pruneAsync(); @@ -86,9 +81,9 @@ NetFilteringResultCache.prototype = { const now = Date.now(); this.blocked.set(key, now); this.hash = now; - }, + } - rememberBlock: function(fctxt) { + rememberBlock(fctxt) { if ( fctxt.tabId <= 0 ) { return; } if ( this.blocked.size === 0 ) { this.pruneAsync(); @@ -99,9 +94,9 @@ NetFilteringResultCache.prototype = { now ); this.hash = now; - }, + } - empty: function() { + empty() { this.blocked.clear(); this.results.clear(); this.hash = 0; @@ -109,9 +104,9 @@ NetFilteringResultCache.prototype = { clearTimeout(this.timer); this.timer = undefined; } - }, + } - prune: function() { + prune() { const obsolete = Date.now() - this.shelfLife; for ( const entry of this.blocked ) { if ( entry[1] <= obsolete ) { @@ -127,9 +122,9 @@ NetFilteringResultCache.prototype = { if ( this.blocked.size !== 0 || this.results.size !== 0 ) { this.pruneAsync(); } - }, + } - pruneAsync: function() { + pruneAsync() { if ( this.timer !== undefined ) { return; } this.timer = vAPI.setTimeout( ( ) => { @@ -138,17 +133,17 @@ NetFilteringResultCache.prototype = { }, this.shelfLife ); - }, + } - lookupResult: function(fctxt) { + lookupResult(fctxt) { return this.results.get( fctxt.getDocHostname() + ' ' + fctxt.type + ' ' + fctxt.url ); - }, + } - lookupAllBlocked: function(hostname) { + lookupAllBlocked(hostname) { const result = []; for ( const entry of this.blocked ) { const pos = entry[0].indexOf(' '); @@ -157,17 +152,18 @@ NetFilteringResultCache.prototype = { } } return result; - }, + } + + static factory() { + const entry = netFilteringCacheJunkyard.pop(); + return entry !== undefined + ? entry.init() + : new NetFilteringResultCache(); + } }; -NetFilteringResultCache.factory = function() { - const entry = netFilteringCacheJunkyard.pop(); - return entry !== undefined - ? entry.init() - : new NetFilteringResultCache(); -}; +NetFilteringResultCache.prototype.shelfLife = 15000; -/******************************************************************************/ /******************************************************************************/ // Frame stores are used solely to associate a URL with a frame id. The @@ -179,420 +175,518 @@ NetFilteringResultCache.factory = function() { const frameStoreJunkyard = []; const frameStoreJunkyardMax = 50; -/******************************************************************************/ +const FrameStore = class { + constructor(frameURL) { + this.init(frameURL); + } -const FrameStore = function(frameURL) { - this.init(frameURL); -}; - -FrameStore.prototype = { - init: function(frameURL) { + init(frameURL) { const µburi = µb.URI; this.pageHostname = µburi.hostnameFromURI(frameURL); this.pageDomain = µburi.domainFromHostname(this.pageHostname) || this.pageHostname; return this; - }, + } - dispose: function() { + dispose() { this.pageHostname = this.pageDomain = ''; if ( frameStoreJunkyard.length < frameStoreJunkyardMax ) { frameStoreJunkyard.push(this); } return null; - }, -}; - -FrameStore.factory = function(frameURL) { - const entry = frameStoreJunkyard.pop(); - if ( entry === undefined ) { - return new FrameStore(frameURL); } - return entry.init(frameURL); + + static factory(frameURL) { + const entry = frameStoreJunkyard.pop(); + if ( entry === undefined ) { + return new FrameStore(frameURL); + } + return entry.init(frameURL); + } }; -/******************************************************************************/ /******************************************************************************/ // To mitigate memory churning const pageStoreJunkyard = []; const pageStoreJunkyardMax = 10; -/******************************************************************************/ - -const PageStore = function(tabId, context) { - this.extraData = new Map(); - this.journal = []; - this.journalTimer = null; - this.journalLastCommitted = this.journalLastUncommitted = undefined; - this.journalLastUncommittedURL = undefined; - this.init(tabId, context); -}; - -/******************************************************************************/ - -PageStore.factory = function(tabId, context) { - let entry = pageStoreJunkyard.pop(); - if ( entry === undefined ) { - entry = new PageStore(tabId, context); - } else { - entry.init(tabId, context); - } - return entry; -}; - -/******************************************************************************/ - -// https://github.com/gorhill/uBlock/issues/3201 -// The context is used to determine whether we report behavior change to the -// logger. - -PageStore.prototype.init = function(tabId, context) { - const tabContext = µb.tabContextManager.mustLookup(tabId); - this.tabId = tabId; - - // If we are navigating from-to same site, remember whether large - // media elements were temporarily allowed. - if ( - typeof this.allowLargeMediaElementsUntil !== 'number' || - tabContext.rootHostname !== this.tabHostname - ) { - this.allowLargeMediaElementsUntil = 0; +const PageStore = class { + constructor(tabId, context) { + this.extraData = new Map(); + this.journal = []; + this.journalTimer = null; + this.journalLastCommitted = this.journalLastUncommitted = undefined; + this.journalLastUncommittedURL = undefined; + this.init(tabId, context); } - this.tabHostname = tabContext.rootHostname; - this.title = tabContext.rawURL; - this.rawURL = tabContext.rawURL; - this.hostnameToCountMap = new Map(); - this.contentLastModified = 0; - this.frames = new Map(); - this.logData = undefined; - this.perLoadBlockedRequestCount = 0; - this.perLoadAllowedRequestCount = 0; - this.hiddenElementCount = ''; // Empty string means "unknown" - this.remoteFontCount = 0; - this.scriptCount = 0; - this.popupBlockedCount = 0; - this.largeMediaCount = 0; - this.largeMediaTimer = null; - this.netFilteringCache = NetFilteringResultCache.factory(); - this.internalRedirectionCount = 0; - this.extraData.clear(); + static factory(tabId, context) { + let entry = pageStoreJunkyard.pop(); + if ( entry === undefined ) { + entry = new PageStore(tabId, context); + } else { + entry.init(tabId, context); + } + return entry; + } - // The current filtering context is cloned because: - // - We may be called with or without the current context having been - // initialized. - // - If it has been initialized, we do not want to change the state - // of the current context. - const fctxt = µb.logger.enabled - ? µBlock.filteringContext + // https://github.com/gorhill/uBlock/issues/3201 + // The context is used to determine whether we report behavior change + // to the logger. + + init(tabId, context) { + const tabContext = µb.tabContextManager.mustLookup(tabId); + this.tabId = tabId; + + // If we are navigating from-to same site, remember whether large + // media elements were temporarily allowed. + if ( + typeof this.allowLargeMediaElementsUntil !== 'number' || + tabContext.rootHostname !== this.tabHostname + ) { + this.allowLargeMediaElementsUntil = 0; + } + + this.tabHostname = tabContext.rootHostname; + this.title = tabContext.rawURL; + this.rawURL = tabContext.rawURL; + this.hostnameToCountMap = new Map(); + this.contentLastModified = 0; + this.frames = new Map(); + this.logData = undefined; + this.perLoadBlockedRequestCount = 0; + this.perLoadAllowedRequestCount = 0; + this.hiddenElementCount = ''; // Empty string means "unknown" + this.remoteFontCount = 0; + this.scriptCount = 0; + this.popupBlockedCount = 0; + this.largeMediaCount = 0; + this.largeMediaTimer = null; + this.netFilteringCache = NetFilteringResultCache.factory(); + this.internalRedirectionCount = 0; + this.extraData.clear(); + + // The current filtering context is cloned because: + // - We may be called with or without the current context having been + // initialized. + // - If it has been initialized, we do not want to change the state + // of the current context. + const fctxt = µb.logger.enabled + ? µb.filteringContext .duplicate() .fromTabId(tabId) .setURL(tabContext.rawURL) - : undefined; + : undefined; - // https://github.com/uBlockOrigin/uBlock-issues/issues/314 - const masterSwitch = tabContext.getNetFilteringSwitch(); + // https://github.com/uBlockOrigin/uBlock-issues/issues/314 + const masterSwitch = tabContext.getNetFilteringSwitch(); - this.noCosmeticFiltering = µb.sessionSwitches.evaluateZ( - 'no-cosmetic-filtering', - tabContext.rootHostname - ) === true; - if ( - masterSwitch && - this.noCosmeticFiltering && - µb.logger.enabled && - context === 'tabCommitted' - ) { - fctxt.setRealm('cosmetic') - .setType('dom') - .setFilter(µb.sessionSwitches.toLogData()) - .toLogger(); - } + this.noCosmeticFiltering = µb.sessionSwitches.evaluateZ( + 'no-cosmetic-filtering', + tabContext.rootHostname + ) === true; + if ( + masterSwitch && + this.noCosmeticFiltering && + µb.logger.enabled && + context === 'tabCommitted' + ) { + fctxt.setRealm('cosmetic') + .setType('dom') + .setFilter(µb.sessionSwitches.toLogData()) + .toLogger(); + } - return this; -}; - -/******************************************************************************/ - -PageStore.prototype.reuse = function(context) { - // When force refreshing a page, the page store data needs to be reset. - - // If the hostname changes, we can't merely just update the context. - const tabContext = µb.tabContextManager.mustLookup(this.tabId); - if ( tabContext.rootHostname !== this.tabHostname ) { - context = ''; - } - - // If URL changes without a page reload (more and more common), then we - // need to keep all that we collected for reuse. In particular, not - // doing so was causing a problem in `videos.foxnews.com`: clicking a - // video thumbnail would not work, because the frame hierarchy structure - // was flushed from memory, while not really being flushed on the page. - if ( context === 'tabUpdated' ) { - // As part of https://github.com/chrisaljoudi/uBlock/issues/405 - // URL changed, force a re-evaluation of filtering switch - this.rawURL = tabContext.rawURL; return this; } - // A new page is completely reloaded from scratch, reset all. - if ( this.largeMediaTimer !== null ) { - clearTimeout(this.largeMediaTimer); - this.largeMediaTimer = null; + reuse(context) { + // When force refreshing a page, the page store data needs to be reset. + + // If the hostname changes, we can't merely just update the context. + const tabContext = µb.tabContextManager.mustLookup(this.tabId); + if ( tabContext.rootHostname !== this.tabHostname ) { + context = ''; + } + + // If URL changes without a page reload (more and more common), then + // we need to keep all that we collected for reuse. In particular, + // not doing so was causing a problem in `videos.foxnews.com`: + // clicking a video thumbnail would not work, because the frame + // hierarchy structure was flushed from memory, while not really being + // flushed on the page. + if ( context === 'tabUpdated' ) { + // As part of https://github.com/chrisaljoudi/uBlock/issues/405 + // URL changed, force a re-evaluation of filtering switch + this.rawURL = tabContext.rawURL; + return this; + } + + // A new page is completely reloaded from scratch, reset all. + if ( this.largeMediaTimer !== null ) { + clearTimeout(this.largeMediaTimer); + this.largeMediaTimer = null; + } + this.disposeFrameStores(); + this.netFilteringCache = this.netFilteringCache.dispose(); + this.init(this.tabId, context); + return this; } - this.disposeFrameStores(); - this.netFilteringCache = this.netFilteringCache.dispose(); - this.init(this.tabId, context); - return this; -}; -// https://www.youtube.com/watch?v=dltNSbOupgE - -/******************************************************************************/ - -PageStore.prototype.dispose = function() { - this.tabHostname = ''; - this.title = ''; - this.rawURL = ''; - this.hostnameToCountMap = null; - this.allowLargeMediaElementsUntil = 0; - if ( this.largeMediaTimer !== null ) { - clearTimeout(this.largeMediaTimer); - this.largeMediaTimer = null; + dispose() { + this.tabHostname = ''; + this.title = ''; + this.rawURL = ''; + this.hostnameToCountMap = null; + this.allowLargeMediaElementsUntil = 0; + if ( this.largeMediaTimer !== null ) { + clearTimeout(this.largeMediaTimer); + this.largeMediaTimer = null; + } + 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); + } + return null; } - this.disposeFrameStores(); - this.netFilteringCache = this.netFilteringCache.dispose(); - if ( this.journalTimer !== null ) { - clearTimeout(this.journalTimer); - this.journalTimer = null; + + disposeFrameStores() { + for ( const frameStore of this.frames.values() ) { + frameStore.dispose(); + } + this.frames.clear(); } - this.journal = []; - this.journalLastUncommittedURL = undefined; - if ( pageStoreJunkyard.length < pageStoreJunkyardMax ) { - pageStoreJunkyard.push(this); + + getFrame(frameId) { + return this.frames.get(frameId) || null; } - return null; -}; -/******************************************************************************/ - -PageStore.prototype.disposeFrameStores = function() { - for ( const frameStore of this.frames.values() ) { - frameStore.dispose(); + setFrame(frameId, frameURL) { + const frameStore = this.frames.get(frameId); + if ( frameStore !== undefined ) { + frameStore.init(frameURL); + } else { + this.frames.set(frameId, FrameStore.factory(frameURL)); + } } - this.frames.clear(); -}; -PageStore.prototype.getFrame = function(frameId) { - return this.frames.get(frameId) || null; -}; - -PageStore.prototype.setFrame = function(frameId, frameURL) { - const frameStore = this.frames.get(frameId); - if ( frameStore !== undefined ) { - frameStore.init(frameURL); - } else { - this.frames.set(frameId, FrameStore.factory(frameURL)); + getNetFilteringSwitch() { + return µb.tabContextManager + .mustLookup(this.tabId) + .getNetFilteringSwitch(); } -}; -/******************************************************************************/ + getSpecificCosmeticFilteringSwitch() { + return this.noCosmeticFiltering !== true; + } -PageStore.prototype.getNetFilteringSwitch = function() { - return µb.tabContextManager.mustLookup(this.tabId).getNetFilteringSwitch(); -}; + toggleNetFilteringSwitch(url, scope, state) { + µb.toggleNetFilteringSwitch(url, scope, state); + this.netFilteringCache.empty(); + } -PageStore.prototype.getSpecificCosmeticFilteringSwitch = function() { - return this.noCosmeticFiltering !== true; -}; + injectLargeMediaElementScriptlet() { + vAPI.tabs.injectScript(this.tabId, { + file: '/js/scriptlets/load-large-media-interactive.js', + allFrames: true, + runAt: 'document_idle', + }); + µb.contextMenu.update(this.tabId); + } -PageStore.prototype.toggleNetFilteringSwitch = function(url, scope, state) { - µb.toggleNetFilteringSwitch(url, scope, state); - this.netFilteringCache.empty(); -}; + temporarilyAllowLargeMediaElements(state) { + this.largeMediaCount = 0; + µb.contextMenu.update(this.tabId); + this.allowLargeMediaElementsUntil = state ? Date.now() + 86400000 : 0; + µb.scriptlets.injectDeep(this.tabId, 'load-large-media-all'); + } -/******************************************************************************/ + // 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. + journalAddRequest(hostname, result) { + if ( hostname === '' ) { return; } + this.journal.push( + hostname, + result === 1 ? 0x00000001 : 0x00010000 + ); + if ( this.journalTimer === null ) { + this.journalTimer = vAPI.setTimeout( + ( ) => { this.journalProcess(true); }, + µb.hiddenSettings.requestJournalProcessPeriod + ); + } + } -PageStore.prototype.injectLargeMediaElementScriptlet = function() { - vAPI.tabs.injectScript(this.tabId, { - file: '/js/scriptlets/load-large-media-interactive.js', - allFrames: true, - runAt: 'document_idle', - }); - µb.contextMenu.update(this.tabId); -}; - -PageStore.prototype.temporarilyAllowLargeMediaElements = function(state) { - this.largeMediaCount = 0; - µb.contextMenu.update(this.tabId); - this.allowLargeMediaElementsUntil = state ? Date.now() + 86400000 : 0; - µb.scriptlets.injectDeep(this.tabId, 'load-large-media-all'); -}; - -/******************************************************************************/ - -// 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 === 1 ? 0x00000001 : 0x00010000 - ); - if ( this.journalTimer === null ) { + journalAddRootFrame(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(true); }, µb.hiddenSettings.requestJournalProcessPeriod ); } -}; -PageStore.prototype.journalAddRootFrame = function(type, url) { - if ( type === 'committed' ) { - this.journalLastCommitted = this.journal.length; + journalProcess(fromTimer) { + if ( !fromTimer ) { + clearTimeout(this.journalTimer); + } + this.journalTimer = null; + + const journal = this.journal; + const now = Date.now(); + let aggregateCounts = 0; + let pivot = this.journalLastCommitted || 0; + + // Everything after pivot originates from current page. + for ( let i = pivot; i < journal.length; i += 2 ) { + const hostname = journal[i]; + let hostnameCounts = this.hostnameToCountMap.get(hostname); + if ( hostnameCounts === undefined ) { + hostnameCounts = 0; + this.contentLastModified = now; + } + let 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.updateToolbarIcon(this.tabId, 0x02); + } + + // Everything before pivot does not originate from current page -- we + // still need to bump global blocked/allowed counts. + for ( let 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; + } + + filterRequest(fctxt) { + fctxt.filter = undefined; + + if ( this.getNetFilteringSwitch(fctxt) === false ) { + return 0; + } + + const requestType = fctxt.type; + if ( - this.journalLastUncommitted !== undefined && - this.journalLastUncommitted < this.journalLastCommitted && - this.journalLastUncommittedURL === url + requestType === 'csp_report' && + this.filterCSPReport(fctxt) === 1 ) { - this.journalLastCommitted = this.journalLastUncommitted; - this.journalLastUncommitted = undefined; + return 1; } - } 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(true); }, - µb.hiddenSettings.requestJournalProcessPeriod - ); -}; -PageStore.prototype.journalProcess = function(fromTimer) { - if ( !fromTimer ) { - clearTimeout(this.journalTimer); - } - this.journalTimer = null; - - const journal = this.journal; - const now = Date.now(); - let aggregateCounts = 0; - let pivot = this.journalLastCommitted || 0; - - // Everything after pivot originates from current page. - for ( let i = pivot; i < journal.length; i += 2 ) { - const hostname = journal[i]; - let hostnameCounts = this.hostnameToCountMap.get(hostname); - if ( hostnameCounts === undefined ) { - hostnameCounts = 0; - this.contentLastModified = now; + if ( requestType.endsWith('font') && this.filterFont(fctxt) === 1 ) { + return 1; } - let 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.updateToolbarIcon(this.tabId, 0x02); + if ( + requestType === 'script' && + this.filterScripting(fctxt, true) === 1 + ) { + return 1; + } + + const cacheableResult = this.cacheableResults.has(requestType); + + if ( cacheableResult ) { + const entry = this.netFilteringCache.lookupResult(fctxt); + if ( entry !== undefined ) { + fctxt.filter = entry.logData; + return entry.result; + } + } + + // Dynamic URL filtering. + let result = µb.sessionURLFiltering.evaluateZ( + fctxt.getTabHostname(), + fctxt.url, + requestType + ); + if ( result !== 0 && µb.logger.enabled ) { + fctxt.filter = µb.sessionURLFiltering.toLogData(); + } + + // Dynamic hostname/type filtering. + if ( result === 0 && µb.userSettings.advancedUserEnabled ) { + result = µb.sessionFirewall.evaluateCellZY( + fctxt.getTabHostname(), + fctxt.getHostname(), + requestType + ); + if ( result !== 0 && result !== 3 && µb.logger.enabled ) { + fctxt.filter = µb.sessionFirewall.toLogData(); + } + } + + // Static filtering has lowest precedence. + if ( result === 0 || result === 3 ) { + result = µb.staticNetFilteringEngine.matchString(fctxt); + if ( result !== 0 && µb.logger.enabled ) { + fctxt.filter = µb.staticNetFilteringEngine.toLogData(); + } + } + + if ( cacheableResult ) { + this.netFilteringCache.rememberResult(fctxt, result); + } else if ( + result === 1 && + this.collapsibleResources.has(requestType) + ) { + this.netFilteringCache.rememberBlock(fctxt, true); + } + + return result; } - // Everything before pivot does not originate from current page -- we still - // need to bump global blocked/allowed counts. - for ( let 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(fctxt) { - fctxt.filter = undefined; - - if ( this.getNetFilteringSwitch() === false ) { + filterCSPReport(fctxt) { + if ( + µb.sessionSwitches.evaluateZ( + 'no-csp-reports', + fctxt.getHostname() + ) + ) { + if ( µb.logger.enabled ) { + fctxt.filter = µb.sessionSwitches.toLogData(); + } + return 1; + } return 0; } - const requestType = fctxt.type; + filterFont(fctxt) { + if ( fctxt.type === 'font' ) { + this.remoteFontCount += 1; + } + if ( + µb.sessionSwitches.evaluateZ( + 'no-remote-fonts', + fctxt.getTabHostname() + ) !== false + ) { + if ( µb.logger.enabled ) { + fctxt.filter = µb.sessionSwitches.toLogData(); + } + return 1; + } + return 0; + } - if ( requestType === 'csp_report' && this.filterCSPReport(fctxt) === 1 ) { + filterScripting(fctxt, netFiltering) { + fctxt.filter = undefined; + if ( netFiltering === undefined ) { + netFiltering = this.getNetFilteringSwitch(fctxt); + } + if ( + netFiltering === false || + µb.sessionSwitches.evaluateZ( + 'no-scripting', + fctxt.getTabHostname() + ) === false + ) { + return 0; + } + if ( µb.logger.enabled ) { + fctxt.filter = µb.sessionSwitches.toLogData(); + } return 1; } - if ( requestType.endsWith('font') && this.filterFont(fctxt) === 1 ) { + // The caller is responsible to check whether filtering is enabled or not. + filterLargeMediaElement(fctxt, size) { + fctxt.filter = undefined; + + if ( Date.now() < this.allowLargeMediaElementsUntil ) { + return 0; + } + if ( + µb.sessionSwitches.evaluateZ( + 'no-large-media', + fctxt.getTabHostname() + ) !== true + ) { + return 0; + } + if ( (size >>> 10) < µb.userSettings.largeMediaSize ) { + return 0; + } + + this.largeMediaCount += 1; + if ( this.largeMediaTimer === null ) { + this.largeMediaTimer = vAPI.setTimeout(( ) => { + this.largeMediaTimer = null; + this.injectLargeMediaElementScriptlet(); + }, 500); + } + + if ( µb.logger.enabled ) { + fctxt.filter = µb.sessionSwitches.toLogData(); + } + return 1; } - if ( - requestType === 'script' && - this.filterScripting(fctxt, true) === 1 - ) { - return 1; - } - - const cacheableResult = this.cacheableResults.has(requestType); - - if ( cacheableResult ) { - const entry = this.netFilteringCache.lookupResult(fctxt); - if ( entry !== undefined ) { - fctxt.filter = entry.logData; - return entry.result; + getBlockedResources(request, response) { + const normalURL = µb.normalizePageURL(this.tabId, request.frameURL); + const resources = request.resources; + const fctxt = µb.filteringContext; + fctxt.fromTabId(this.tabId) + .setDocOriginFromURL(normalURL); + // Force some resources to go through the filtering engine in order to + // populate the blocked-resources cache. This is required because for + // some resources it's not possible to detect whether they were blocked + // content script-side (i.e. `iframes` -- unlike `img`). + if ( Array.isArray(resources) && resources.length !== 0 ) { + for ( const resource of resources ) { + this.filterRequest( + fctxt.setType(resource.type) + .setURL(resource.url) + ); + } } + if ( this.netFilteringCache.hash === response.hash ) { return; } + response.hash = this.netFilteringCache.hash; + response.blockedResources = + this.netFilteringCache.lookupAllBlocked(fctxt.getDocHostname()); } - - // Dynamic URL filtering. - let result = µb.sessionURLFiltering.evaluateZ( - fctxt.getTabHostname(), - fctxt.url, - requestType - ); - if ( result !== 0 && µb.logger.enabled ) { - fctxt.filter = µb.sessionURLFiltering.toLogData(); - } - - // Dynamic hostname/type filtering. - if ( result === 0 && µb.userSettings.advancedUserEnabled ) { - result = µb.sessionFirewall.evaluateCellZY( - fctxt.getTabHostname(), - fctxt.getHostname(), - requestType - ); - if ( result !== 0 && result !== 3 && µb.logger.enabled ) { - fctxt.filter = µb.sessionFirewall.toLogData(); - } - } - - // Static filtering has lowest precedence. - if ( result === 0 || result === 3 ) { - result = µb.staticNetFilteringEngine.matchString(fctxt); - if ( result !== 0 && µb.logger.enabled ) { - fctxt.filter = µb.staticNetFilteringEngine.toLogData(); - } - } - - if ( cacheableResult ) { - this.netFilteringCache.rememberResult(fctxt, result); - } else if ( result === 1 && this.collapsibleResources.has(requestType) ) { - this.netFilteringCache.rememberBlock(fctxt, true); - } - - return result; }; PageStore.prototype.cacheableResults = new Set([ @@ -606,136 +700,11 @@ PageStore.prototype.collapsibleResources = new Set([ 'sub_frame', ]); -/******************************************************************************/ - -PageStore.prototype.filterCSPReport = function(fctxt) { - if ( - µb.sessionSwitches.evaluateZ( - 'no-csp-reports', - fctxt.getHostname() - ) - ) { - if ( µb.logger.enabled ) { - fctxt.filter = µb.sessionSwitches.toLogData(); - } - return 1; - } - return 0; -}; +µb.PageStore = PageStore; /******************************************************************************/ -PageStore.prototype.filterFont = function(fctxt) { - if ( fctxt.type === 'font' ) { - this.remoteFontCount += 1; - } - if ( - µb.sessionSwitches.evaluateZ( - 'no-remote-fonts', - fctxt.getTabHostname() - ) !== false - ) { - if ( µb.logger.enabled ) { - fctxt.filter = µb.sessionSwitches.toLogData(); - } - return 1; - } - return 0; -}; +// <<<<< +// end of private namespace -/******************************************************************************/ - -PageStore.prototype.filterScripting = function(fctxt, netFiltering) { - fctxt.filter = undefined; - if ( netFiltering === undefined ) { - netFiltering = this.getNetFilteringSwitch(); - } - if ( - netFiltering === false || - µb.sessionSwitches.evaluateZ( - 'no-scripting', - fctxt.getTabHostname() - ) === false - ) { - return 0; - } - if ( µb.logger.enabled ) { - fctxt.filter = µb.sessionSwitches.toLogData(); - } - return 1; -}; - -/******************************************************************************/ - -// The caller is responsible to check whether filtering is enabled or not. - -PageStore.prototype.filterLargeMediaElement = function(fctxt, size) { - fctxt.filter = undefined; - - if ( Date.now() < this.allowLargeMediaElementsUntil ) { - return 0; - } - if ( - µb.sessionSwitches.evaluateZ( - 'no-large-media', - fctxt.getTabHostname() - ) !== true - ) { - return 0; - } - if ( (size >>> 10) < µb.userSettings.largeMediaSize ) { - return 0; - } - - this.largeMediaCount += 1; - if ( this.largeMediaTimer === null ) { - this.largeMediaTimer = vAPI.setTimeout(( ) => { - this.largeMediaTimer = null; - this.injectLargeMediaElementScriptlet(); - }, 500); - } - - if ( µb.logger.enabled ) { - fctxt.filter = µb.sessionSwitches.toLogData(); - } - - return 1; -}; - -// https://www.youtube.com/watch?v=drW8p_dTLD4 - -/******************************************************************************/ - -PageStore.prototype.getBlockedResources = function(request, response) { - const normalURL = µb.normalizePageURL(this.tabId, request.frameURL); - const resources = request.resources; - const fctxt = µBlock.filteringContext; - fctxt.fromTabId(this.tabId) - .setDocOriginFromURL(normalURL); - // Force some resources to go through the filtering engine in order to - // populate the blocked-resources cache. This is required because for - // some resources it's not possible to detect whether they were blocked - // content script-side (i.e. `iframes` -- unlike `img`). - if ( Array.isArray(resources) && resources.length !== 0 ) { - for ( const resource of resources ) { - this.filterRequest( - fctxt.setType(resource.type) - .setURL(resource.url) - ); - } - } - if ( this.netFilteringCache.hash === response.hash ) { return; } - response.hash = this.netFilteringCache.hash; - response.blockedResources = - this.netFilteringCache.lookupAllBlocked(fctxt.getDocHostname()); -}; - -/******************************************************************************/ - -return { - factory: PageStore.factory -}; - -})(); - -/******************************************************************************/ +} diff --git a/src/js/tab.js b/src/js/tab.js index 648309e15..50504f47a 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -19,21 +19,12 @@ Home: https://github.com/gorhill/uBlock */ -// https://github.com/gorhill/uBlock/issues/2720 - -/******************************************************************************/ -/******************************************************************************/ - -(function() { - 'use strict'; /******************************************************************************/ - -var µb = µBlock; +/******************************************************************************/ // https://github.com/gorhill/httpswitchboard/issues/303 -// 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 µBlock into being able to process an // otherwise unmanageable scheme. µBlock needs web page to have a proper @@ -42,10 +33,7 @@ var µb = µBlock; // hostname. This way, for a specific scheme you can create scope with // rules which will apply only to that scheme. -/******************************************************************************/ -/******************************************************************************/ - -µb.normalizePageURL = function(tabId, pageURL) { +µBlock.normalizePageURL = function(tabId, pageURL) { if ( tabId < 0 ) { return 'http://behind-the-scene/'; } @@ -63,7 +51,7 @@ var µb = µBlock; fakeHostname = uri.path + '.' + fakeHostname; } - return 'http://' + fakeHostname + '/'; + return `http://${fakeHostname}/`; }; /******************************************************************************/ @@ -127,48 +115,54 @@ housekeep itself. */ -µb.tabContextManager = (function() { - var tabContexts = new Map(); +µBlock.tabContextManager = (( ) => { + const µb = µBlock; + const tabContexts = new Map(); // https://github.com/chrisaljoudi/uBlock/issues/1001 // This is to be used as last-resort fallback in case a tab is found to not // be bound while network requests are fired for the tab. - var mostRecentRootDocURL = ''; - var mostRecentRootDocURLTimestamp = 0; + let mostRecentRootDocURL = ''; + let mostRecentRootDocURLTimestamp = 0; - var popupCandidates = new Map(); + const popupCandidates = new Map(); - var PopupCandidate = function(targetTabId, openerTabId) { - this.targetTabId = targetTabId; - this.opener = { - tabId: openerTabId, - popunder: false, - trustedURL: openerTabId === µb.mouseEventRegister.tabId ? - µb.mouseEventRegister.url : - '' - }; - this.selfDestructionTimer = null; - this.launchSelfDestruction(); - }; - - PopupCandidate.prototype.destroy = function() { - if ( this.selfDestructionTimer !== null ) { - clearTimeout(this.selfDestructionTimer); + const PopupCandidate = class { + constructor(targetTabId, openerTabId) { + this.targetTabId = targetTabId; + this.opener = { + tabId: openerTabId, + popunder: false, + trustedURL: openerTabId === µb.mouseEventRegister.tabId + ? µb.mouseEventRegister.url + : '' + }; + this.selfDestructionTimer = null; + this.launchSelfDestruction(); } - popupCandidates.delete(this.targetTabId); - }; - PopupCandidate.prototype.launchSelfDestruction = function() { - if ( this.selfDestructionTimer !== null ) { - clearTimeout(this.selfDestructionTimer); + destroy() { + if ( this.selfDestructionTimer !== null ) { + clearTimeout(this.selfDestructionTimer); + } + popupCandidates.delete(this.targetTabId); + } + + launchSelfDestruction() { + if ( this.selfDestructionTimer !== null ) { + clearTimeout(this.selfDestructionTimer); + } + this.selfDestructionTimer = vAPI.setTimeout( + ( ) => this.destroy(), + 10000 + ); } - this.selfDestructionTimer = vAPI.setTimeout(this.destroy.bind(this), 10000); }; - var popupCandidateTest = function(targetTabId) { - for ( var entry of popupCandidates ) { - var tabId = entry[0]; - var candidate = entry[1]; + const popupCandidateTest = function(targetTabId) { + for ( const entry of popupCandidates ) { + const tabId = entry[0]; + const candidate = entry[1]; if ( targetTabId !== tabId && targetTabId !== candidate.opener.tabId @@ -190,7 +184,7 @@ housekeep itself. }; vAPI.tabs.onPopupCreated = function(targetTabId, openerTabId) { - var popup = popupCandidates.get(targetTabId); + const popup = popupCandidates.get(targetTabId); if ( popup === undefined ) { popupCandidates.set( targetTabId, @@ -200,17 +194,17 @@ housekeep itself. popupCandidateTest(targetTabId); }; - var gcPeriod = 10 * 60 * 1000; + const gcPeriod = 10 * 60 * 1000; // A pushed entry is removed from the stack unless it is committed with // a set time. - var StackEntry = function(url, commit) { + const StackEntry = function(url, commit) { this.url = url; this.committed = commit; this.tstamp = Date.now(); }; - var TabContext = function(tabId) { + const TabContext = function(tabId) { this.tabId = tabId; this.stack = []; this.rawURL = @@ -238,16 +232,14 @@ housekeep itself. TabContext.prototype.onTab = function(tab) { if ( tab ) { - this.gcTimer = vAPI.setTimeout(this.onGC.bind(this), gcPeriod); + this.gcTimer = vAPI.setTimeout(( ) => this.onGC(), gcPeriod); } else { this.destroy(); } }; TabContext.prototype.onGC = function() { - if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { - return; - } + if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { return; } // https://github.com/gorhill/uBlock/issues/1713 // For unknown reasons, Firefox's setTimeout() will sometimes // causes the callback function to be called immediately, bypassing @@ -258,7 +250,7 @@ housekeep itself. } this.onGCBarrier = true; this.gcTimer = null; - vAPI.tabs.get(this.tabId, this.onTab.bind(this)); + vAPI.tabs.get(this.tabId, tab => { this.onTab(tab); }); this.onGCBarrier = false; }; @@ -271,11 +263,9 @@ housekeep itself. } this.commitTimer = null; // Remove uncommitted entries at the top of the stack. - var i = this.stack.length; + let i = this.stack.length; while ( i-- ) { - if ( this.stack[i].committed ) { - break; - } + if ( this.stack[i].committed ) { break; } } // https://github.com/gorhill/uBlock/issues/300 // If no committed entry was found, fall back on the bottom-most one @@ -295,10 +285,8 @@ housekeep itself. // contexts, as the behind-the-scene context is permanent -- so we do not // want to flush it. TabContext.prototype.autodestroy = function() { - if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { - return; - } - this.gcTimer = vAPI.setTimeout(this.onGC.bind(this), gcPeriod); + if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { return; } + this.gcTimer = vAPI.setTimeout(( ) => this.onGC(), gcPeriod); }; // Update just force all properties to be updated to match the most recent @@ -313,7 +301,7 @@ housekeep itself. this.rootDomain = ''; return; } - var stackEntry = this.stack[this.stack.length - 1]; + const stackEntry = this.stack[this.stack.length - 1]; this.rawURL = stackEntry.url; this.normalURL = µb.normalizePageURL(this.tabId, this.rawURL); this.origin = µb.URI.originFromURI(this.normalURL); @@ -328,7 +316,7 @@ housekeep itself. if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { return; } - var count = this.stack.length; + const count = this.stack.length; if ( count !== 0 && this.stack[count - 1].url === url ) { return; } @@ -338,7 +326,7 @@ housekeep itself. if ( this.commitTimer !== null ) { clearTimeout(this.commitTimer); } - this.commitTimer = vAPI.setTimeout(this.onCommit.bind(this), 500); + this.commitTimer = vAPI.setTimeout(( ) => this.onCommit(), 500); }; // This tells that the url is definitely the one to be associated with the @@ -347,10 +335,8 @@ housekeep itself. TabContext.prototype.commit = function(url) { if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { return; } if ( this.stack.length !== 0 ) { - var top = this.stack[this.stack.length - 1]; - if ( top.url === url && top.committed ) { - return false; - } + const top = this.stack[this.stack.length - 1]; + if ( top.url === url && top.committed ) { return false; } } this.stack = [new StackEntry(url, true)]; this.update(); @@ -364,7 +350,11 @@ housekeep itself. // https://github.com/chrisaljoudi/uBlock/issues/1078 // Use both the raw and normalized URLs. this.netFiltering = µb.getNetFilteringSwitch(this.normalURL); - if ( this.netFiltering && this.rawURL !== this.normalURL && this.rawURL !== '' ) { + if ( + this.netFiltering && + this.rawURL !== this.normalURL && + this.rawURL !== '' + ) { this.netFiltering = µb.getNetFilteringSwitch(this.rawURL); } this.netFilteringReadTime = Date.now(); @@ -373,8 +363,8 @@ housekeep itself. // These are to be used for the API of the tab context manager. - var push = function(tabId, url) { - var entry = tabContexts.get(tabId); + const push = function(tabId, url) { + let entry = tabContexts.get(tabId); if ( entry === undefined ) { entry = new TabContext(tabId); entry.autodestroy(); @@ -386,14 +376,14 @@ housekeep itself. }; // Find a tab context for a specific tab. - var lookup = function(tabId) { + const lookup = function(tabId) { return tabContexts.get(tabId) || null; }; // Find a tab context for a specific tab. If none is found, attempt to // fix this. When all fail, the behind-the-scene context is returned. - var mustLookup = function(tabId) { - var entry = tabContexts.get(tabId); + const mustLookup = function(tabId) { + const entry = tabContexts.get(tabId); if ( entry !== undefined ) { return entry; } @@ -401,7 +391,10 @@ housekeep itself. // Google Hangout popup opens without a root frame. So for now we will // just discard that best-guess root frame if it is too far in the // future, at which point it ceases to be a "best guess". - if ( mostRecentRootDocURL !== '' && mostRecentRootDocURLTimestamp + 500 < Date.now() ) { + if ( + mostRecentRootDocURL !== '' && + mostRecentRootDocURLTimestamp + 500 < Date.now() + ) { mostRecentRootDocURL = ''; } // https://github.com/chrisaljoudi/uBlock/issues/1001 @@ -423,8 +416,8 @@ housekeep itself. // https://github.com/gorhill/uBlock/issues/1735 // Filter for popups if actually committing. - var commit = function(tabId, url) { - var entry = tabContexts.get(tabId); + const commit = function(tabId, url) { + let entry = tabContexts.get(tabId); if ( entry === undefined ) { entry = push(tabId, url); } else if ( entry.commit(url) ) { @@ -433,12 +426,12 @@ housekeep itself. return entry; }; - var exists = function(tabId) { + const exists = function(tabId) { return tabContexts.get(tabId) !== undefined; }; // Behind-the-scene tab context - (function() { + { const entry = new TabContext(vAPI.noTabId); entry.stack.push(new StackEntry('', true)); entry.rawURL = ''; @@ -446,30 +439,32 @@ housekeep itself. entry.origin = µb.URI.originFromURI(entry.normalURL); entry.rootHostname = µb.URI.hostnameFromURI(entry.origin); entry.rootDomain = µb.URI.domainFromHostname(entry.rootHostname); - })(); + } // Context object, typically to be used to feed filtering engines. - var contextJunkyard = []; - var Context = function(tabId) { - this.init(tabId); - }; - Context.prototype.init = function(tabId) { - var tabContext = lookup(tabId); - this.rootHostname = tabContext.rootHostname; - this.rootDomain = tabContext.rootDomain; - this.pageHostname = - this.pageDomain = - this.requestURL = - this.origin = - this.requestHostname = - this.requestDomain = ''; - return this; - }; - Context.prototype.dispose = function() { - contextJunkyard.push(this); + const contextJunkyard = []; + const Context = class { + constructor(tabId) { + this.init(tabId); + } + init(tabId) { + const tabContext = lookup(tabId); + this.rootHostname = tabContext.rootHostname; + this.rootDomain = tabContext.rootDomain; + this.pageHostname = + this.pageDomain = + this.requestURL = + this.origin = + this.requestHostname = + this.requestDomain = ''; + return this; + } + dispose() { + contextJunkyard.push(this); + } }; - var createContext = function(tabId) { + const createContext = function(tabId) { if ( contextJunkyard.length ) { return contextJunkyard.pop().init(tabId); } @@ -493,6 +488,7 @@ housekeep itself. // content has changed. vAPI.tabs.onNavigation = function(details) { + const µb = µBlock; if ( details.frameId === 0 ) { µb.tabContextManager.commit(details.tabId, details.url); let pageStore = µb.bindTabToPageStats(details.tabId, 'tabCommitted'); @@ -517,16 +513,16 @@ vAPI.tabs.onNavigation = function(details) { vAPI.tabs.onUpdated = function(tabId, changeInfo, tab) { if ( !tab.url || tab.url === '' ) { return; } if ( !changeInfo.url ) { return; } - µb.tabContextManager.commit(tabId, changeInfo.url); - µb.bindTabToPageStats(tabId, 'tabUpdated'); + µBlock.tabContextManager.commit(tabId, changeInfo.url); + µBlock.bindTabToPageStats(tabId, 'tabUpdated'); }; /******************************************************************************/ vAPI.tabs.onClosed = function(tabId) { if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } - µb.unbindTabFromPageStats(tabId); - µb.contextMenu.update(); + µBlock.unbindTabFromPageStats(tabId); + µBlock.contextMenu.update(); }; /******************************************************************************/ @@ -553,7 +549,8 @@ vAPI.tabs.onClosed = function(tabId) { // c: close opener // d: close target -vAPI.tabs.onPopupUpdated = (function() { +vAPI.tabs.onPopupUpdated = (( ) => { + const µb = µBlock; // The same context object will be reused everytime. This also allows to // remember whether a popup or popunder was matched. const fctxt = µBlock.filteringContext.setFilter(undefined); @@ -839,17 +836,17 @@ vAPI.tabs.registerListeners(); // Create an entry for the tab if it doesn't exist. -µb.bindTabToPageStats = function(tabId, context) { +µBlock.bindTabToPageStats = function(tabId, context) { this.updateToolbarIcon(tabId, 0x03); // Do not create a page store for URLs which are of no interests - if ( µb.tabContextManager.exists(tabId) === false ) { + if ( this.tabContextManager.exists(tabId) === false ) { this.unbindTabFromPageStats(tabId); return null; } // Reuse page store if one exists: this allows to guess if a tab is a popup - var pageStore = this.pageStores.get(tabId); + let pageStore = this.pageStores.get(tabId); // Tab is not bound if ( pageStore === undefined ) { @@ -885,45 +882,59 @@ vAPI.tabs.registerListeners(); /******************************************************************************/ -µb.unbindTabFromPageStats = function(tabId) { +µBlock.unbindTabFromPageStats = function(tabId) { //console.debug('µBlock> unbindTabFromPageStats(%d)', tabId); - var pageStore = this.pageStores.get(tabId); - if ( pageStore !== undefined ) { - pageStore.dispose(); - this.pageStores.delete(tabId); - this.pageStoresToken = Date.now(); - } + const pageStore = this.pageStores.get(tabId); + if ( pageStore === undefined ) { return; } + pageStore.dispose(); + this.pageStores.delete(tabId); + this.pageStoresToken = Date.now(); }; /******************************************************************************/ -µb.pageStoreFromTabId = function(tabId) { +µBlock.pageStoreFromTabId = function(tabId) { return this.pageStores.get(tabId) || null; }; -µb.mustPageStoreFromTabId = function(tabId) { +µBlock.mustPageStoreFromTabId = function(tabId) { return this.pageStores.get(tabId) || this.pageStores.get(vAPI.noTabId); }; /******************************************************************************/ // Permanent page store for behind-the-scene requests. Must never be removed. +// +// https://github.com/uBlockOrigin/uBlock-issues/issues/651 +// The whitelist status of the tabless page store will be determined by +// the document context (if present) of the network request. -(function() { - const pageStore = µb.PageStore.factory(vAPI.noTabId); - µb.pageStores.set(pageStore.tabId, pageStore); +{ + const NoPageStore = class extends µBlock.PageStore { + getNetFilteringSwitch(fctxt) { + if ( fctxt.docId === 0 ) { + const docOrigin = fctxt.getDocOrigin(); + if ( docOrigin ) { + return µBlock.getNetFilteringSwitch(docOrigin); + } + } + return super.getNetFilteringSwitch(); + } + }; + const pageStore = new NoPageStore(vAPI.noTabId); + µBlock.pageStores.set(pageStore.tabId, pageStore); pageStore.title = vAPI.i18n('logBehindTheScene'); -})(); +} /******************************************************************************/ // Update visual of extension icon. -µb.updateToolbarIcon = (function() { - let tabIdToDetails = new Map(); +µBlock.updateToolbarIcon = (( ) => { + const tabIdToDetails = new Map(); - let updateBadge = function(tabId) { - let parts = tabIdToDetails.get(tabId); + const updateBadge = function(tabId) { + const parts = tabIdToDetails.get(tabId); tabIdToDetails.delete(tabId); let state = 0; @@ -963,24 +974,24 @@ vAPI.tabs.registerListeners(); /******************************************************************************/ -µb.updateTitle = (function() { - var tabIdToTimer = new Map(); - var delay = 499; +µBlock.updateTitle = (( ) => { + const tabIdToTimer = new Map(); + const delay = 499; - var tryAgain = function(entry) { + const tryAgain = function(entry) { if ( entry.count === 1 ) { return false; } entry.count -= 1; tabIdToTimer.set( entry.tabId, - vAPI.setTimeout(updateTitle.bind(null, entry), delay) + vAPI.setTimeout(( ) => { updateTitle(entry); }, delay) ); return true; }; - var onTabReady = function(entry, tab) { + const onTabReady = function(entry, tab) { if ( !tab ) { return; } - var µb = µBlock; - var pageStore = µb.pageStoreFromTabId(entry.tabId); + const µb = µBlock; + const pageStore = µb.pageStoreFromTabId(entry.tabId); if ( pageStore === null ) { return; } // Firefox needs this: if you detach a tab, the new tab won't have // its rawURL set. Concretely, this causes the logger to report an @@ -991,21 +1002,21 @@ vAPI.tabs.registerListeners(); if ( !tab.title && tryAgain(entry) ) { return; } // https://github.com/gorhill/uMatrix/issues/225 // Sometimes title changes while page is loading. - var settled = tab.title && tab.title === pageStore.title; + const settled = tab.title && tab.title === pageStore.title; pageStore.title = tab.title || tab.url || ''; if ( !settled ) { tryAgain(entry); } }; - var updateTitle = function(entry) { + const updateTitle = function(entry) { tabIdToTimer.delete(entry.tabId); vAPI.tabs.get(entry.tabId, onTabReady.bind(null, entry)); }; return function(tabId) { if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } - var timer = tabIdToTimer.get(tabId); + const timer = tabIdToTimer.get(tabId); if ( timer !== undefined ) { clearTimeout(timer); } @@ -1021,41 +1032,40 @@ vAPI.tabs.registerListeners(); /******************************************************************************/ -// Stale page store entries janitor // https://github.com/chrisaljoudi/uBlock/issues/455 +// Stale page store entries janitor -var pageStoreJanitorPeriod = 15 * 60 * 1000; -var pageStoreJanitorSampleAt = 0; -var pageStoreJanitorSampleSize = 10; +{ + const pageStoreJanitorPeriod = 15 * 60 * 1000; + let pageStoreJanitorSampleAt = 0; + let pageStoreJanitorSampleSize = 10; -var pageStoreJanitor = function() { - var vapiTabs = vAPI.tabs; - var tabIds = Array.from(µb.pageStores.keys()).sort(); - var checkTab = function(tabId) { - vapiTabs.get(tabId, function(tab) { - if ( !tab ) { - µb.unbindTabFromPageStats(tabId); - } - }); + const pageStoreJanitor = function() { + const tabIds = Array.from(µBlock.pageStores.keys()).sort(); + const checkTab = tabId => { + vAPI.tabs.get(tabId, tab => { + if ( tab ) { return; } + µBlock.unbindTabFromPageStats(tabId); + }); + }; + if ( pageStoreJanitorSampleAt >= tabIds.length ) { + pageStoreJanitorSampleAt = 0; + } + const n = Math.min( + pageStoreJanitorSampleAt + pageStoreJanitorSampleSize, + tabIds.length + ); + for ( let i = pageStoreJanitorSampleAt; i < n; i++ ) { + const tabId = tabIds[i]; + if ( vAPI.isBehindTheSceneTabId(tabId) ) { continue; } + checkTab(tabId); + } + pageStoreJanitorSampleAt = n; + + vAPI.setTimeout(pageStoreJanitor, pageStoreJanitorPeriod); }; - if ( pageStoreJanitorSampleAt >= tabIds.length ) { - pageStoreJanitorSampleAt = 0; - } - var n = Math.min(pageStoreJanitorSampleAt + pageStoreJanitorSampleSize, tabIds.length); - for ( var i = pageStoreJanitorSampleAt; i < n; i++ ) { - var tabId = tabIds[i]; - if ( vAPI.isBehindTheSceneTabId(tabId) ) { continue; } - checkTab(tabId); - } - pageStoreJanitorSampleAt = n; vAPI.setTimeout(pageStoreJanitor, pageStoreJanitorPeriod); -}; - -vAPI.setTimeout(pageStoreJanitor, pageStoreJanitorPeriod); - -/******************************************************************************/ - -})(); +} /******************************************************************************/