| 2 | import { config as menuConfig } from '../content-script/menu-tools/index.mjs' |
| 3 | |
| 4 | export function registerCommands() { |
| 5 | Browser.commands.onCommand.addListener(async (command, tab) => { |
| 6 | const message = { |
| 7 | itemId: command, |
| 8 | selectionText: '', |
| 9 | useMenuPosition: false, |
| 10 | } |
| 11 | console.debug('command triggered', message) |
| 12 | |
| 13 | if (command in menuConfig) { |
| 14 | if (menuConfig[command].action) { |
| 15 | // The action may return a Promise (e.g. openSidePanel returns the |
| 16 | // chrome.sidePanel.open() Promise). Keep the call synchronous so the |
| 17 | // user-gesture context is preserved, but observe the Promise so a |
| 18 | // rejection does not become an unhandled rejection in the background. |
| 19 | // Also wrap in try/catch because Browser.commands.onCommand documents |
| 20 | // `tab` as optional, so an action that dereferences tab.* (e.g. the |
| 21 | // openSidePanel call) can throw synchronously. |
| 22 | let result |
| 23 | try { |
| 24 | result = menuConfig[command].action(true, tab) |
| 25 | } catch (error) { |
| 26 | console.error(`failed to run command action "${command}"`, error) |
| 27 | return |
| 28 | } |
| 29 | if (result && typeof result.catch === 'function') { |
| 30 | result.catch((error) => { |
| 31 | console.error(`failed to run command action "${command}"`, error) |
| 32 | }) |
| 33 | } |
| 34 | } |
| 35 | |
| 36 | if (menuConfig[command].genPrompt) { |
| 37 | // Mirror the pattern in menus.mjs so no step here can leak an |
| 38 | // unhandled rejection in the background: |
| 39 | // - Browser.tabs.query() can reject (permission errors, etc.) — |
| 40 | // observe via try/catch. |
| 41 | // - tabs[0] may be undefined when no active tab exists — guard |
| 42 | // before dereferencing currentTab.id. |
| 43 | // - Browser.tabs.sendMessage() (via webextension-polyfill) rejects |
| 44 | // in normal extension usage (no content script listening, |
| 45 | // restricted pages like chrome://, stale content scripts after |
| 46 | // extension reload) — attach a .catch(). |
| 47 | let tabs |
| 48 | try { |
| 49 | tabs = await Browser.tabs.query({ active: true, currentWindow: true }) |
| 50 | } catch (error) { |
| 51 | console.error(`failed to query active tab for command "${command}"`, error) |
| 52 | return |
| 53 | } |
| 54 | const currentTab = tabs && tabs[0] |
| 55 | if (!currentTab) { |
| 56 | console.debug(`command "${command}" triggered but no active tab found, skipping`) |
| 57 | return |
| 58 | } |
| 59 | Browser.tabs |
| 60 | .sendMessage(currentTab.id, { |
| 61 | type: 'CREATE_CHAT', |