refactor(desktop): extract hidden Playwright CDP target helper
Move CDPTargetDescriptor and the reclaim predicate out of playwright-cdp.ts into a dedicated module so the reclamation rule can be unit-tested in isolation and reused without pulling in the whole hidden browser manager. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,32 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { isReclaimableHiddenPlaywrightTarget } from "./playwright-cdp-targets";
|
||||||
|
|
||||||
|
describe("playwright cdp target pruning", () => {
|
||||||
|
it("does not reclaim blank app-owned page targets", () => {
|
||||||
|
expect(isReclaimableHiddenPlaywrightTarget({
|
||||||
|
id: "main-window-host",
|
||||||
|
type: "page",
|
||||||
|
title: "",
|
||||||
|
url: "",
|
||||||
|
})).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("only reclaims targets marked as hidden Playwright pages", () => {
|
||||||
|
expect(isReclaimableHiddenPlaywrightTarget({
|
||||||
|
id: "hidden-window",
|
||||||
|
type: "page",
|
||||||
|
title: "监控任务 · hidden-playwright",
|
||||||
|
url: "data:text/html;charset=utf-8,%3Cmeta%20name%3D%22geo-hidden-playwright-token%22%3E",
|
||||||
|
})).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores regular user-visible browser targets", () => {
|
||||||
|
expect(isReclaimableHiddenPlaywrightTarget({
|
||||||
|
id: "renderer",
|
||||||
|
type: "page",
|
||||||
|
title: "GEO Rankly Desktop",
|
||||||
|
url: "http://localhost:5173/",
|
||||||
|
})).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
export interface CDPTargetDescriptor {
|
||||||
|
id?: string;
|
||||||
|
type?: string;
|
||||||
|
title?: string;
|
||||||
|
url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hiddenPlaywrightTargetMarkers = [
|
||||||
|
"hidden-playwright",
|
||||||
|
"geo-hidden-playwright-token",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export function isReclaimableHiddenPlaywrightTarget(target: CDPTargetDescriptor): boolean {
|
||||||
|
if ((target.type ?? "").trim() !== "page") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(target.id ?? "").trim()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = target.title ?? "";
|
||||||
|
const url = target.url ?? "";
|
||||||
|
const haystack = `${title}\n${url}`.toLowerCase();
|
||||||
|
|
||||||
|
// Blank Electron page targets can belong to visible BrowserWindow shells.
|
||||||
|
// Only reclaim targets that we can prove were created by the hidden
|
||||||
|
// Playwright bootstrap flow.
|
||||||
|
return hiddenPlaywrightTargetMarkers.some((marker) => haystack.includes(marker));
|
||||||
|
}
|
||||||
@@ -2,6 +2,10 @@ import { BrowserWindow, nativeTheme } from "electron/main";
|
|||||||
import type { BrowserWindow as ElectronBrowserWindow, Session } from "electron/main";
|
import type { BrowserWindow as ElectronBrowserWindow, Session } from "electron/main";
|
||||||
import type { Browser as PlaywrightBrowser, Page as PlaywrightPage } from "playwright-core";
|
import type { Browser as PlaywrightBrowser, Page as PlaywrightPage } from "playwright-core";
|
||||||
|
|
||||||
|
import {
|
||||||
|
isReclaimableHiddenPlaywrightTarget,
|
||||||
|
type CDPTargetDescriptor,
|
||||||
|
} from "./playwright-cdp-targets";
|
||||||
import { STANDARD_USER_AGENT } from "./user-agent";
|
import { STANDARD_USER_AGENT } from "./user-agent";
|
||||||
|
|
||||||
const hiddenPlaywrightIdleTTLms = 5 * 60_000;
|
const hiddenPlaywrightIdleTTLms = 5 * 60_000;
|
||||||
@@ -13,13 +17,6 @@ const cdpPort = Number.parseInt(process.env.GEO_ELECTRON_CDP_PORT ?? "9339", 10)
|
|||||||
const cdpEndpointURL = `http://127.0.0.1:${cdpPort}`;
|
const cdpEndpointURL = `http://127.0.0.1:${cdpPort}`;
|
||||||
const cdpTargetsListURL = `${cdpEndpointURL}/json/list`;
|
const cdpTargetsListURL = `${cdpEndpointURL}/json/list`;
|
||||||
|
|
||||||
interface CDPTargetDescriptor {
|
|
||||||
id?: string;
|
|
||||||
type?: string;
|
|
||||||
title?: string;
|
|
||||||
url?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HiddenPlaywrightRecord {
|
interface HiddenPlaywrightRecord {
|
||||||
key: string;
|
key: string;
|
||||||
accountId: string;
|
accountId: string;
|
||||||
@@ -347,7 +344,7 @@ async function pruneStaleCDPTargets(): Promise<number> {
|
|||||||
|
|
||||||
let closedCount = 0;
|
let closedCount = 0;
|
||||||
for (const target of targets) {
|
for (const target of targets) {
|
||||||
if (!isStaleCDPTarget(target)) {
|
if (!isReclaimableHiddenPlaywrightTarget(target)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,16 +365,6 @@ async function pruneStaleCDPTargets(): Promise<number> {
|
|||||||
return closedCount;
|
return closedCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isStaleCDPTarget(target: CDPTargetDescriptor): boolean {
|
|
||||||
if ((target.type ?? "").trim() !== "page") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!(target.id ?? "").trim()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return !(target.title ?? "").trim() && !(target.url ?? "").trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function isConnectOverCDPTimeout(error: unknown): boolean {
|
function isConnectOverCDPTimeout(error: unknown): boolean {
|
||||||
return error instanceof Error
|
return error instanceof Error
|
||||||
&& error.name === "TimeoutError"
|
&& error.name === "TimeoutError"
|
||||||
|
|||||||
Reference in New Issue
Block a user