mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
feat(vom): split function fix
This commit is contained in:
@@ -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";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user