feat(vom): split function fix

This commit is contained in:
Ljy-0827
2026-07-28 11:19:19 +08:00
parent c002c394e6
commit a52c2d6bf2
2 changed files with 0 additions and 190 deletions
@@ -119,88 +119,6 @@ describe("ChromiumCdp", () => {
expect(api.sendCommand).toHaveBeenCalledWith({ tabId: 9 }, "Page.enable", {});
});
it("validates the attached top-frame URL for page reads", async () => {
const { api } = fakeApi();
(api.sendCommand as ReturnType<typeof vi.fn>).mockImplementation(
async (_target, method: string) => {
if (method === "Page.getFrameTree") {
return { frameTree: { frame: { url: "https://example.test/page" } } };
}
return {};
},
);
const cdp = new ChromiumCdp(api);
await expect(
cdp.ensureAttachedToUrl(9, "https://example.test/page#section"),
).resolves.toBeUndefined();
expect(api.detach).not.toHaveBeenCalled();
});
it("reattaches when the cached CDP target points at an extension page", async () => {
const { api } = fakeApi();
let frameReads = 0;
(api.sendCommand as ReturnType<typeof vi.fn>).mockImplementation(
async (_target, method: string) => {
if (method === "Page.getFrameTree") {
frameReads += 1;
return {
frameTree: {
frame: {
url:
frameReads === 1
? "chrome-extension://other-extension/page.html"
: "https://example.test/page",
},
},
};
}
return {};
},
);
const cdp = new ChromiumCdp(api);
await expect(cdp.ensureAttachedToUrl(9, "https://example.test/page")).resolves.toBeUndefined();
expect(api.attach).toHaveBeenCalledTimes(2);
expect(api.detach).toHaveBeenCalledWith({ tabId: 9 });
});
it("rejects when reattach still points at the wrong page", async () => {
const { api } = fakeApi();
(api.sendCommand as ReturnType<typeof vi.fn>).mockImplementation(
async (_target, method: string) => {
if (method === "Page.getFrameTree") {
return { frameTree: { frame: { url: "https://wrong.test/" } } };
}
return {};
},
);
const cdp = new ChromiumCdp(api);
await expect(cdp.ensureAttachedToUrl(9, "https://example.test/")).rejects.toThrow(
/CDP target URL mismatch/,
);
expect(api.attach).toHaveBeenCalledTimes(2);
});
it("clears the attach cache after an internal-page access error", async () => {
const { api } = fakeApi();
(api.sendCommand as ReturnType<typeof vi.fn>).mockImplementation(
async (_target, method: string) => {
if (method === "DOM.getDocument") {
throw new Error("Cannot access a chrome-extension:// URL of different extension");
}
return {};
},
);
const cdp = new ChromiumCdp(api);
await expect(cdp.ensureAttached(9)).resolves.toBeUndefined();
expect(cdp.isAttached(9)).toBe(true);
await expect(cdp.send(9, "DOM.getDocument")).rejects.toThrow(/chrome-extension/);
expect(cdp.isAttached(9)).toBe(false);
});
it("enables console capture domains best-effort during attach", async () => {
const { api } = fakeApi();
(api.sendCommand as ReturnType<typeof vi.fn>).mockImplementation(
@@ -93,15 +93,6 @@ const MAX_CONSOLE_STACK_FRAMES = 20;
const MAX_NETWORK_BUFFER = 200;
const MAX_NETWORK_FIELD_LENGTH = 4096;
const MAX_NETWORK_REQUEST_META = 1024;
const CDP_INTERNAL_PROTOCOLS = new Set([
"chrome:",
"chrome-extension:",
"devtools:",
"edge:",
"brave:",
"vivaldi:",
"opera:",
]);
interface ParsedDialogOpening {
type: JavaScriptDialogType;
@@ -182,31 +173,6 @@ export class ChromiumCdp {
await attach;
}
/**
* Attach and verify Chrome's actual CDP target still points at the page URL
* Chrome tabs reported to the tool resolver. A stale debugger target can
* otherwise surface another extension/internal URL while the tab record looks
* like a normal page.
*/
async ensureAttachedToUrl(tabId: number, expectedUrl: string | undefined): Promise<void> {
await this.ensureAttached(tabId);
if (!expectedUrl) return;
const first = await this.readTopFrameUrl(tabId).catch((err) => {
if (isInternalTargetAccessError(err)) return null;
throw err;
});
if (first && cdpUrlsCompatible(first, expectedUrl)) return;
await this.forceReattach(tabId);
const second = await this.readTopFrameUrl(tabId);
if (cdpUrlsCompatible(second, expectedUrl)) return;
throw new Error(
`CDP target URL mismatch for tab ${tabId}: expected ${expectedUrl}, attached to ${second}`,
);
}
/**
* Send a CDP command and decode the result as `T`. Throws on any
* `chrome.runtime.lastError`.
@@ -219,9 +185,6 @@ export class ChromiumCdp {
const result = await this.api.sendCommand({ tabId }, method, params ?? {});
return result as T;
} catch (err) {
if (isInternalTargetAccessError(err)) {
this.clearAttachedState(tabId);
}
throw normalizeError(err);
}
}
@@ -396,31 +359,6 @@ export class ChromiumCdp {
}
}
private async forceReattach(tabId: number): Promise<void> {
this.clearAttachedState(tabId);
try {
await this.api.detach({ tabId });
} catch {
// Chrome may already have detached; attach below is the source of truth.
}
await this.ensureAttached(tabId);
}
private clearAttachedState(tabId: number): void {
this.attachInFlight.delete(tabId);
this.attachedTabs.delete(tabId);
this.clearDialogState(tabId);
this.clearConsoleState(tabId);
this.clearNetworkState(tabId);
}
private async readTopFrameUrl(tabId: number): Promise<string> {
const frameTree = await this.api.sendCommand({ tabId }, "Page.getFrameTree", {});
const url = topFrameUrl(frameTree);
if (!url) throw new Error(`Page.getFrameTree returned no top-frame URL for tab ${tabId}`);
return url;
}
private bindDialogHandler(): void {
if (this.dialogSubscription) return;
const listener = (source: chrome.debugger.Debuggee, method: string, params: unknown) => {
@@ -968,49 +906,3 @@ function normalizeError(err: unknown): Error {
}
return new Error("unknown chrome.debugger error");
}
function isInternalTargetAccessError(err: unknown): boolean {
const message = normalizeError(err).message;
return (
/Cannot access a chrome-extension:\/\/ URL/i.test(message) ||
/Cannot access a chrome:\/\/ URL/i.test(message) ||
/Cannot access contents of url/i.test(message)
);
}
function topFrameUrl(frameTree: unknown): string | null {
const raw = (frameTree ?? {}) as Record<string, unknown>;
const tree = (raw.frameTree ?? {}) as Record<string, unknown>;
const frame = (tree.frame ?? {}) as Record<string, unknown>;
return typeof frame.url === "string" && frame.url.length > 0 ? frame.url : null;
}
function cdpUrlsCompatible(actualUrl: string, expectedUrl: string): boolean {
const actual = normalizeComparableUrl(actualUrl);
const expected = normalizeComparableUrl(expectedUrl);
if (!actual || !expected) return false;
if (isInternalCdpUrl(actual)) return false;
return actual === expected;
}
function normalizeComparableUrl(value: string): string | null {
let parsed: URL;
try {
parsed = new URL(value);
} catch {
return null;
}
parsed.hash = "";
return parsed.toString();
}
function isInternalCdpUrl(value: string): boolean {
let parsed: URL;
try {
parsed = new URL(value);
} catch {
return false;
}
if (CDP_INTERNAL_PROTOCOLS.has(parsed.protocol)) return true;
return parsed.protocol === "about:" && parsed.pathname !== "blank";
}