feat(extension): add Firefox WebExtension support
Testing / remote-protocol-compat (0.9.5) (push) Successful in 48s
Testing / remote-protocol-compat (0.9.3) (push) Successful in 47s
Build & Publish Package / publish (push) Successful in 46s
Package Extension / package-extension (push) Successful in 59s
Testing / test (push) Failing after 50s

- Add a neutral WebExtension API adapter that uses Firefox browser.* or Chromium chrome.* without mutating globals.
- Switch extension runtime code to the adapter and add Firefox-specific typings for tabs, windows, tab groups, storage, scripting, and native messaging ports.
- Fix Firefox temporary add-on instructions to load the packaged manifest with background.scripts instead of the Chromium service worker manifest.
- Detect Firefox in clients.list via runtime.getBrowserInfo and keep Chromium user-agent fallback support.
- Make navigate.open wait briefly for Firefox to replace initial about:blank with the requested URL.
- Add JS coverage for API selection, clients.list browser detection, and Firefox navigate.open URL polling.
- Bump package and extension version to 0.15.2.
This commit is contained in:
2026-06-14 19:09:10 +02:00
parent 523108e442
commit 477a00db1a
37 changed files with 526 additions and 183 deletions
+9 -8
View File
@@ -1,3 +1,4 @@
import { webExtApi as api } from '../browser-api';
import { fetchTabHtml, getActiveTab, getAliases, isBrowserErrorUrl, tabInfo } from '../core';
import { CommandGroup } from '../classes/CommandGroup';
import type { CommandEntry } from '../classes/CommandGroup';
@@ -17,7 +18,7 @@ export class TabsQueryCommands extends CommandGroup {
};
private async tabsList() {
const windows = await chrome.windows.getAll({ populate: true });
const windows = await api.windows.getAll({ populate: true });
const aliases = await getAliases();
const tabs = [];
for (const w of windows) {
@@ -34,7 +35,7 @@ export class TabsQueryCommands extends CommandGroup {
}
private async tabsActiveInWindow({ windowId }: TabsActiveInWindowArgs) {
const activeTabs = await chrome.tabs.query({ windowId, active: true });
const activeTabs = await api.tabs.query({ windowId, active: true });
const tab = activeTabs[0];
if (!tab) {
throw new Error(`No active tab found for window ${windowId}`);
@@ -43,24 +44,24 @@ export class TabsQueryCommands extends CommandGroup {
}
private async tabsStatus({ tabId }: TabIdArgs) {
const tab = tabId ? await chrome.tabs.get(tabId) : await getActiveTab();
const tab = tabId ? await api.tabs.get(tabId) : await getActiveTab();
return tabInfo(tab);
}
private async tabsFilter({ pattern }: TabsPatternArgs) {
const all = await chrome.tabs.query({});
const all = await api.tabs.query({});
return all.filter(t => t.url && t.url.includes(pattern)).map(tabInfo);
}
private async tabsCount({ pattern }: TabsPatternArgs) {
const all = await chrome.tabs.query({});
const all = await api.tabs.query({});
if (pattern) return all.filter(t => t.url && t.url.includes(pattern)).length;
return all.length;
}
private async tabsQuery({ search }: TabsQueryArgs) {
const q = search.toLowerCase();
const all = await chrome.tabs.query({});
const all = await api.tabs.query({});
return all.filter(t =>
(t.url && t.url.toLowerCase().includes(q)) ||
(t.title && t.title.toLowerCase().includes(q))
@@ -68,7 +69,7 @@ export class TabsQueryCommands extends CommandGroup {
}
private async tabsWatchUrl({ pattern, timeout = 30000, tabId }: TabsWatchUrlArgs = {}) {
const tab = tabId ? await chrome.tabs.get(tabId) : await getActiveTab();
const tab = tabId ? await api.tabs.get(tabId) : await getActiveTab();
const deadline = Date.now() + timeout;
const regex = new RegExp(pattern);
let lastUrl = tab.url || tab.pendingUrl || "";
@@ -81,7 +82,7 @@ export class TabsQueryCommands extends CommandGroup {
if (matches(lastUrl)) return tabInfo(tab);
while (Date.now() < deadline) {
const t = await chrome.tabs.get(tab.id);
const t = await api.tabs.get(tab.id);
lastUrl = t.url || t.pendingUrl || "";
lastStatus = t.status || "unknown";
if (matches(t.pendingUrl || "") || matches(t.url || "")) return tabInfo(t);