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
- 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.
119 lines
4.6 KiB
TypeScript
119 lines
4.6 KiB
TypeScript
import { webExtApi as api } from '../browser-api';
|
|
// Large-operation throttling, performance profile, and job-progress helpers.
|
|
import type { Job, JobProgressUpdate } from '../types';
|
|
|
|
export function sleep(ms: number): Promise<void> {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
|
|
export const LARGE_OPERATION_BATCH_SIZE = 25;
|
|
export const LARGE_OPERATION_PAUSE_MS = 25;
|
|
export const GENTLE_OPERATION_BATCH_SIZE = 8;
|
|
export const GENTLE_OPERATION_PAUSE_MS = 100;
|
|
const DEBUG_LARGE_OPERATIONS = false;
|
|
|
|
function debugLargeOperation(message: string) {
|
|
if (DEBUG_LARGE_OPERATIONS) console.log(message);
|
|
}
|
|
|
|
export async function hasAudibleTabs() {
|
|
const audibleTabs = await api.tabs.query({ audible: true });
|
|
return audibleTabs.some(tab => !(tab.mutedInfo && tab.mutedInfo.muted));
|
|
}
|
|
|
|
let largeOperationQueue: Promise<void> = Promise.resolve();
|
|
|
|
export async function runLargeOperation<T>(name: string, fn: () => Promise<T>): Promise<T> {
|
|
const run = largeOperationQueue.then(async () => {
|
|
debugLargeOperation(`[browser-cli] large operation start: ${name}`);
|
|
try {
|
|
return await fn();
|
|
} finally {
|
|
debugLargeOperation(`[browser-cli] large operation done: ${name}`);
|
|
}
|
|
});
|
|
largeOperationQueue = run.then(() => {}, () => {});
|
|
return run;
|
|
}
|
|
|
|
export async function getPerformanceProfile() {
|
|
const { performanceProfile } = await api.storage.local.get<{ performanceProfile?: string }>("performanceProfile");
|
|
return performanceProfile || "auto";
|
|
}
|
|
|
|
export async function setPerformanceProfile(profile: string) {
|
|
const allowed = new Set(["auto", "normal", "gentle", "ultra"]);
|
|
const performanceProfile = allowed.has(profile) ? profile : "auto";
|
|
await api.storage.local.set({ performanceProfile });
|
|
return { performanceProfile };
|
|
}
|
|
|
|
export async function getLargeOperationThrottle(itemCount = 0, mode: string = "auto") {
|
|
const audible = await hasAudibleTabs();
|
|
const storedProfile = await getPerformanceProfile();
|
|
const configuredMode = mode && mode !== "auto" ? mode : storedProfile;
|
|
const gentle = configuredMode === "gentle" || configuredMode === "ultra" || (configuredMode === "auto" && audible);
|
|
let batchSize = gentle ? GENTLE_OPERATION_BATCH_SIZE : LARGE_OPERATION_BATCH_SIZE;
|
|
let pauseMs = gentle ? GENTLE_OPERATION_PAUSE_MS : LARGE_OPERATION_PAUSE_MS;
|
|
|
|
if (configuredMode === "ultra" || itemCount >= 300) {
|
|
batchSize = Math.max(3, Math.floor(batchSize / 2));
|
|
pauseMs *= 2;
|
|
} else if (itemCount >= 100) {
|
|
batchSize = Math.max(5, Math.floor(batchSize * 0.75));
|
|
pauseMs = Math.max(pauseMs, 75);
|
|
}
|
|
|
|
return { batchSize, pauseMs, gentle, audible, itemCount, mode: configuredMode };
|
|
}
|
|
|
|
export function updateJobProgress(job: Job | undefined | null, { phase, current, total }: JobProgressUpdate = {}) {
|
|
if (!job) return;
|
|
if (phase) job.phase = phase;
|
|
if (total != null) job.total = total;
|
|
if (current != null) job.current = current;
|
|
if (job.total) job.percent = Math.min(100, Math.round((job.current || 0) * 100 / job.total));
|
|
job.updatedAt = Date.now();
|
|
}
|
|
|
|
export function throwIfJobCancelled(job: Job | undefined | null) {
|
|
if (job?.cancelRequested) {
|
|
throw new Error(`Job '${job.id}' cancelled`);
|
|
}
|
|
}
|
|
|
|
export async function yieldForLargeOperation(processed: number, batchSize = LARGE_OPERATION_BATCH_SIZE, pauseMs = LARGE_OPERATION_PAUSE_MS) {
|
|
if (processed > 0 && processed % batchSize === 0) {
|
|
await sleep(pauseMs);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run `handler` over `items` in throttle-sized slices, threading job progress
|
|
* and cancellation. Each slice is one `handler` call (so chrome batch APIs like
|
|
* tabs.remove get an array); between slices it reports progress and yields.
|
|
*
|
|
* `total`/`baseCurrent` let callers report against a fixed total accumulated
|
|
* across several calls (e.g. merging tabs from many windows). Returns the
|
|
* running processed count so the next call can continue from it.
|
|
*/
|
|
export async function processInBatches<T>(
|
|
items: T[],
|
|
throttle: { batchSize: number; pauseMs: number },
|
|
handler: (batch: T[]) => Promise<unknown>,
|
|
progress: { job?: Job | null; phase: string; total?: number; baseCurrent?: number },
|
|
): Promise<number> {
|
|
const total = progress.total ?? items.length;
|
|
let done = progress.baseCurrent ?? 0;
|
|
updateJobProgress(progress.job, { phase: progress.phase, current: done, total });
|
|
for (let i = 0; i < items.length; i += throttle.batchSize) {
|
|
throwIfJobCancelled(progress.job);
|
|
const batch = items.slice(i, i + throttle.batchSize);
|
|
await handler(batch);
|
|
done += batch.length;
|
|
updateJobProgress(progress.job, { phase: progress.phase, current: done, total });
|
|
await yieldForLargeOperation(done, throttle.batchSize, throttle.pauseMs);
|
|
}
|
|
return done;
|
|
}
|