import { parseAgentRef } from "./snapshot.ts"; export interface TabPage { url(): string; mainFrame(): unknown; on(event: "close", listener: () => void): unknown; on(event: "framenavigated", listener: (frame: unknown) => void): unknown; } /** Tab identity and document lifetime are part of every snapshot capability. */ export class TabRegistry

{ readonly pages = new Map(); private sequence = 0; private activeId = ""; private snapshotPage?: P; revision = 0; get active(): string { return this.activeId; } get page(): P | undefined { return this.pages.get(this.activeId); } activate(id: string): void { if (!this.pages.has(id)) throw new Error(`Unknown tab ${id}`); if (id !== this.activeId) { this.activeId = id; this.invalidate(); } } add(page: P, active: boolean): string { for (const [id, existing] of this.pages) { if (existing === page) { if (active) this.activate(id); return id; } } const id = `t${++this.sequence}`; this.pages.set(id, page); if (active || !this.activeId) this.activate(id); page.on("close", () => this.remove(id)); page.on("framenavigated", (frame) => { if (frame === page.mainFrame() && (page === this.page || page === this.snapshotPage)) this.invalidate(); }); return id; } remove(id: string): void { const page = this.pages.get(id); if (!page) return; this.pages.delete(id); if (this.activeId === id) { this.activeId = this.pages.keys().next().value ?? ""; this.invalidate(); } else if (page === this.snapshotPage) this.invalidate(); } clear(): void { this.pages.clear(); this.activeId = ""; this.invalidate(); } invalidate(): void { this.revision++; this.snapshotPage = undefined; } beginSnapshot(page: P): number { if (page !== this.page) throw new Error("Active tab changed. Call action snapshot first."); this.invalidate(); return this.revision; } completeSnapshot(page: P, revision: number): void { if (page !== this.page || revision !== this.revision) throw new Error("Page changed during snapshot. Call action snapshot again."); this.snapshotPage = page; } assertSnapshot(page: P, revision: number): void { if (page !== this.page || page !== this.snapshotPage || revision !== this.revision) { throw new Error("Stale snapshot: tab or document changed. Call action snapshot first."); } } ref(page: P, ref: string): string { const parsed = parseAgentRef(ref, this.revision); this.assertSnapshot(page, this.revision); return parsed.playwrightRef; } }