From cf9549263f341f93a1d07d10da7346e1341fc563 Mon Sep 17 00:00:00 2001 From: jogibear9988 Date: Sat, 29 Aug 2026 14:48:41 +0200 Subject: [PATCH] fix(extension): serialize toolbar startup messaging Wait for CSS and content-script injection before sending stored color preferences so the page-side message listener is ready. Target preference updates at the tab that triggered the action, handle receivers that disappear during navigation, reset failed toggle state, and centralize tab lifecycle cleanup to avoid accumulating listeners. --- extension/contextmenu/colormode.js | 22 ++++--- extension/contextmenu/colorscheme.js | 22 ++++--- extension/visbug.js | 91 ++++++++++++++++------------ 3 files changed, 82 insertions(+), 53 deletions(-) diff --git a/extension/contextmenu/colormode.js b/extension/contextmenu/colormode.js index afababa2..39261a87 100644 --- a/extension/contextmenu/colormode.js +++ b/extension/contextmenu/colormode.js @@ -22,16 +22,24 @@ var platform = typeof browser === 'undefined' ? chrome : browser -const sendColorMode = () => { - platform.tabs.query({active: true, currentWindow: true}, ([tab]) => { - tab && platform.tabs.sendMessage(tab.id, { +const sendColorMode = tab_id => { + if (tab_id === undefined) return + + try { + const pending_message = platform.tabs.sendMessage(tab_id, { action: 'COLOR_MODE', params: {mode:colormodestate.mode}, }) - }) + + // The tab can navigate or close before the message is delivered. + pending_message?.catch?.(() => undefined) + } + catch { + // Callback-based browser implementations throw instead of returning a promise. + } } -export const getColorMode = () => { +export const getColorMode = tab_id => { platform.storage.sync.get([storagekey], value => { let found_value = value[storagekey] @@ -64,7 +72,7 @@ export const getColorMode = () => { // send visbug user preference colormodestate.mode = found_value - sendColorMode() + sendColorMode(tab_id) return found_value }) @@ -96,5 +104,5 @@ platform.contextMenus.onClicked.addListener(({parentMenuItemId, menuItemId}, tab platform.storage.sync.set({[storagekey]: menuItemId}) colormodestate.mode = menuItemId - sendColorMode() + sendColorMode(tab?.id) }) diff --git a/extension/contextmenu/colorscheme.js b/extension/contextmenu/colorscheme.js index 8bae16b7..a88a4b0c 100644 --- a/extension/contextmenu/colorscheme.js +++ b/extension/contextmenu/colorscheme.js @@ -15,16 +15,24 @@ var platform = typeof browser === 'undefined' ? chrome : browser -const sendColorScheme = () => { - platform.tabs.query({active: true, currentWindow: true}, ([tab]) => { - tab && platform.tabs.sendMessage(tab.id, { +const sendColorScheme = tab_id => { + if (tab_id === undefined) return + + try { + const pending_message = platform.tabs.sendMessage(tab_id, { action: 'COLOR_SCHEME', params: {mode:colorschemestate.mode}, }) - }) + + // The tab can navigate or close before the message is delivered. + pending_message?.catch?.(() => undefined) + } + catch { + // Callback-based browser implementations throw instead of returning a promise. + } } -export const getColorScheme = () => { +export const getColorScheme = tab_id => { platform.storage.sync.get([schemestoragekey], value => { let found_value = value[schemestoragekey]; @@ -43,7 +51,7 @@ export const getColorScheme = () => { // send visbug user preference colorschemestate.mode = found_value - sendColorScheme() + sendColorScheme(tab_id) return found_value }) @@ -75,5 +83,5 @@ platform.contextMenus.onClicked.addListener(({parentMenuItemId, menuItemId}, tab platform.storage.sync.set({[schemestoragekey]: menuItemId}) colorschemestate.mode = menuItemId - sendColorScheme() + sendColorScheme(tab?.id) }) diff --git a/extension/visbug.js b/extension/visbug.js index ac61f8d5..1b0aa535 100644 --- a/extension/visbug.js +++ b/extension/visbug.js @@ -11,48 +11,61 @@ var platform = typeof browser === 'undefined' ? chrome : browser -const toggleIn = ({id:tab_id}) => { - // toggle out: it's currently loaded and injected - if (state.loaded[tab_id] && state.injected[tab_id]) { - platform.scripting.executeScript({ - target: {tabId: tab_id}, - files: ['toolbar/eject.js'], - }) - state.injected[tab_id] = false - } +const toggleIn = async ({id:tab_id}) => { + try { + // toggle out: it's currently loaded and injected + if (state.loaded[tab_id] && state.injected[tab_id]) { + await platform.scripting.executeScript({ + target: {tabId: tab_id}, + files: ['toolbar/eject.js'], + }) + state.injected[tab_id] = false + } - // toggle in: it's loaded and needs injected - else if (state.loaded[tab_id] && !state.injected[tab_id]) { - platform.scripting.executeScript({ - target: {tabId: tab_id}, - files: ['toolbar/restore.js'], - }) - state.injected[tab_id] = true - getColorMode() - getColorScheme() - } + // toggle in: it's loaded and needs injected + else if (state.loaded[tab_id] && !state.injected[tab_id]) { + await platform.scripting.executeScript({ + target: {tabId: tab_id}, + files: ['toolbar/restore.js'], + }) + state.injected[tab_id] = true + getColorMode(tab_id) + getColorScheme(tab_id) + } - // fresh start in tab - else { - platform.scripting.insertCSS({ - target: {tabId: tab_id}, - files: ['toolbar/bundle.css' ], - }) - platform.scripting.executeScript({ - target: {tabId: tab_id}, - files: ['toolbar/inject.js'], - }) - - state.loaded[tab_id] = true - state.injected[tab_id] = true - getColorMode() - getColorScheme() - } + // fresh start in tab + else { + await platform.scripting.insertCSS({ + target: {tabId: tab_id}, + files: ['toolbar/bundle.css' ], + }) + await platform.scripting.executeScript({ + target: {tabId: tab_id}, + files: ['toolbar/inject.js'], + }) - platform.tabs.onUpdated.addListener(function(tabId) { - if (tabId === tab_id) - state.loaded[tabId] = false - }) + state.loaded[tab_id] = true + state.injected[tab_id] = true + getColorMode(tab_id) + getColorScheme(tab_id) + } + } + catch (error) { + state.loaded[tab_id] = false + state.injected[tab_id] = false + console.error(`Could not toggle VisBug2 in tab ${tab_id}.`, error) + } } +platform.tabs.onUpdated.addListener((tab_id, change_info) => { + if (change_info.status !== 'loading') return + delete state.loaded[tab_id] + delete state.injected[tab_id] +}) + +platform.tabs.onRemoved.addListener(tab_id => { + delete state.loaded[tab_id] + delete state.injected[tab_id] +}) + gimmeToggle(toggleIn)