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.
This commit is contained in:
Raymond Hill 2020-07-19 17:41:13 -04:00
parent 3b46b2532d
commit aed850978e
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
2 changed files with 68 additions and 71 deletions

View File

@ -52,38 +52,45 @@ vAPI.app.onShutdown = function() {
// the extension was launched. It can be used to inject content scripts // 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 // in already opened web pages, to remove whatever nuisance could make it to
// the web pages before uBlock was ready. // 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 initializeTabs = async function() {
const manifest = browser.runtime.getManifest(); const manifest = browser.runtime.getManifest();
if ( manifest instanceof Object === false ) { return; } if ( manifest instanceof Object === false ) { return; }
const tabs = await vAPI.tabs.query({ url: '<all_urls>' });
const toCheck = []; const toCheck = [];
const checker = { const tabIds = [];
file: 'js/scriptlets/should-inject-contentscript.js' {
}; const checker = { file: 'js/scriptlets/should-inject-contentscript.js' };
const tabs = await vAPI.tabs.query({ url: '<all_urls>' });
for ( const tab of tabs ) { for ( const tab of tabs ) {
µb.tabContextManager.commit(tab.id, tab.url); if ( tab.discarded === true ) { continue; }
µb.bindTabToPageStats(tab.id); const { id, url } = tab;
µb.tabContextManager.commit(id, url);
µb.bindTabToPageStats(id);
// https://github.com/chrisaljoudi/uBlock/issues/129 // https://github.com/chrisaljoudi/uBlock/issues/129
// Find out whether content scripts need to be injected // Find out whether content scripts need to be injected
// programmatically. This may be necessary for web pages which // programmatically. This may be necessary for web pages which
// were loaded before uBO launched. // were loaded before uBO launched.
toCheck.push( toCheck.push(
/^https?:\/\//.test(tab.url) /^https?:\/\//.test(url)
? vAPI.tabs.executeScript(tab.id, checker) ? vAPI.tabs.executeScript(id, checker)
.then(result => result, ( ) => false)
: false : false
); );
tabIds.push(id);
}
} }
const results = await Promise.all(toCheck); const results = await Promise.all(toCheck);
for ( let i = 0; i < results.length; i++ ) { for ( let i = 0; i < results.length; i++ ) {
const result = results[i]; const result = results[i];
if ( result.length === 0 || result[0] !== true ) { continue; } if ( result.length === 0 || result[0] !== true ) { continue; }
// Inject dclarative content scripts programmatically. // Inject declarative content scripts programmatically.
const tabId = tabs[i].id;
for ( const contentScript of manifest.content_scripts ) { for ( const contentScript of manifest.content_scripts ) {
for ( const file of contentScript.js ) { for ( const file of contentScript.js ) {
vAPI.tabs.executeScript(tabId, { vAPI.tabs.executeScript(tabIds[i], {
file: file, file: file,
allFrames: contentScript.all_frames, allFrames: contentScript.all_frames,
runAt: contentScript.run_at runAt: contentScript.run_at

View File

@ -577,26 +577,22 @@ housekeep itself.
tabContexts.delete(this.tabId); 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() { TabContext.prototype.onGC = async function() {
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { return; } if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { return; }
// https://github.com/gorhill/uBlock/issues/1713 // https://github.com/gorhill/uBlock/issues/1713
// For unknown reasons, Firefox's setTimeout() will sometimes // For unknown reasons, Firefox's setTimeout() will sometimes
// causes the callback function to be called immediately, bypassing // causes the callback function to be called immediately, bypassing
// the main event loop. For now this should prevent uBO from crashing // the main event loop. For now this should prevent uBO from
// as a result of the bad setTimeout() behavior. // crashing as a result of the bad setTimeout() behavior.
if ( this.onGCBarrier ) { return; } if ( this.onGCBarrier ) { return; }
this.onGCBarrier = true; this.onGCBarrier = true;
this.gcTimer = null; this.gcTimer = null;
const tab = await vAPI.tabs.get(this.tabId); 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; this.onGCBarrier = false;
}; };
@ -1074,23 +1070,19 @@ vAPI.tabs = new vAPI.Tabs();
/******************************************************************************/ /******************************************************************************/
µBlock.updateTitle = (( ) => { µBlock.updateTitle = (( ) => {
const tabIdToTimer = new Map(); const tabIdToCount = new Map();
const delay = 499; const delay = 499;
const tryAgain = function(entry) { const updateTitle = async function(tabId) {
if ( entry.count === 1 ) { return false; } let count = tabIdToCount.get(tabId);
entry.count -= 1; if ( count === undefined ) { return; }
tabIdToTimer.set( tabIdToCount.delete(tabId);
entry.tabId, const tab = await vAPI.tabs.get(tabId);
vAPI.setTimeout(( ) => { updateTitle(entry); }, delay) if ( tab instanceof Object === false || tab.discarded === true ) {
); return;
return true; }
};
const onTabReady = function(entry, tab) {
if ( !tab ) { return; }
const µb = µBlock; const µb = µBlock;
const pageStore = µb.pageStoreFromTabId(entry.tabId); const pageStore = µb.pageStoreFromTabId(tabId);
if ( pageStore === null ) { return; } if ( pageStore === null ) { return; }
// Firefox needs this: if you detach a tab, the new tab won't have // 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 // 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. // TODO: Investigate for a fix vAPI-side.
pageStore.rawURL = tab.url; pageStore.rawURL = tab.url;
µb.pageStoresToken = Date.now(); µb.pageStoresToken = Date.now();
if ( !tab.title && tryAgain(entry) ) { return; }
// https://github.com/gorhill/uMatrix/issues/225 // https://github.com/gorhill/uMatrix/issues/225
// Sometimes title changes while page is loading. // Sometimes title changes while page is loading.
const settled = tab.title && tab.title === pageStore.title; const settled =
typeof tab.title === 'string' &&
tab.title !== '' &&
tab.title === pageStore.title;
pageStore.title = tab.title || tab.url || ''; pageStore.title = tab.title || tab.url || '';
if ( !settled ) { if ( settled ) { return; }
tryAgain(entry); if ( tabIdToCount.has(tabId) ) { return; }
} count -= 1;
if ( count === 0 ) { return; }
tabIdToCount.set(tabId, count);
updateTitleAsync(tabId);
}; };
const updateTitle = async function(entry) { const updateTitleAsync = function(tabId) {
tabIdToTimer.delete(entry.tabId); vAPI.setTimeout(( ) => { updateTitle(tabId); }, delay);
const tab = await vAPI.tabs.get(entry.tabId);
onTabReady(entry, tab);
}; };
return function(tabId) { return function(tabId) {
if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; }
const timer = tabIdToTimer.get(tabId); const count = tabIdToCount.get(tabId);
if ( timer !== undefined ) { tabIdToCount.set(tabId, 5);
clearTimeout(timer); 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 pageStoreJanitorSampleAt = 0;
let pageStoreJanitorSampleSize = 10; let pageStoreJanitorSampleSize = 10;
const pageStoreJanitor = function() {
const tabIds = Array.from(µBlock.pageStores.keys()).sort();
const checkTab = async tabId => { const checkTab = async tabId => {
const tab = await vAPI.tabs.get(tabId); const tab = await vAPI.tabs.get(tabId);
if ( tab ) { return; } if ( tab instanceof Object && tab.discarded !== true ) { return; }
µBlock.unbindTabFromPageStats(tabId); µBlock.unbindTabFromPageStats(tabId);
}; };
const pageStoreJanitor = function() {
const tabIds = Array.from(µBlock.pageStores.keys()).sort();
if ( pageStoreJanitorSampleAt >= tabIds.length ) { if ( pageStoreJanitorSampleAt >= tabIds.length ) {
pageStoreJanitorSampleAt = 0; pageStoreJanitorSampleAt = 0;
} }