From b7f8f66df235bc2b1bfc49ab616bb87650e43f99 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Tue, 22 Sep 2026 11:48:22 +0300 Subject: [PATCH 1/2] #6295 register service worker listeners synchronously --- extension/js/common/browser/browser-msg.ts | 7 +- extension/js/service_worker/background.ts | 75 +++++++++++++--------- extension/js/service_worker/bg-handlers.ts | 4 +- extension/js/service_worker/inject.ts | 13 +--- extension/manifest.json | 2 +- 5 files changed, 55 insertions(+), 46 deletions(-) diff --git a/extension/js/common/browser/browser-msg.ts b/extension/js/common/browser/browser-msg.ts index e133cf65e02..78a16ca67e6 100644 --- a/extension/js/common/browser/browser-msg.ts +++ b/extension/js/common/browser/browser-msg.ts @@ -459,7 +459,7 @@ export class BrowserMsg { BrowserMsg.INTERVAL_HANDLERS[name] = handler; } - public static bgListen() { + public static bgListen(ready: Promise) { chrome.runtime.onMessage.addListener((msg: Bm.Raw, _sender, rawRespond: (rawRes: Bm.RawResponse) => void) => { const respondIfPageStillOpen = (response: Bm.RawResponse) => { try { @@ -480,7 +480,10 @@ export class BrowserMsg { if (Object.keys(BrowserMsg.HANDLERS_REGISTERED_BACKGROUND).includes(msg.name)) { // standard or broadcast message const handler: Bm.AsyncRespondingHandler = BrowserMsg.HANDLERS_REGISTERED_BACKGROUND[msg.name]; - BrowserMsg.sendRawResponse(handler(msg.data.bm), respondIfPageStillOpen); + BrowserMsg.sendRawResponse( + ready.then(() => handler(msg.data.bm)), + respondIfPageStillOpen + ); return true; // will respond } else { // broadcast message that backend does not have a handler for - ignored diff --git a/extension/js/service_worker/background.ts b/extension/js/service_worker/background.ts index 2188ba67f35..e09c0ace68a 100644 --- a/extension/js/service_worker/background.ts +++ b/extension/js/service_worker/background.ts @@ -25,10 +25,12 @@ import { ConfiguredIdpOAuth } from '../common/api/authentication/configured-idp- console.info('background.js service worker starting'); -(async () => { - let db: IDBDatabase; +let db: IDBDatabase; +const inMemoryStore = new ExpirationCache('in_memory_store', 4 * 60 * 60 * 1000); // 4 hours + +// Start initialization after all event listeners below have been registered synchronously. +const ready = Promise.resolve().then(async () => { let storage: GlobalStoreDict; - const inMemoryStore = new ExpirationCache('in_memory_store', 4 * 60 * 60 * 1000); // 4 hours await chrome.alarms.clearAll(); await BrowserMsg.createIntervalAlarm('delete_expired', 1); // each minute @@ -56,34 +58,45 @@ console.info('background.js service worker starting'); await BgUtils.handleStoreErr(e); return; } - // storage related handlers +}); - BrowserMsg.bgAddListener('db', (r: Bm.Db) => BgHandlers.dbOperationHandler(db, r)); - BrowserMsg.bgAddListener('inMemoryStoreSet', async (r: Bm.InMemoryStoreSet) => inMemoryStore.set(emailKeyIndex(r.acctEmail, r.key), r.value, r.expiration)); - BrowserMsg.bgAddListener('inMemoryStoreGet', async (r: Bm.InMemoryStoreGet) => inMemoryStore.get(emailKeyIndex(r.acctEmail, r.key))); +BrowserMsg.bgAddListener('db', (r: Bm.Db) => BgHandlers.dbOperationHandler(db, r)); +BrowserMsg.bgAddListener('inMemoryStoreSet', async (r: Bm.InMemoryStoreSet) => inMemoryStore.set(emailKeyIndex(r.acctEmail, r.key), r.value, r.expiration)); +BrowserMsg.bgAddListener('inMemoryStoreGet', async (r: Bm.InMemoryStoreGet) => inMemoryStore.get(emailKeyIndex(r.acctEmail, r.key))); - BrowserMsg.bgAddListener('ajax', BgHandlers.ajaxHandler); - BrowserMsg.bgAddListener('ajaxGmailAttachmentGetChunk', BgHandlers.ajaxGmailAttachmentGetChunkHandler); - BrowserMsg.bgAddListener('expirationCacheGet', BgHandlers.expirationCacheGetHandler); - BrowserMsg.bgAddListener('expirationCacheSet', BgHandlers.expirationCacheSetHandler); - BrowserMsg.bgAddListener('expirationCacheDeleteExpired', BgHandlers.expirationCacheDeleteExpiredHandler); - BrowserMsg.bgAddListener('getApiAuthorization', BgHandlers.getApiAuthorization); - BrowserMsg.bgAddListener('settings', BgHandlers.openSettingsPageHandler); - BrowserMsg.bgAddListener('update_uninstall_url', BgHandlers.updateUninstallUrl); - BrowserMsg.bgAddListener('get_active_tab_info', BgHandlers.getActiveTabInfo); - BrowserMsg.bgAddListener('reconnect_acct_auth_popup', (r: Bm.ReconnectAcctAuthPopup) => GoogleOAuth.newAuthPopup(r)); - BrowserMsg.bgAddListener('reconnect_custom_idp_acct_auth_popup', (r: Bm.ReconnectCustomIDPAcctAuthPopup) => ConfiguredIdpOAuth.newAuthPopup(r.acctEmail)); - BrowserMsg.intervalAddListener('delete_expired', inMemoryStore.deleteExpired); - BrowserMsg.bgListen(); - BrowserMsg.alarmListen(); - await BgHandlers.updateUninstallUrl({}); - injectFcIntoWebmail(); +BrowserMsg.bgAddListener('ajax', BgHandlers.ajaxHandler); +BrowserMsg.bgAddListener('ajaxGmailAttachmentGetChunk', BgHandlers.ajaxGmailAttachmentGetChunkHandler); +BrowserMsg.bgAddListener('expirationCacheGet', BgHandlers.expirationCacheGetHandler); +BrowserMsg.bgAddListener('expirationCacheSet', BgHandlers.expirationCacheSetHandler); +BrowserMsg.bgAddListener('expirationCacheDeleteExpired', BgHandlers.expirationCacheDeleteExpiredHandler); +BrowserMsg.bgAddListener('getApiAuthorization', BgHandlers.getApiAuthorization); +BrowserMsg.bgAddListener('settings', BgHandlers.openSettingsPageHandler); +BrowserMsg.bgAddListener('update_uninstall_url', BgHandlers.updateUninstallUrl); +BrowserMsg.bgAddListener('get_active_tab_info', BgHandlers.getActiveTabInfo); +BrowserMsg.bgAddListener('reconnect_acct_auth_popup', (r: Bm.ReconnectAcctAuthPopup) => GoogleOAuth.newAuthPopup(r)); +BrowserMsg.bgAddListener('reconnect_custom_idp_acct_auth_popup', (r: Bm.ReconnectCustomIDPAcctAuthPopup) => ConfiguredIdpOAuth.newAuthPopup(r.acctEmail)); +BrowserMsg.intervalAddListener('delete_expired', async () => { + await ready; + return inMemoryStore.deleteExpired(); +}); - if (Catch.isThunderbirdMail()) { - BgHandlers.thunderbirdSecureComposeHandler(); - await BgHandlers.thunderbirdContentScriptRegistration(); - BrowserMsg.bgAddListener('thunderbirdGetCurrentUser', BgHandlers.thunderbirdGetCurrentUserHandler); - BrowserMsg.bgAddListener('thunderbirdMsgGet', BgHandlers.thunderbirdMsgGetHandler); - BrowserMsg.bgAddListener('thunderbirdOpenPassphraseDialog', BgHandlers.thunderbirdOpenPassphraseDialog); - } -})().catch(Catch.reportErr); +if (Catch.isThunderbirdMail()) { + BgHandlers.thunderbirdSecureComposeHandler(ready); + BrowserMsg.bgAddListener('thunderbirdGetCurrentUser', BgHandlers.thunderbirdGetCurrentUserHandler); + BrowserMsg.bgAddListener('thunderbirdMsgGet', BgHandlers.thunderbirdMsgGetHandler); + BrowserMsg.bgAddListener('thunderbirdOpenPassphraseDialog', BgHandlers.thunderbirdOpenPassphraseDialog); +} + +BrowserMsg.bgListen(ready); +BrowserMsg.alarmListen(); + +void ready + .then(async () => { + await BgHandlers.updateUninstallUrl({}); + injectFcIntoWebmail(); + + if (Catch.isThunderbirdMail()) { + await BgHandlers.thunderbirdContentScriptRegistration(); + } + }) + .catch(Catch.reportErr); diff --git a/extension/js/service_worker/bg-handlers.ts b/extension/js/service_worker/bg-handlers.ts index 7a68854dd43..b5309b220b4 100644 --- a/extension/js/service_worker/bg-handlers.ts +++ b/extension/js/service_worker/bg-handlers.ts @@ -117,7 +117,7 @@ export class BgHandlers { }); }); - public static thunderbirdSecureComposeHandler = () => { + public static thunderbirdSecureComposeHandler = (ready: Promise) => { const handleClickEvent = async (tabId: number, acctEmail: string, thunderbirdMsgId: number, composeMethod?: messenger.compose._ComposeDetailsType) => { const accountEmails = await GlobalStore.acctEmailsGet(); const useFullScreenSecureCompose = (await messenger.windows.getCurrent()).type === 'messageCompose'; @@ -132,6 +132,7 @@ export class BgHandlers { } }; messenger.composeAction.onClicked.addListener(async tab => { + await ready; const messageDetails = await messenger.compose.getComposeDetails(Number(tab.id)); const composeMethod = messageDetails.type; const msgId = Number(messageDetails.relatedMessageId); @@ -139,6 +140,7 @@ export class BgHandlers { if (acctEmail) await handleClickEvent(Number(tab.id), acctEmail, msgId, composeMethod); }); messenger.messageDisplayAction.onClicked.addListener(async tab => { + await ready; const tabId = Number(tab.id); const messageDetails = await messenger.messageDisplay.getDisplayedMessage(tabId); if (messageDetails) { diff --git a/extension/js/service_worker/inject.ts b/extension/js/service_worker/inject.ts index e1fdb1b8f98..bc5da18bb32 100644 --- a/extension/js/service_worker/inject.ts +++ b/extension/js/service_worker/inject.ts @@ -44,21 +44,12 @@ const isContentScriptInjectionNeeded = (tabId: number, callback: (injected: bool }; const injectContentScripts = (tabId: number, files: string[], callback?: () => void) => { - const filesCopy = files.slice(); - const scriptFile = filesCopy.shift(); - chrome.scripting.executeScript( { target: { tabId }, - files: scriptFile ? [scriptFile] : [], + files, injectImmediately: true, }, - () => { - if (filesCopy.length) { - injectContentScripts(tabId, filesCopy, callback); - } else if (callback) { - callback(); - } - } + () => callback?.() ); }; diff --git a/extension/manifest.json b/extension/manifest.json index 8e73ad9818a..cba9c90b9ea 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -90,7 +90,7 @@ "matches": ["https://mail.google.com/*", "https://accounts.google.com/*", "https://www.google.com/*"] } ], - "minimum_chrome_version": "96", + "minimum_chrome_version": "106", "content_security_policy": { "extension_pages": "script-src 'self'; default-src 'self'; frame-ancestors 'self' https://mail.google.com; img-src 'self' https://* data: blob:; frame-src 'self' blob:; worker-src 'self'; form-action 'none'; media-src 'none'; font-src 'none'; manifest-src 'none'; object-src 'none'; base-uri 'self'; connect-src 'self' *; style-src 'self' 'unsafe-inline';" } From c927d2fb552f4c75a32ad58d0a93ceb27ac836b0 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Tue, 22 Sep 2026 11:56:54 +0300 Subject: [PATCH 2/2] optimize service worker initialization --- extension/js/common/browser/browser-msg.ts | 4 +++- extension/js/service_worker/background.ts | 25 +++++++++++++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/extension/js/common/browser/browser-msg.ts b/extension/js/common/browser/browser-msg.ts index 78a16ca67e6..1ca2a35d376 100644 --- a/extension/js/common/browser/browser-msg.ts +++ b/extension/js/common/browser/browser-msg.ts @@ -452,7 +452,9 @@ export class BrowserMsg { public static async createIntervalAlarm(action: string, periodInMinutes: number) { const alarmName = `${action}_interval`; - await chrome.alarms.create(alarmName, { periodInMinutes }); + if (!(await chrome.alarms.get(alarmName))) { + await chrome.alarms.create(alarmName, { periodInMinutes }); + } } public static intervalAddListener(name: string, handler: IntervalHandler) { diff --git a/extension/js/service_worker/background.ts b/extension/js/service_worker/background.ts index e09c0ace68a..d2775b6958f 100644 --- a/extension/js/service_worker/background.ts +++ b/extension/js/service_worker/background.ts @@ -4,6 +4,7 @@ import { GoogleOAuth } from '../common/api/authentication/google/google-oauth.js'; import { Bm, BrowserMsg } from '../common/browser/browser-msg.js'; +import { storageGet, storageSet } from '../common/browser/chrome.js'; import { emailKeyIndex } from '../common/core/common.js'; import { ExpirationCache } from '../common/core/expiration-cache.js'; import { BgHandlers } from './bg-handlers.js'; @@ -31,12 +32,10 @@ const inMemoryStore = new ExpirationCache('in_memory_store', 4 * 60 * 60 // Start initialization after all event listeners below have been registered synchronously. const ready = Promise.resolve().then(async () => { let storage: GlobalStoreDict; - await chrome.alarms.clearAll(); await BrowserMsg.createIntervalAlarm('delete_expired', 1); // each minute try { await migrateGlobal(); - await GlobalStore.set({ version: Number(VERSION.replace(/\./g, '')) }); storage = await GlobalStore.get(['settings_seen']); } catch (e) { await BgUtils.handleStoreErr(GlobalStore.errCategorize(e)); @@ -90,11 +89,27 @@ if (Catch.isThunderbirdMail()) { BrowserMsg.bgListen(ready); BrowserMsg.alarmListen(); +chrome.runtime.onInstalled.addListener(() => { + void ready + .then(async () => { + await GlobalStore.set({ version: Number(VERSION.replace(/\./g, '')) }); + await BgHandlers.updateUninstallUrl({}); + }) + .catch(Catch.reportErr); +}); + +chrome.runtime.onStartup.addListener(() => { + // Wake the worker on browser startup so the per-session initialization below runs. +}); + void ready .then(async () => { - await BgHandlers.updateUninstallUrl({}); - injectFcIntoWebmail(); - + // Session storage survives worker restarts, but is cleared on disable, reload, update, and browser restart. + const session = await storageGet('session', ['webmailInjectionStarted']); + if (!session.webmailInjectionStarted) { + injectFcIntoWebmail(); + await storageSet('session', { webmailInjectionStarted: true }); + } if (Catch.isThunderbirdMail()) { await BgHandlers.thunderbirdContentScriptRegistration(); }