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", {});
|
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 () => {
|
it("enables console capture domains best-effort during attach", async () => {
|
||||||
const { api } = fakeApi();
|
const { api } = fakeApi();
|
||||||
(api.sendCommand as ReturnType<typeof vi.fn>).mockImplementation(
|
(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_BUFFER = 200;
|
||||||
const MAX_NETWORK_FIELD_LENGTH = 4096;
|
const MAX_NETWORK_FIELD_LENGTH = 4096;
|
||||||
const MAX_NETWORK_REQUEST_META = 1024;
|
const MAX_NETWORK_REQUEST_META = 1024;
|
||||||
const CDP_INTERNAL_PROTOCOLS = new Set([
|
|
||||||
"chrome:",
|
|
||||||
"chrome-extension:",
|
|
||||||
"devtools:",
|
|
||||||
"edge:",
|
|
||||||
"brave:",
|
|
||||||
"vivaldi:",
|
|
||||||
"opera:",
|
|
||||||
]);
|
|
||||||
|
|
||||||
interface ParsedDialogOpening {
|
interface ParsedDialogOpening {
|
||||||
type: JavaScriptDialogType;
|
type: JavaScriptDialogType;
|
||||||
@@ -182,31 +173,6 @@ export class ChromiumCdp {
|
|||||||
await attach;
|
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
|
* Send a CDP command and decode the result as `T`. Throws on any
|
||||||
* `chrome.runtime.lastError`.
|
* `chrome.runtime.lastError`.
|
||||||
@@ -219,9 +185,6 @@ export class ChromiumCdp {
|
|||||||
const result = await this.api.sendCommand({ tabId }, method, params ?? {});
|
const result = await this.api.sendCommand({ tabId }, method, params ?? {});
|
||||||
return result as T;
|
return result as T;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (isInternalTargetAccessError(err)) {
|
|
||||||
this.clearAttachedState(tabId);
|
|
||||||
}
|
|
||||||
throw normalizeError(err);
|
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 {
|
private bindDialogHandler(): void {
|
||||||
if (this.dialogSubscription) return;
|
if (this.dialogSubscription) return;
|
||||||
const listener = (source: chrome.debugger.Debuggee, method: string, params: unknown) => {
|
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");
|
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