From ef86a18cb9777a7785ef6b398c171763da5fd3b7 Mon Sep 17 00:00:00 2001 From: Rostyslav Nihrutsa Date: Tue, 28 Oct 2025 12:56:56 +0200 Subject: [PATCH 1/3] fix(tabs): remove unsupported `documentId` option in sendTabMessage for Firefox --- src/browser.ts | 11 +++++++++++ src/tabs.ts | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/browser.ts b/src/browser.ts index a98adad..f44cbe6 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -7,3 +7,14 @@ export const browser = (): typeof chrome => { return chrome; }; + +export const isFirefox = (): boolean => { + let isFirefox = false; + + try { + // @ts-expect-error + isFirefox = !!browser().runtime.getBrowserInfo; + } catch (_e) {} + + return isFirefox; +}; diff --git a/src/tabs.ts b/src/tabs.ts index 006095f..2e672a3 100644 --- a/src/tabs.ts +++ b/src/tabs.ts @@ -1,4 +1,4 @@ -import {browser} from "./browser"; +import {browser, isFirefox} from "./browser"; import {throwRuntimeError} from "./runtime"; import {handleListener} from "./utils"; @@ -270,6 +270,9 @@ export const sendTabMessage = ( options: MessageSendOptions = {} ): Promise => new Promise((resolve, reject) => { + // Firefox does not support `documentId` option + if (isFirefox()) delete options.documentId; + tabs().sendMessage(tabId, message, options, response => { try { throwRuntimeError(); From 8658e48a02054e6686ce16c62bfcc8a905723037 Mon Sep 17 00:00:00 2001 From: Rostyslav Nihrutsa Date: Tue, 28 Oct 2025 13:23:50 +0200 Subject: [PATCH 2/3] Revert "fix(tabs): remove unsupported `documentId` option in sendTabMessage for Firefox" This reverts commit ef86a18cb9777a7785ef6b398c171763da5fd3b7. --- src/browser.ts | 11 ----------- src/tabs.ts | 5 +---- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/browser.ts b/src/browser.ts index f44cbe6..a98adad 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -7,14 +7,3 @@ export const browser = (): typeof chrome => { return chrome; }; - -export const isFirefox = (): boolean => { - let isFirefox = false; - - try { - // @ts-expect-error - isFirefox = !!browser().runtime.getBrowserInfo; - } catch (_e) {} - - return isFirefox; -}; diff --git a/src/tabs.ts b/src/tabs.ts index 2e672a3..006095f 100644 --- a/src/tabs.ts +++ b/src/tabs.ts @@ -1,4 +1,4 @@ -import {browser, isFirefox} from "./browser"; +import {browser} from "./browser"; import {throwRuntimeError} from "./runtime"; import {handleListener} from "./utils"; @@ -270,9 +270,6 @@ export const sendTabMessage = ( options: MessageSendOptions = {} ): Promise => new Promise((resolve, reject) => { - // Firefox does not support `documentId` option - if (isFirefox()) delete options.documentId; - tabs().sendMessage(tabId, message, options, response => { try { throwRuntimeError(); From f2b16586e68a746ed64e008d5deb9be327338864 Mon Sep 17 00:00:00 2001 From: Rostyslav Nihrutsa Date: Wed, 18 Feb 2026 18:32:51 +0200 Subject: [PATCH 3/3] feat(sidebar): enhance API support and add error handling for unsupported features --- package-lock.json | 2 +- package.json | 2 +- src/sidebar.ts | 131 ++++++++++++++++++++++++++++++++++------------ 3 files changed, 99 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index efc9d48..5bb1226 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@commitlint/cli": "^20.0.0", "@commitlint/config-conventional": "^20.0.0", "@release-it/conventional-changelog": "^10.0.1", - "@types/chrome": "^0.1.12", + "@types/chrome": "^0.1.36", "@types/jest": "^30.0.0", "husky": "^9.1.7", "jest": "^30.1.3", diff --git a/package.json b/package.json index 8317bac..32dc0d6 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "@commitlint/cli": "^20.0.0", "@commitlint/config-conventional": "^20.0.0", "@release-it/conventional-changelog": "^10.0.1", - "@types/chrome": "^0.1.12", + "@types/chrome": "^0.1.36", "@types/jest": "^30.0.0", "husky": "^9.1.7", "jest": "^30.1.3", diff --git a/src/sidebar.ts b/src/sidebar.ts index b5dbfaa..db2e919 100644 --- a/src/sidebar.ts +++ b/src/sidebar.ts @@ -6,6 +6,7 @@ type Color = string | ColorArray; type ColorArray = chrome.extensionTypes.ColorArray; type OpenOptions = chrome.sidePanel.OpenOptions; +type CloseOptions = chrome.sidePanel.CloseOptions; type PanelOptions = chrome.sidePanel.PanelOptions; type PanelBehavior = chrome.sidePanel.PanelBehavior; type IconDetails = opr.sidebarAction.IconDetails; @@ -19,6 +20,8 @@ const isAvailableOperaSidebar = (): boolean => globalThis?.opr?.sidebarAction != // Chromium standard const sidePanel = (): typeof chrome.sidePanel | undefined => browser().sidePanel; +export class SidebarError extends Error {} + // Methods export const getSidebarOptions = (tabId?: number): Promise => callWithPromise(cb => { @@ -56,8 +59,22 @@ export const canOpenSidebar = (): boolean => { return false; }; +export const canCloseSidebar = (): boolean => { + if (sidePanel()) { + return true; + } + + const sb = (sidebarAction() as FirefoxSidebarAction) || undefined; + + if (sb) { + return !!sb.close; + } + + return false; +}; + export const openSidebar = (options: OpenOptions): Promise => - callWithPromise(cb => { + callWithPromise(async cb => { const sp = sidePanel(); if (sp) { @@ -67,12 +84,39 @@ export const openSidebar = (options: OpenOptions): Promise => const sb = sidebarAction() as FirefoxSidebarAction | undefined; if (sb?.open) { - return sb.open(); + const result = sb.open(); + + if (result instanceof Promise) { + await result; + } + + return cb(); + } + + throw new SidebarError("The sidebarAction.open API is not supported in this browser"); + }); + +export const closeSidebar = (options: CloseOptions): Promise => + callWithPromise(async cb => { + const sp = sidePanel(); + + if (sp) { + return sp.close(options, cb); } - console.warn("The sidebar open API is not supported in this browser"); + const sb = sidebarAction() as FirefoxSidebarAction | undefined; + + if (sb?.close) { + const result = sb.close(); - cb(); + if (result instanceof Promise) { + await result; + } + + return cb(); + } + + throw new SidebarError("The sidebarAction.close API is not supported in this browser"); }); export const setSidebarOptions = (options?: PanelOptions): Promise => @@ -80,9 +124,7 @@ export const setSidebarOptions = (options?: PanelOptions): Promise => const sp = sidePanel(); if (!sp) { - console.warn("The chrome.sidePanel.setOptions API is not supported for this browser"); - - return cb(); + throw new SidebarError("The chrome.sidePanel.setOptions API is not supported for this browser"); } sp.setOptions(options || {}, cb); @@ -93,12 +135,44 @@ export const setSidebarBehavior = (behavior?: PanelBehavior): Promise => const sp = sidePanel(); if (!sp) { - console.warn("The chrome.sidePanel.setPanelBehavior API is not supported in this browser"); + throw new SidebarError("The chrome.sidePanel.setPanelBehavior API is not supported in this browser"); + } + + sp.setPanelBehavior(behavior || {}, cb); + }); + +export const isOpenSidebar = (windowId?: number): Promise => + callWithPromise(async cb => { + const sb = sidebarAction() as FirefoxSidebarAction | undefined; + + if (sb?.isOpen) { + const result = sb.isOpen({windowId}); + + if (result instanceof Promise) { + return cb(await result); + } else { + return cb(result); + } + } + + throw new SidebarError("The sidebarAction.isOpen API is not supported in this browser"); + }); + +export const toggleSidebar = (): Promise => + callWithPromise(async cb => { + const sb = sidebarAction() as FirefoxSidebarAction | undefined; + + if (sb?.toggle) { + const result = sb.toggle(); + + if (result instanceof Promise) { + await result; + } return cb(); } - sp.setPanelBehavior(behavior || {}, cb); + throw new SidebarError("The sidebarAction.toggle API is not supported in this browser"); }); // Customs methods @@ -153,19 +227,16 @@ export const setSidebarTitle = (title: string | number, tabId?: number): Promise callWithPromise(async cb => { const sb = sidebarAction(); - if (!sb) { - console.warn("The sidebarAction.setTitle API is supported only in Opera or Firefox"); + if (sb?.setTitle) { + const result = sb.setTitle({tabId, title: title.toString()}); + if (result instanceof Promise) { + await result; + } return cb(); } - const result = sb.setTitle({tabId, title: title.toString()}); - - if (result instanceof Promise) { - await result; - } - - cb(); + throw new SidebarError("The sidebarAction.setTitle API is supported only in Opera or Firefox"); }); export const setSidebarBadgeText = (text: string | number, tabId?: number): Promise => @@ -178,9 +249,7 @@ export const setSidebarBadgeText = (text: string | number, tabId?: number): Prom return cb(); } - console.warn("The opr.sidebarAction.setBadgeText API is supported only in Opera"); - - cb(); + throw new SidebarError("The sidebarAction.setBadgeText API is supported only in Opera"); }); export const clearSidebarBadgeText = (tabId?: number): Promise => setSidebarBadgeText("", tabId); @@ -211,9 +280,7 @@ export const setSidebarIcon = (details: IconDetails): Promise => return cb(); } - console.warn("The sidebarAction.setIcon API is supported only in Opera or Firefox"); - - cb(); + throw new SidebarError("The sidebarAction.setIcon API is supported only in Opera or Firefox"); }); export const setSidebarBadgeTextColor = (color: Color, tabId?: number): Promise => @@ -226,9 +293,7 @@ export const setSidebarBadgeTextColor = (color: Color, tabId?: number): Promise< return cb(); } - console.warn("The opr.sidebarAction.setBadgeTextColor API is supported only in Opera"); - - cb(); + throw new SidebarError("The sidebarAction.setBadgeTextColor API is supported only in Opera"); }); export const setSidebarBadgeBgColor = (color: Color, tabId?: number): Promise => @@ -241,9 +306,7 @@ export const setSidebarBadgeBgColor = (color: Color, tabId?: number): Promise => @@ -258,7 +321,7 @@ export const getSidebarTitle = (tabId?: number): Promise => return (sb as any).getTitle({tabId}); } - throw new Error("The sidebarAction.getTitle API not available"); + throw new SidebarError("The sidebarAction.getTitle API not available"); }); export const getSidebarBadgeText = (tabId?: number): Promise => @@ -269,7 +332,7 @@ export const getSidebarBadgeText = (tabId?: number): Promise => return sb.getBadgeText({tabId}, cb); } - throw new Error("The opr.sidebarAction.getBadgeText API is supported only in Opera"); + throw new SidebarError("The sidebarAction.getBadgeText API is supported only in Opera"); }); export const getSidebarBadgeTextColor = (tabId?: number): Promise => @@ -280,7 +343,7 @@ export const getSidebarBadgeTextColor = (tabId?: number): Promise => return sb.getBadgeTextColor({tabId}, cb); } - throw new Error("The opr.sidebarAction.getBadgeTextColor API is supported only in Opera"); + throw new SidebarError("The sidebarAction.getBadgeTextColor API is supported only in Opera"); }); export const getSidebarBadgeBgColor = (tabId?: number): Promise => @@ -291,5 +354,5 @@ export const getSidebarBadgeBgColor = (tabId?: number): Promise => return sb.getBadgeBackgroundColor({tabId}, cb); } - throw new Error("The opr.sidebarAction.getBadgeBackgroundColor API is supported only in Opera"); + throw new SidebarError("The sidebarAction.getBadgeBackgroundColor API is supported only in Opera"); });