From aed850978e08e0dd084227078fae4110c41319ab Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 19 Jul 2020 17:41:13 -0400 Subject: [PATCH] Mind discarded status of tabs when internally handling them Related discussion: - https://bugzilla.mozilla.org/show_bug.cgi?id=1652925#c19 Content scripts should not be injected in discarded tabs, and a discarded tab should treated as if it does not exist. --- src/js/start.js | 45 +++++++++++++---------- src/js/tab.js | 94 ++++++++++++++++++++++--------------------------- 2 files changed, 68 insertions(+), 71 deletions(-) diff --git a/src/js/start.js b/src/js/start.js index 19e3184ec..5acc14cdf 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -52,38 +52,45 @@ vAPI.app.onShutdown = function() { // the extension was launched. It can be used to inject content scripts // in already opened web pages, to remove whatever nuisance could make it to // the web pages before uBlock was ready. +// +// https://bugzilla.mozilla.org/show_bug.cgi?id=1652925#c19 +// Mind discarded tabs. const initializeTabs = async function() { const manifest = browser.runtime.getManifest(); if ( manifest instanceof Object === false ) { return; } - const tabs = await vAPI.tabs.query({ url: '' }); const toCheck = []; - const checker = { - file: 'js/scriptlets/should-inject-contentscript.js' - }; - for ( const tab of tabs ) { - µb.tabContextManager.commit(tab.id, tab.url); - µb.bindTabToPageStats(tab.id); - // https://github.com/chrisaljoudi/uBlock/issues/129 - // Find out whether content scripts need to be injected - // programmatically. This may be necessary for web pages which - // were loaded before uBO launched. - toCheck.push( - /^https?:\/\//.test(tab.url) - ? vAPI.tabs.executeScript(tab.id, checker) - : false - ); + const tabIds = []; + { + const checker = { file: 'js/scriptlets/should-inject-contentscript.js' }; + const tabs = await vAPI.tabs.query({ url: '' }); + for ( const tab of tabs ) { + if ( tab.discarded === true ) { continue; } + const { id, url } = tab; + µb.tabContextManager.commit(id, url); + µb.bindTabToPageStats(id); + // https://github.com/chrisaljoudi/uBlock/issues/129 + // Find out whether content scripts need to be injected + // programmatically. This may be necessary for web pages which + // were loaded before uBO launched. + toCheck.push( + /^https?:\/\//.test(url) + ? vAPI.tabs.executeScript(id, checker) + .then(result => result, ( ) => false) + : false + ); + tabIds.push(id); + } } const results = await Promise.all(toCheck); for ( let i = 0; i < results.length; i++ ) { const result = results[i]; if ( result.length === 0 || result[0] !== true ) { continue; } - // Inject dclarative content scripts programmatically. - const tabId = tabs[i].id; + // Inject declarative content scripts programmatically. for ( const contentScript of manifest.content_scripts ) { for ( const file of contentScript.js ) { - vAPI.tabs.executeScript(tabId, { + vAPI.tabs.executeScript(tabIds[i], { file: file, allFrames: contentScript.all_frames, runAt: contentScript.run_at diff --git a/src/js/tab.js b/src/js/tab.js index ae096faeb..2e978163d 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -577,26 +577,22 @@ housekeep itself. tabContexts.delete(this.tabId); }; - TabContext.prototype.onTab = function(tab) { - if ( tab ) { - this.gcTimer = vAPI.setTimeout(( ) => this.onGC(), gcPeriod); - } else { - this.destroy(); - } - }; - TabContext.prototype.onGC = async function() { 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 - // the main event loop. For now this should prevent uBO from crashing - // as a result of the bad setTimeout() behavior. + // For unknown reasons, Firefox's setTimeout() will sometimes + // causes the callback function to be called immediately, bypassing + // the main event loop. For now this should prevent uBO from + // crashing as a result of the bad setTimeout() behavior. if ( this.onGCBarrier ) { return; } this.onGCBarrier = true; this.gcTimer = null; const tab = await vAPI.tabs.get(this.tabId); - this.onTab(tab); + if ( tab instanceof Object === false || tab.discarded === true ) { + this.destroy(); + } else { + this.gcTimer = vAPI.setTimeout(( ) => this.onGC(), gcPeriod); + } this.onGCBarrier = false; }; @@ -1074,23 +1070,19 @@ vAPI.tabs = new vAPI.Tabs(); /******************************************************************************/ µBlock.updateTitle = (( ) => { - const tabIdToTimer = new Map(); + const tabIdToCount = new Map(); const delay = 499; - const tryAgain = function(entry) { - if ( entry.count === 1 ) { return false; } - entry.count -= 1; - tabIdToTimer.set( - entry.tabId, - vAPI.setTimeout(( ) => { updateTitle(entry); }, delay) - ); - return true; - }; - - const onTabReady = function(entry, tab) { - if ( !tab ) { return; } + const updateTitle = async function(tabId) { + let count = tabIdToCount.get(tabId); + if ( count === undefined ) { return; } + tabIdToCount.delete(tabId); + const tab = await vAPI.tabs.get(tabId); + if ( tab instanceof Object === false || tab.discarded === true ) { + return; + } const µb = µBlock; - const pageStore = µb.pageStoreFromTabId(entry.tabId); + const pageStore = µb.pageStoreFromTabId(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 @@ -1098,35 +1090,32 @@ vAPI.tabs = new vAPI.Tabs(); // TODO: Investigate for a fix vAPI-side. pageStore.rawURL = tab.url; µb.pageStoresToken = Date.now(); - if ( !tab.title && tryAgain(entry) ) { return; } // https://github.com/gorhill/uMatrix/issues/225 - // Sometimes title changes while page is loading. - const settled = tab.title && tab.title === pageStore.title; + // Sometimes title changes while page is loading. + const settled = + typeof tab.title === 'string' && + tab.title !== '' && + tab.title === pageStore.title; pageStore.title = tab.title || tab.url || ''; - if ( !settled ) { - tryAgain(entry); - } + if ( settled ) { return; } + if ( tabIdToCount.has(tabId) ) { return; } + count -= 1; + if ( count === 0 ) { return; } + tabIdToCount.set(tabId, count); + updateTitleAsync(tabId); }; - const updateTitle = async function(entry) { - tabIdToTimer.delete(entry.tabId); - const tab = await vAPI.tabs.get(entry.tabId); - onTabReady(entry, tab); + const updateTitleAsync = function(tabId) { + vAPI.setTimeout(( ) => { updateTitle(tabId); }, delay); }; return function(tabId) { if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } - const timer = tabIdToTimer.get(tabId); - if ( timer !== undefined ) { - clearTimeout(timer); + const count = tabIdToCount.get(tabId); + tabIdToCount.set(tabId, 5); + if ( count === undefined ) { + updateTitleAsync(tabId); } - tabIdToTimer.set( - tabId, - vAPI.setTimeout( - updateTitle.bind(null, { tabId: tabId, count: 5 }), - delay - ) - ); }; })(); @@ -1140,13 +1129,14 @@ vAPI.tabs = new vAPI.Tabs(); let pageStoreJanitorSampleAt = 0; let pageStoreJanitorSampleSize = 10; + const checkTab = async tabId => { + const tab = await vAPI.tabs.get(tabId); + if ( tab instanceof Object && tab.discarded !== true ) { return; } + µBlock.unbindTabFromPageStats(tabId); + }; + const pageStoreJanitor = function() { const tabIds = Array.from(µBlock.pageStores.keys()).sort(); - const checkTab = async tabId => { - const tab = await vAPI.tabs.get(tabId); - if ( tab ) { return; } - µBlock.unbindTabFromPageStats(tabId); - }; if ( pageStoreJanitorSampleAt >= tabIds.length ) { pageStoreJanitorSampleAt = 0; }