Files
browser-cli/extension/src/classes/CommandRegistry.ts
T
daniel156161 477a00db1a
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
feat(extension): add Firefox WebExtension support
- 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.
2026-06-14 19:09:10 +02:00

96 lines
3.7 KiB
TypeScript

import { webExtApi as api } from '../browser-api';
import { CommandGroup } from './CommandGroup';
import type { CommandContext, CommandEntry, CommandSpec } from './CommandGroup';
import { NavigationCommands } from '../commands/navigation';
import { TabsMutationCommands } from '../commands/tabs';
import { TabsQueryCommands } from '../commands/tabs-query';
import { GroupsCommands } from '../commands/groups';
import { WindowsCommands } from '../commands/windows';
import { DomCommands } from '../commands/dom';
import { BrowserDataCommands } from '../commands/browser-data';
import { SessionCommands } from '../commands/session';
import { PerfCommands } from '../commands/perf';
import { ExtensionCommands } from '../commands/extension';
import type { CommandArgs, Serializable, DispatchArgs, PageRequest } from '../types';
function isCommandSpec(entry: CommandEntry): entry is CommandSpec {
return typeof entry !== "function";
}
export function makePagedData(items: Serializable[], page: PageRequest) {
const total = items.length;
const offset = Math.max(0, Number(page.offset) || 0);
const requestedLimit = Math.max(1, Number(page.limit) || 100);
const limit = Math.min(requestedLimit, 1000);
const end = Math.min(offset + limit, total);
return {
__browserCliPage: true,
items: items.slice(offset, end),
offset,
limit,
total,
nextOffset: end < total ? end : null,
};
}
export class CommandRegistry {
private readonly entries = new Map<string, CommandEntry>();
constructor(private readonly ctx: CommandContext) {}
/** Flattens a group's full-id-keyed commands into the registry map. */
register(group: CommandGroup): void {
for (const [command, entry] of Object.entries(group.commands)) {
if (this.entries.has(command)) {
throw new Error(`Duplicate command registration: ${command}`);
}
this.entries.set(command, entry);
}
}
private resolve(command: string): CommandEntry {
const entry = this.entries.get(command);
if (!entry) throw new Error(`Unknown command: ${command}`);
return entry;
}
async dispatch(command: string, args: DispatchArgs, opts: { background?: boolean; page?: PageRequest }): Promise<Serializable> {
const entry = this.resolve(command);
if (isCommandSpec(entry) && entry.background && opts.background) {
// Narrow the dynamic IPC dict to the handler's declared arg type at the
// call boundary.
return this.ctx.jobs.start(command, args, jobArgs => Promise.resolve(entry.run(jobArgs as CommandArgs)));
}
const run = isCommandSpec(entry) ? entry.run : entry;
let result = await run(args as CommandArgs);
if (opts.page && Array.isArray(result)) {
result = makePagedData(result, opts.page);
}
return result;
}
}
/**
* Builds the registry and registers every command group. The SessionCommands
* instance is returned alongside because index.ts wires its lifecycle methods
* (api.tabs.onActivated → activateLazyTab) and NativeConnection references it
* for the clients.rename_profile reconnect side-effect.
*/
export function assembleRegistry(ctx: CommandContext): { registry: CommandRegistry; session: SessionCommands } {
const registry = new CommandRegistry(ctx);
const session = new SessionCommands(ctx);
registry.register(new NavigationCommands(ctx));
registry.register(new TabsMutationCommands(ctx));
registry.register(new TabsQueryCommands(ctx));
registry.register(new GroupsCommands(ctx));
registry.register(new WindowsCommands(ctx));
registry.register(new DomCommands(ctx));
registry.register(new BrowserDataCommands(ctx));
registry.register(session);
registry.register(new PerfCommands(ctx));
registry.register(new ExtensionCommands(ctx));
return { registry, session };
}