Files
reverse-engineering-anything/tests/boundary/browser/cdpElectronProvider.test.ts
T
0154141304 fix(browser): preserve frame identity across captures (#4040)
* fix(browser): preserve capture evidence fidelity

* fix(browser): align frame identity comparisons

* fix(browser): retain historical frame evidence

* fix(electron): retain same-document script owners

* fix(electron): bind frame proof to tree reads

* fix(browser): retain malformed frame scope

* fix(browser): retain WebMCP event scope

* fix(browser): retain malformed timeline identity

* fix(browser): unify frame identity and scope

* fix(electron): prove enclosing frame ancestry

* fix(electron): separate ancestry from file policy

* fix(browser): preserve transient fragment semantics

* fix(browser): retry uncertain initial frame scope

* fix(browser): preserve runtime frame identity

* refactor(browser): separate independent capture fixes

* fix(electron): detect tree-first document replacement

* fix(electron): retain same-document tree authority

---------

Co-authored-by: eco-null <eco-null@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-23 17:19:35 +08:00

1520 lines
50 KiB
TypeScript

import { mkdir, realpath, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
import { createPackage } from "@electron/asar";
import { afterEach, describe, expect, it } from "vitest";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { CdpElectronProvider } from "../../../src/browser/CdpElectronProvider.js";
import { AnalysisCancelledError } from "../../../src/domain/analysisErrorCore.js";
import {
inspectElectronPageInputSchema,
listElectronTargetsInputSchema,
} from "../../../src/domain/javascript/electronObservation.js";
import {
startFakeCdpBrowser,
type FakeCdpBrowser,
} from "../../fixtures/fakeCdpBrowser.js";
const browsers: FakeCdpBrowser[] = [];
const temporary: string[] = [];
afterEach(async () => {
await Promise.all(browsers.splice(0).map(async (browser) => browser.close()));
await Promise.all(
temporary
.splice(0)
.map(async (path) => rm(path, { recursive: true, force: true })),
);
});
it.each(["late", "initial", "destroyed", "final-malformed", "out-of-scope"])(
"captures the current authorized frame inventory: %s",
async (kind) => {
const root = await electronFixture();
const mainUrl = pathToFileURL(join(root, "index.html")).href;
const childUrl = pathToFileURL(join(root, "child.html")).href;
const scriptUrl = pathToFileURL(join(root, "app.js")).href;
const browser = await startFakeCdpBrowser({
electronFileUrl: mainUrl,
eventsBeforeReply: ["Runtime.enable", "Debugger.enable"],
commandResult: ({ method }, _origin, reads) =>
method === "Page.getFrameTree"
? {
frameTree: {
frame: { id: "frame-main", url: mainUrl },
childFrames:
reads > 1 || kind === "initial"
? [
{
frame: {
id: "child",
parentId: "frame-main",
url:
kind === "out-of-scope"
? "https://foreign.test/child"
: childUrl,
},
},
]
: [],
...(kind === "final-malformed" && reads > 2
? {
childFrames: [
{
frame: {
id: "child",
parentId: "frame-main",
url: childUrl,
},
},
null,
],
}
: {}),
},
}
: method === "Debugger.getScriptSource"
? { scriptSource: "void 0;" }
: undefined,
commandEvents: ({ method, sessionId }) =>
method === "Runtime.enable"
? [
{
method: "Runtime.executionContextCreated",
...(sessionId === undefined ? {} : { sessionId }),
params: { context: { id: 7, auxData: { frameId: "child" } } },
},
]
: method === "Debugger.enable"
? [
{
method: "Debugger.scriptParsed",
...(sessionId === undefined ? {} : { sessionId }),
params: {
scriptId: "child-script",
url: scriptUrl,
executionContextId: 7,
length: 7,
scriptLanguage: "JavaScript",
},
},
...(kind === "destroyed"
? [
{
method: "Runtime.executionContextDestroyed",
...(sessionId === undefined ? {} : { sessionId }),
params: { executionContextId: 7 },
},
]
: []),
]
: undefined,
});
browsers.push(browser);
const inspected = await new CdpElectronProvider().inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
include_script_sources: true,
}),
);
if (!inspected.ok) throw inspected.error;
expect(
inspected.value.frames.some(({ frame_id }) => frame_id === "child"),
).toBe(kind !== "out-of-scope");
expect(inspected.value.scripts.items).toContainEqual(
expect.objectContaining({
file_path: join(root, "app.js"),
// Main snapshots historical frame identity at script ingestion, so
// later context teardown does not erase this observed association.
frame_id: kind === "out-of-scope" ? null : "child",
}),
);
if (kind === "final-malformed") {
expect(inspected.value.completeness.attach_limited_sections).toEqual(
expect.arrayContaining(["frames", "scripts", "workers"]),
);
expect(
inspected.value.completeness.excluded.filter(
({ section }) => section === "frames",
),
).toEqual([]);
}
},
);
it.each(["reordered", "parent-changed", "path-changed"])(
"compares semantic frame inventory independent of sibling order: %s",
async (kind) => {
const root = await electronFixture();
const main = pathToFileURL(join(root, "index.html")).href;
const child = pathToFileURL(join(root, "child.html")).href;
const app = pathToFileURL(join(root, "app.js")).href;
const browser = await startFakeCdpBrowser({
electronFileUrl: main,
commandResult: ({ method }, _origin, reads) => {
if (method !== "Page.getFrameTree") return undefined;
const first = {
frame: {
id: "first",
parentId:
reads > 2 && kind === "parent-changed" ? "second" : "frame-main",
url: reads > 2 && kind === "path-changed" ? app : child,
},
};
const second = {
frame: { id: "second", parentId: "frame-main", url: app },
};
return {
frameTree: {
frame: { id: "frame-main", url: main },
childFrames: reads > 2 ? [second, first] : [first, second],
},
};
},
});
browsers.push(browser);
const result = await new CdpElectronProvider().inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
expect(result.value.frames.map(({ frame_id }) => frame_id)).toEqual([
"frame-main",
"first",
"second",
]);
for (const section of ["frames", "scripts", "workers"] as const)
expect(
result.value.completeness.attach_limited_sections.includes(section),
).toBe(kind !== "reordered");
},
);
it.each([
"detached",
"foreign-reuse",
"reparented",
"unvalidated-context",
"same-document",
])("retains authorized historical script owners after %s", async (kind) => {
const root = await electronFixture();
const main = pathToFileURL(join(root, "index.html")).href;
const child = pathToFileURL(join(root, "child.html")).href;
const script = pathToFileURL(join(root, "app.js")).href;
const browser = await startFakeCdpBrowser({
electronFileUrl: main,
eventsBeforeReply: ["Debugger.enable"],
commandResult: ({ method }, _origin, reads) =>
method === "Page.getFrameTree"
? {
frameTree: {
frame: { id: "frame-main", url: main },
childFrames:
reads === 1 || kind === "same-document"
? ["first", "second"].map((id) => ({
frame: { id, parentId: "frame-main", url: child },
}))
: [],
},
}
: undefined,
commandEvents: ({ method, sessionId }) => {
if (method !== "Debugger.enable")
return method === "Runtime.enable" ? [] : undefined;
const scope = sessionId === undefined ? {} : { sessionId };
const sameDocument = (id: string, fragment: string) =>
kind === "same-document"
? [
{
method: "Page.navigatedWithinDocument",
...scope,
params: { frameId: id, url: `${child}#${fragment}` },
},
]
: [];
const events = ["first", "second"].flatMap((id, index) => [
{
method: "Runtime.executionContextCreated",
...scope,
params: { context: { id: index + 7, auxData: { frameId: id } } },
},
...sameDocument(id, "before"),
{
method: "Debugger.scriptParsed",
...scope,
params: {
scriptId: id,
url: script,
executionContextId: index + 7,
hash: "same",
length: 7,
scriptLanguage: "JavaScript",
},
},
...sameDocument(id, "after"),
...(kind === "detached"
? [
{
method: "Page.frameDetached",
...scope,
params: { frameId: id, reason: "remove" },
},
]
: []),
]);
return kind !== "detached" && kind !== "same-document"
? [
...events,
{
method:
kind === "reparented"
? "Page.frameAttached"
: "Page.frameNavigated",
...scope,
params:
kind === "reparented"
? { frameId: "first", parentFrameId: "unknown-parent" }
: {
frame: {
id: "first",
parentId: "frame-main",
url: "https://foreign.test/",
},
},
},
...(kind === "unvalidated-context"
? [
{
method: "Runtime.executionContextCreated",
...scope,
params: {
context: {
id: 7,
auxData: { frameId: "not-authorized" },
},
},
},
]
: []),
{
method: "Debugger.scriptParsed",
...scope,
params: {
scriptId: "foreign-reused",
url: script,
executionContextId: 7,
hash: "foreign",
},
},
]
: events;
},
});
browsers.push(browser);
const result = await new CdpElectronProvider().inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
expect(result.value.frames.map(({ frame_id }) => frame_id)).toEqual(
kind === "same-document"
? ["frame-main", "first", "second"]
: ["frame-main"],
);
expect(
result.value.scripts.items
.filter(({ cdp_hash }) => cdp_hash === "same")
.map(({ frame_id }) => frame_id)
.sort(),
).toEqual(["first", "second"]);
if (kind !== "detached" && kind !== "same-document")
expect(
result.value.scripts.items.find(({ cdp_hash }) => cdp_hash === "foreign")
?.frame_id,
).toBeNull();
});
describe("CdpElectronProvider target access", () => {
it("retains a completed page observation when cancellation arrives during final detach", async () => {
const root = await electronFixture();
const controller = new AbortController();
const browser = await startFakeCdpBrowser({
electronFileUrl: pathToFileURL(join(root, "index.html")).href,
commandEvents: ({ method }) => {
if (method === "Target.detachFromTarget") controller.abort();
return undefined;
},
});
browsers.push(browser);
const result = await new CdpElectronProvider().inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
}),
{ signal: controller.signal },
);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Expected late cancellation");
expect(result.error).toBeInstanceOf(AnalysisCancelledError);
expect(result.error.partialObservation).toMatchObject({
kind: "electron-page-observation",
cleanup: "transport-closed",
inspection: { target: { file_path: join(root, "index.html") } },
});
expect(browser.commands.map(({ method }) => method)).toContain(
"Target.detachFromTarget",
);
const closeDeadline = Date.now() + 1_000;
while (browser.activeConnectionCount() !== 0 && Date.now() < closeDeadline)
await new Promise((resolve) => setTimeout(resolve, 5));
expect(browser.activeConnectionCount()).toBe(0);
});
it("returns every in-scope resource beyond the former collection ceiling", async () => {
const root = await electronFixture();
const index = join(root, "index.html");
const count = 2_001;
await Promise.all(
Array.from({ length: count }, (_value, item) =>
writeFile(join(root, `inventory-${String(item)}.js`), ""),
),
);
const browser = await startFakeCdpBrowser({
electronFileUrl: pathToFileURL(index).href,
electronInventoryCount: count,
});
browsers.push(browser);
const inspected = await new CdpElectronProvider().inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
}),
);
if (!inspected.ok) throw inspected.error;
expect(inspected.value.resources).toHaveLength(count + 1);
expect(inspected.value.completeness.truncated_sections).toEqual([]);
});
it("returns every Electron page target exposed by the endpoint inline", async () => {
const root = await electronFixture();
const index = join(root, "index.html");
const browser = await startFakeCdpBrowser({
electronFileUrl: pathToFileURL(index).href,
additionalElectronPageCount: 205,
});
browsers.push(browser);
const listed = await new CdpElectronProvider().listTargets(
listElectronTargetsInputSchema.parse({
cdp_endpoint: browser.endpoint,
}),
);
if (!listed.ok) throw listed.error;
expect(listed.value.targets).toHaveLength(206);
expect(listed.value.targets[0]?.target_id).toBe("electron-page");
expect(listed.value.targets.at(-1)?.target_id).toBe("electron-page-0204");
});
});
describe("CdpElectronProvider target selection", () => {
it("lists and inspects every canonical local file target", async () => {
const root = await electronFixture();
const index = join(root, "index.html");
const browser = await startFakeCdpBrowser({
electronFileUrl: pathToFileURL(index).href,
duplicateElectronInventory: true,
omitElectronTargetAttachmentState: true,
});
browsers.push(browser);
const provider = new CdpElectronProvider();
const listed = await provider.listTargets(
listElectronTargetsInputSchema.parse({
cdp_endpoint: browser.endpoint,
}),
);
if (!listed.ok) throw listed.error;
expect(listed.value.targets).toEqual([
expect.objectContaining({
target_id: "electron-page",
file_path: index,
attached: null,
}),
]);
expect(
listed.value.limitations.some((item) =>
item.includes("attachment state"),
),
).toBe(true);
expect(listed.value.excluded).toEqual({
unsupported_url: 3,
non_page: 2,
});
const inspected = await provider.inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
}),
);
if (!inspected.ok) throw inspected.error;
expect(inspected.value.target.file_path).toBe(index);
expect(inspected.value.target.attached).toBeNull();
expect(
inspected.value.limitations.some((item) =>
item.includes("attachment state"),
),
).toBe(true);
expect(inspected.value.frames).toEqual([
expect.objectContaining({ file_path: index }),
]);
expect(inspected.value.dom).toMatchObject({ total_nodes: 2 });
expect(inspected.value.scripts).toMatchObject({
total: 1,
items: [
expect.objectContaining({
file_path: join(root, "app.js"),
source: {
included: false,
reason: "source capture was not selected",
},
}),
],
});
expect(inspected.value.resources).toEqual([
expect.objectContaining({ file_path: join(root, "app.js") }),
]);
const methods = browser.commands.map(({ method }) => method);
expect(methods).not.toContain("Runtime.evaluate");
expect(methods).not.toContain("Network.enable");
expect(methods).not.toContain("Debugger.getScriptSource");
expect(methods).toContain("Target.detachFromTarget");
const missing = await provider.inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "missing",
observation_ms: 0,
}),
);
expect(missing).toMatchObject({
ok: false,
error: {
_tag: "BrowserObservationError",
operation: "inspect_electron_page",
reason: "target_not_found",
},
});
});
it("inspects a page-scoped Electron endpoint without browser attachment", async () => {
const root = await electronFixture();
const index = join(root, "index.html");
const browser = await startFakeCdpBrowser({
electronFileUrl: pathToFileURL(index).href,
pageScopedVersionWebSocket: true,
omitTargetWebSocket: true,
});
browsers.push(browser);
const result = await new CdpElectronProvider().inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
expect(result.value.target.file_path).toBe(index);
expect(browser.commands.map(({ method }) => method)).not.toContain(
"Target.attachToTarget",
);
expect(
browser.commands.every(({ sessionId }) => sessionId === undefined),
).toBe(true);
});
it("maps cancelled Electron discovery to the Electron operation", async () => {
const controller = new AbortController();
controller.abort();
const result = await new CdpElectronProvider().listTargets(
listElectronTargetsInputSchema.parse({
cdp_endpoint: "http://127.0.0.1:9222",
}),
{ signal: controller.signal },
);
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisCancelledError",
operation: "list_electron_targets",
},
});
});
});
describe("CdpElectronProvider packaged pages", () => {
it("lists and inspects packaged pages served from inside app.asar", async () => {
const root = await createTestTempDirectory("rea-electron-asar-provider-");
temporary.push(root);
const source = join(root, "source");
await mkdir(source);
await writeFile(
join(source, "index.html"),
"<script src='app.js'></script>",
);
await writeFile(join(source, "app.js"), "export const app = true;");
const archive = join(root, "app.asar");
await createPackage(source, archive);
const packaged = await realpath(archive);
const browser = await startFakeCdpBrowser({
electronFileUrl: `${pathToFileURL(join(archive, "index.html")).href}?isBookDoubleClicked=0#/library`,
});
browsers.push(browser);
const provider = new CdpElectronProvider();
const listed = await provider.listTargets(
listElectronTargetsInputSchema.parse({ cdp_endpoint: browser.endpoint }),
);
if (!listed.ok) throw listed.error;
expect(listed.value.targets).toEqual([
expect.objectContaining({
target_id: "electron-page",
file_path: join(packaged, "index.html"),
}),
]);
const inspected = await provider.inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
}),
);
if (!inspected.ok) throw inspected.error;
expect(inspected.value.target.file_path).toBe(join(packaged, "index.html"));
expect(inspected.value.scripts.items).toEqual([
expect.objectContaining({ file_path: join(packaged, "app.js") }),
]);
});
});
describe("CdpElectronProvider frame capture", () => {
it.each([7, undefined])(
"reports a malformed child frame ID as unavailable protocol evidence: %s",
async (childId) => {
const root = await electronFixture();
const main = pathToFileURL(join(root, "index.html")).href;
const child = pathToFileURL(join(root, "child.html")).href;
const browser = await startFakeCdpBrowser({
electronFileUrl: main,
commandResult: (command) =>
command.method === "Page.getFrameTree"
? {
frameTree: {
frame: { id: "frame-main", url: main },
childFrames: [
{
frame: {
...(childId === undefined ? {} : { id: childId }),
url: child,
},
},
],
},
}
: undefined,
});
browsers.push(browser);
const result = await new CdpElectronProvider().inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
expect(result.value.frames.map((frame) => frame.frame_id)).toEqual([
"frame-main",
]);
expect(result.value.completeness).toMatchObject({
unavailable_sections: ["frames"],
});
expect(result.value.completeness.excluded).toContainEqual({
section: "frames",
reason: "invalid_protocol_value",
count: 1,
});
expect(result.value.completeness.policy_filtered_sections).not.toContain(
"frames",
);
expect(result.value.completeness.conditions).toContain("attach_limited");
},
);
});
describe("CdpElectronProvider malformed source replies", () => {
it.each([{}, { scriptSource: null }, { scriptSource: 123 }])(
"rejects malformed Electron source replies instead of inventing source: %j",
async (reply) => {
const root = await electronFixture();
const browser = await startFakeCdpBrowser({
electronFileUrl: pathToFileURL(join(root, "index.html")).href,
commandResult: ({ method }) =>
method === "Debugger.getScriptSource" ? reply : undefined,
});
browsers.push(browser);
const result = await new CdpElectronProvider().inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
include_script_sources: true,
}),
);
expect(result).toMatchObject({
ok: false,
error: {
operation: "inspect_electron_page",
reason: "protocol_error",
userMessage: expect.stringContaining("scriptSource must be a string"),
},
});
expect(browser.commands.map(({ method }) => method)).toContain(
"Target.detachFromTarget",
);
},
);
});
describe("CdpElectronProvider malformed inventory replies", () => {
const inspectWith = async (
method: string,
reply: (fileUrl: string) => Readonly<Record<string, unknown>>,
) => {
const root = await electronFixture();
const fileUrl = pathToFileURL(join(root, "index.html")).href;
const browser = await startFakeCdpBrowser({
electronFileUrl: fileUrl,
commandResult: (command) =>
command.method === method ? reply(fileUrl) : undefined,
});
browsers.push(browser);
const result = await new CdpElectronProvider().inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
return result.value;
};
it("keeps documents unknown when the DOM string table is missing", async () => {
const inspected = await inspectWith("DOMSnapshot.captureSnapshot", () => ({
documents: [{ documentURL: 0, nodes: { nodeType: [] } }],
}));
expect(inspected.dom).toEqual({ total_nodes: 0, nodes: [] });
expect(inspected.completeness.unavailable_sections).toContain("dom");
expect(inspected.completeness.excluded).toContainEqual({
section: "dom",
reason: "invalid_protocol_value",
count: null,
});
expect(inspected.completeness.policy_filtered_sections).not.toContain(
"dom",
);
});
it.each([
["dom", "DOMSnapshot.captureSnapshot", () => ({ strings: [] })],
[
"dom",
"DOMSnapshot.captureSnapshot",
(fileUrl: string) => ({
strings: [fileUrl],
documents: [{ documentURL: 0, nodes: { nodeType: null } }],
}),
],
[
"resources",
"Page.getResourceTree",
(fileUrl: string) => ({
frameTree: { frame: { id: "main", url: fileUrl }, resources: null },
}),
],
["workers", "Target.getTargets", () => ({ targetInfos: null })],
] as const)(
"keeps a malformed %s inventory incomplete (%s)",
async (section, method, reply) => {
const inspected = await inspectWith(method, reply);
expect(inspected.completeness.status).not.toBe("complete_within_window");
expect(inspected.completeness.excluded).toContainEqual(
expect.objectContaining({
section,
reason: "invalid_protocol_value",
}),
);
},
);
});
describe("CdpElectronProvider script source capture", () => {
it("retains an explicitly empty Electron producer script", async () => {
const root = await electronFixture();
const browser = await startFakeCdpBrowser({
electronFileUrl: pathToFileURL(join(root, "index.html")).href,
commandResult: ({ method }) =>
method === "Debugger.getScriptSource"
? { scriptSource: "" }
: undefined,
});
browsers.push(browser);
const result = await new CdpElectronProvider().inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
include_script_sources: true,
}),
);
if (!result.ok) throw result.error;
expect(result.value.scripts.items[0]?.source).toMatchObject({
included: true,
artifact: { text: "", bytes: 0 },
});
});
it("captures Electron script source only after separate approval", async () => {
const root = await electronFixture();
const browser = await startFakeCdpBrowser({
electronFileUrl: pathToFileURL(join(root, "index.html")).href,
});
browsers.push(browser);
const result = await new CdpElectronProvider().inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
include_script_sources: true,
}),
);
if (!result.ok) throw result.error;
expect(result.value.scripts.items[0]?.source).toMatchObject({
included: true,
artifact: { text: "export const observed = 'source-secret';" },
});
expect(browser.commands.map(({ method }) => method)).toContain(
"Debugger.getScriptSource",
);
});
it("rebases DOM parent indexes across allowed file documents", async () => {
const root = await electronFixture();
const browser = await startFakeCdpBrowser({
electronFileUrl: pathToFileURL(join(root, "index.html")).href,
extraCollections: true,
});
browsers.push(browser);
const result = await new CdpElectronProvider().inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
expect(result.value.dom).toMatchObject({
total_nodes: 4,
nodes: [
{ index: 0, parent_index: -1 },
{ index: 1, parent_index: 0 },
{ index: 2, parent_index: -1 },
{ index: 3, parent_index: 2 },
],
});
});
it("rejects mixed Electron evidence when the main frame navigates", async () => {
const root = await electronFixture();
const page = join(root, "index.html");
const next = join(root, "next.html");
await writeFile(next, "<html>next</html>");
const browser = await startFakeCdpBrowser({
electronFileUrl: pathToFileURL(page).href,
navigateDuringCaptureUrl: pathToFileURL(next).href,
});
browsers.push(browser);
const result = await new CdpElectronProvider().inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
}),
);
expect(result).toMatchObject({
ok: false,
error: {
_tag: "BrowserObservationError",
operation: "inspect_electron_page",
reason: "target_changed",
},
});
});
});
const electronFixture = async (): Promise<string> => {
const root = await createTestTempDirectory("rea-electron-provider-");
temporary.push(root);
await writeFile(join(root, "index.html"), "<script src='app.js'></script>");
await writeFile(join(root, "app.js"), "export const app = true;");
await writeFile(join(root, "child.html"), "<div>child</div>");
return root;
};
it.each(
["initial", "refreshed"].flatMap((phase) =>
["missing", "number", "null", "empty", "foreign", "cycle", "valid"].map(
(parent) => ({ phase, parent }),
),
),
)(
"does not authorize unproved nested Electron ancestry: $phase/$parent",
async ({ phase, parent }) => {
const root = await electronFixture();
const main = pathToFileURL(join(root, "index.html")).href;
const child = pathToFileURL(join(root, "child.html")).href;
const script = pathToFileURL(join(root, "app.js")).href;
const parentValue =
parent === "number"
? 7
: parent === "null"
? null
: parent === "empty"
? ""
: parent === "foreign"
? "other"
: parent === "cycle"
? "child"
: "frame-main";
let eventFrameReads = 0;
const browser = await startFakeCdpBrowser({
electronFileUrl: main,
eventsBeforeReply: ["Debugger.enable", "Page.getFrameTree"],
commandResult: ({ method }, _origin, reads) =>
method === "Page.getFrameTree"
? {
frameTree: {
frame: { id: "frame-main", url: main },
childFrames:
phase === "refreshed" && reads === 1
? []
: [
{
frame: {
id: "child",
url: child,
...(parent === "missing"
? {}
: { parentId: parentValue }),
},
childFrames: [
{
frame: {
id: "grandchild",
parentId: "child",
url: child,
},
},
],
},
],
},
}
: undefined,
commandEvents: ({ method, sessionId }) => {
if (method === "Page.getFrameTree") eventFrameReads++;
if (
!(phase === "initial"
? method === "Debugger.enable"
: method === "Page.getFrameTree" && eventFrameReads === 2)
)
return method === "Runtime.enable" || method === "Debugger.enable"
? []
: undefined;
const scope = sessionId === undefined ? {} : { sessionId };
return ["child", "grandchild"].flatMap((id, index) => [
{
method: "Runtime.executionContextCreated",
...scope,
params: { context: { id: 70 + index, auxData: { frameId: id } } },
},
{
method: "Debugger.scriptParsed",
...scope,
params: {
scriptId: id,
url: script,
executionContextId: 70 + index,
hash: id,
length: 7,
scriptLanguage: "JavaScript",
},
},
]);
},
});
browsers.push(browser);
const result = await new CdpElectronProvider().inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
expect(result.value.frames.map((f) => f.frame_id)).toEqual(
parent === "valid"
? ["frame-main", "child", "grandchild"]
: ["frame-main"],
);
for (const id of ["child", "grandchild"])
expect(result.value.scripts.items).toContainEqual(
expect.objectContaining({
cdp_hash: id,
frame_id: parent === "valid" ? id : null,
}),
);
if (parent !== "valid") {
expect(result.value.completeness.unavailable_sections).toContain(
"frames",
);
expect(result.value.completeness.attach_limited_sections).toContain(
"scripts",
);
expect(result.value.completeness.policy_filtered_sections).not.toContain(
"frames",
);
}
},
);
it.each(
["blank", "remote", "missing-file"].flatMap((parent) =>
["initial", "refreshed", "invalidated"].map((phase) => ({ parent, phase })),
),
)(
"separates structural ancestry from parent file policy: $parent/$phase",
async ({ parent, phase }) => {
const root = await electronFixture();
const main = pathToFileURL(join(root, "index.html")).href;
const child = pathToFileURL(join(root, "child.html")).href;
const script = pathToFileURL(join(root, "app.js")).href;
const parentUrl =
parent === "blank"
? "about:blank"
: parent === "remote"
? "https://foreign.invalid/frame"
: pathToFileURL(join(root, "absent.html")).href;
let eventReads = 0;
const browser = await startFakeCdpBrowser({
electronFileUrl: main,
eventsBeforeReply: ["Page.getFrameTree", "Debugger.enable"],
commandResult: ({ method }, _origin, reads) =>
method === "Page.getFrameTree"
? {
frameTree: {
frame: { id: "frame-main", url: main },
childFrames:
phase === "refreshed" && reads === 1
? []
: [
{
frame: {
id: "parent",
parentId: "frame-main",
url: parentUrl,
},
childFrames: [
{
frame: {
id: "local",
parentId: "parent",
url: child,
},
},
],
},
],
},
}
: undefined,
commandEvents: ({ method, sessionId }) => {
if (method === "Page.getFrameTree") eventReads++;
if (
!(phase === "refreshed" || phase === "invalidated"
? method === "Page.getFrameTree" && eventReads === 2
: method === "Debugger.enable")
)
return method === "Runtime.enable" || method === "Debugger.enable"
? []
: undefined;
const scope = sessionId === undefined ? {} : { sessionId };
return [
...(phase === "invalidated"
? [
{
method: "Page.frameNavigated",
...scope,
params: {
frame: {
id: "parent",
parentId: "frame-main",
url: parentUrl,
loaderId: "new-parent",
},
},
},
]
: []),
{
method: "Runtime.executionContextCreated",
...scope,
params: { context: { id: 81, auxData: { frameId: "local" } } },
},
{
method: "Debugger.scriptParsed",
...scope,
params: {
scriptId: "local",
url: script,
executionContextId: 81,
hash: "local",
length: 7,
scriptLanguage: "JavaScript",
},
},
];
},
});
browsers.push(browser);
const result = await new CdpElectronProvider().inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
expect(result.value.frames.map((frame) => frame.frame_id)).toEqual([
"frame-main",
"local",
]);
expect(result.value.scripts.items).toContainEqual(
expect.objectContaining({
cdp_hash: "local",
frame_id: phase === "invalidated" ? null : "local",
}),
);
expect(result.value.completeness.policy_filtered_sections).toContain(
"frames",
);
expect(result.value.completeness.excluded).not.toContainEqual(
expect.objectContaining({
section: "frames",
reason: "invalid_protocol_value",
}),
);
},
);
it.each(["about:blank", "https://foreign.invalid/root"])(
"does not replace an unauthorized tree root with a local descendant: %s",
async (rootUrl) => {
const root = await electronFixture();
const local = pathToFileURL(join(root, "index.html")).href;
const browser = await startFakeCdpBrowser({
electronFileUrl: local,
commandResult: ({ method }) =>
method === "Page.getFrameTree"
? {
frameTree: {
frame: { id: "frame-main", url: rootUrl },
childFrames: [
{
frame: { id: "local", parentId: "frame-main", url: local },
},
],
},
}
: undefined,
});
browsers.push(browser);
const result = await new CdpElectronProvider().inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
}),
);
expect(result).toMatchObject({
ok: false,
error: { reason: "target_not_allowed" },
});
expect(browser.commands.map(({ method }) => method)).not.toContain(
"Debugger.enable",
);
},
);
const replayScripts =
(source: string, sessionId: string | undefined) =>
(id: string, frameId: string, contextId: number) => [
{
method: "Runtime.executionContextCreated",
...(sessionId === undefined ? {} : { sessionId }),
params: { context: { id: contextId, auxData: { frameId } } },
},
{
method: "Debugger.scriptParsed",
...(sessionId === undefined ? {} : { sessionId }),
params: {
scriptId: id,
url: source,
executionContextId: contextId,
hash: id,
length: 7,
scriptLanguage: "JavaScript",
},
},
];
const replayFrameTree = (
main: string,
child: string,
before: string,
kind: string,
reads: number,
) => ({
frameTree: {
frame: { id: "frame-main", url: main },
childFrames:
reads === 1 || (kind === "during-tree" && reads === 2)
? [
{
frame: {
id: "child",
parentId: "frame-main",
url: before,
...(kind === "missing-loader"
? {}
: { loaderId: "child-loader" }),
},
childFrames: [
{
frame: {
id: "grandchild",
parentId: "child",
url: child,
loaderId: "grandchild-loader",
},
},
],
},
]
: [],
},
});
it.each([
"inline",
"split",
"clear",
"query",
"during-tree",
"post-detach",
"changed-loader",
"changed-parent",
"unknown-frame",
"missing-loader",
"missing-url",
"invalid-fragment",
])(
"preserves authorized same-document history across replay and detach: %s",
async (kind) => {
const root = await electronFixture();
const main = pathToFileURL(join(root, "index.html")).href;
const child = pathToFileURL(join(root, "child.html")).href;
const source = pathToFileURL(join(root, "app.js")).href;
const before = `${child}#old`;
const after =
kind === "clear"
? child
: kind === "query"
? `${child}?caller=retained#next`
: `${child}#next`;
const expectedOwner = [
"inline",
"split",
"clear",
"query",
"during-tree",
"post-detach",
].includes(kind);
let eventReads = 0;
const browser = await startFakeCdpBrowser({
electronFileUrl: main,
eventsBeforeReply: [
"Debugger.enable",
"Page.getFrameTree",
"DOMSnapshot.captureSnapshot",
],
commandResult: ({ method }, _origin, reads) =>
method === "Page.getFrameTree"
? replayFrameTree(main, child, before, kind, reads)
: undefined,
commandEvents: ({ method, sessionId }) => {
if (method === "Page.getFrameTree") eventReads += 1;
if (
method !== "Debugger.enable" &&
!(
kind === "during-tree" &&
(method === "Page.getFrameTree" ||
method === "DOMSnapshot.captureSnapshot")
)
)
return method === "Runtime.enable" ? [] : undefined;
const scope = sessionId === undefined ? {} : { sessionId };
const script = replayScripts(source, sessionId);
const events = [
...script("before", "child", 70),
{
method: "Page.navigatedWithinDocument",
...scope,
params: {
frameId: kind === "unknown-frame" ? "unknown" : "child",
...(kind === "missing-url"
? {}
: kind === "split"
? { url: child, urlFragment: "#next" }
: kind === "clear"
? { url: before, urlFragment: "" }
: kind === "invalid-fragment"
? { url: child, urlFragment: 7 }
: { url: after }),
},
},
{
method: "Page.frameNavigated",
...scope,
params: {
frame: {
id: "child",
parentId:
kind === "changed-parent" ? "other-parent" : "frame-main",
url: after,
loaderId:
kind === "changed-loader" ? "replacement" : "child-loader",
},
},
},
...script("after-replay", "child", 71),
...script("descendant", "grandchild", 72),
{
method: "Page.frameDetached",
...scope,
params: { frameId: "child", reason: "remove" },
},
...(kind === "post-detach"
? script("after-detach", "child", 73)
: []),
];
if (kind !== "during-tree") return events;
if (method === "Debugger.enable") return events.slice(0, 2);
if (method === "Page.getFrameTree")
return eventReads === 2 ? events.slice(2, 3) : [];
return events.slice(3);
},
});
browsers.push(browser);
const result = await new CdpElectronProvider().inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
expect(result.value.scripts.items).toEqual(
expect.arrayContaining([
expect.objectContaining({ cdp_hash: "before", frame_id: "child" }),
expect.objectContaining({
cdp_hash: "after-replay",
frame_id: expectedOwner ? "child" : null,
}),
expect.objectContaining({
cdp_hash: "descendant",
frame_id: expectedOwner ? "grandchild" : null,
}),
]),
);
if (kind === "post-detach")
expect(result.value.scripts.items).toContainEqual(
expect.objectContaining({ cdp_hash: "after-detach", frame_id: null }),
);
expect(
result.value.completeness.attach_limited_sections.includes("scripts"),
).toBe(!expectedOwner || kind === "post-detach" || kind === "during-tree");
expect(result.value.frames.map(({ frame_id }) => frame_id)).toEqual(
kind === "during-tree"
? ["frame-main", "child", "grandchild"]
: ["frame-main"],
);
},
);
it.each([
{ stage: 2, lateEvent: false },
{ stage: 2, lateEvent: true },
{ stage: 3, lateEvent: false },
])(
"rejects a tree-first same-file main reload (read $stage, late event $lateEvent)",
async ({ stage, lateEvent }) => {
const root = await electronFixture();
const main = pathToFileURL(join(root, "index.html")).href;
let reads = 0;
const frame = () => ({
id: "frame-main",
url: main,
loaderId: reads >= stage ? "replacement" : "initial",
});
const browser = await startFakeCdpBrowser({
electronFileUrl: main,
commandResult: ({ method }) => {
if (method !== "Page.getFrameTree") return undefined;
reads++;
return { frameTree: { frame: frame() } };
},
commandEvents: ({ method, sessionId }) =>
lateEvent && method === "DOMSnapshot.captureSnapshot"
? [
{
method: "Page.frameNavigated",
...(sessionId === undefined ? {} : { sessionId }),
params: { frame: frame() },
},
]
: undefined,
});
browsers.push(browser);
const result = await new CdpElectronProvider().inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
}),
);
expect(result).toMatchObject({
ok: false,
error: { reason: "target_changed" },
});
expect(reads).toBeGreaterThanOrEqual(stage);
},
);
it.each(["#next", "?route=next#details"])(
"keeps script authority for a tree-first same-document child URL %s",
async (suffix) => {
const root = await electronFixture();
const main = pathToFileURL(join(root, "index.html")).href;
const child = pathToFileURL(join(root, "child.html")).href;
const source = pathToFileURL(join(root, "app.js")).href;
const tree = (reads: number) => ({
frameTree: {
frame: { id: "frame-main", url: main, loaderId: "main-loader" },
childFrames: [
{
frame: {
id: "child",
parentId: "frame-main",
url: child + (reads > 1 ? suffix : ""),
loaderId: "child-loader",
},
childFrames: [
{
frame: {
id: "grandchild",
parentId: "child",
url: child,
loaderId: "grandchild-loader",
},
},
],
},
],
},
});
const browser = await startFakeCdpBrowser({
electronFileUrl: main,
commandResult: ({ method }, _origin, reads) =>
method === "Page.getFrameTree" ? tree(reads) : undefined,
commandEvents: ({ method, sessionId }) => {
const scripts = replayScripts(source, sessionId);
if (method === "Runtime.enable") return [];
if (method === "Debugger.enable")
return [
...scripts("before-child", "child", 70),
...scripts("before-grandchild", "grandchild", 71),
];
if (method === "DOMSnapshot.captureSnapshot")
return [
{
method: "Page.navigatedWithinDocument",
...(sessionId === undefined ? {} : { sessionId }),
params: { frameId: "child", url: child + suffix },
},
...scripts("after-child", "child", 72),
...scripts("after-grandchild", "grandchild", 73),
];
return undefined;
},
});
browsers.push(browser);
const result = await new CdpElectronProvider().inspectPage(
inspectElectronPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "electron-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
for (const id of ["child", "grandchild"])
for (const phase of ["before", "after"])
expect(result.value.scripts.items).toContainEqual(
expect.objectContaining({ cdp_hash: `${phase}-${id}`, frame_id: id }),
);
expect(result.value.completeness.attach_limited_sections).not.toContain(
"scripts",
);
},
);