返回 DeepSeek-Reasonix
graphics-recovery-main.ts
根目录 / desktop / electron / scripts / fixtures / graphics-recovery-main.ts
1 import { app, dialog } from "electron";
2 import { mkdirSync, writeFileSync } from "node:fs";
3 import { join } from "node:path";
4 import { consumeGraphicsRecoveryArg, GraphicsSettingsStore, loadGraphicsBootstrap } from "../../src/main/graphics.js";
5 import { confirmRecoveryDraftLoss, installGraphicsRecovery } from "../../src/main/graphicsRecoveryHost.js";
6 import { QuitSequencer } from "../../src/main/lifecycle.js";
7 import { MainWindow, DEFAULT_GEOMETRY } from "../../src/main/window.js";
8 import { AppZoomStore } from "../../src/main/zoomStore.js";
9
10 const profile = process.env.REASONIX_RECOVERY_TEST_HOME!;
11 mkdirSync(profile, { recursive: true });
12 app.setPath("userData", profile);
13 const boot = loadGraphicsBootstrap(profile, process.env, process.argv);
14 if (boot.shouldDisable) app.disableHardwareAcceleration();
15 const temporary = consumeGraphicsRecoveryArg(process.argv);
16 const graphics = new GraphicsSettingsStore(boot.configPath, boot);
17 const events: string[] = [], prompts: Electron.MessageBoxOptions[] = [], responses: number[] = [];
18 let relaunchArgs: string[] = [], flushHangs = false, reloads = 0;
19 let duringDialog: (() => void) | undefined;
20 if (process.env.REASONIX_RECOVERY_NATIVE_DIALOG !== "1") {
21 dialog.showMessageBox = (async (...args: unknown[]) => {
22 const options = args[args.length - 1] as Electron.MessageBoxOptions;
23 prompts.push(options);
24 duringDialog?.();
25 return { response: responses.shift() ?? options.cancelId ?? 0, checkboxChecked: false };
26 }) as typeof dialog.showMessageBox;
27 }
28 const log = { info: (s: string) => events.push(s), warn: (s: string) => events.push(s), error: (s: string) => events.push(s) };
29 let main: MainWindow;
30 const lifecycle = new QuitSequencer({
31 app: { quit: () => { if (!lifecycle.onBeforeQuit()) return; events.push("quit-complete"); }, relaunch: args => { relaunchArgs = args; } },
32 service: { beforeClose: async () => false, shutdown: async () => { events.push("service-shutdown"); } },
33 flushRenderer: () => flushHangs ? new Promise(() => {}) : main.flushSessionDraft(),
34 recoveryDraftTimeoutMs: 20, confirmRecoveryDraftLoss,
35 onCloseAllowed: () => { events.push("close-allowed"); }, log,
36 });
37 const host = installGraphicsRecovery({ graphics, lifecycle, log, logsDir: profile, build: "test", temporary });
38 main = new MainWindow({
39 preloadPath: join(profile, "unused-preload.cjs"),
40 appURL: "data:text/html,<title>Reasonix recovery test</title><h1>Recovery test</h1>",
41 platform: process.platform, log, zoomStore: new AppZoomStore(join(profile, "zoom.json")),
42 isQuitting: () => lifecycle.isQuitting,
43 onRendererFailure: (details, canReload) => host.recovery.fault({ role: "renderer", ...details }, canReload),
44 onUnresponsive: () => host.recovery.unresponsive(), onResponsive: () => host.recovery.responsive(),
45 onAppDomReady: () => { reloads++; }, onCloseRequested: async () => {}, onShellAction: () => {},
46 });
47 app.on("before-quit", () => main.allowClose());
48 const snapshot = () => ({ reloads, relaunchArgs, phase: lifecycle.currentPhase, temporary, argv: process.argv, startup: graphics.current.startupEnabled, actual: app.isHardwareAccelerationEnabled(), events, prompts });
49 async function until(check: () => boolean, timeout = 10_000): Promise<void> {
50 const end = Date.now() + timeout;
51 while (Date.now() < end) { if (check()) return; await new Promise(resolve => setTimeout(resolve, 25)); }
52 throw new Error("Recovery fixture condition timed out");
53 }
54 void app.whenReady().then(async () => {
55 main.create(DEFAULT_GEOMETRY); await main.loadApp(); main.show("test");
56 const mode = process.env.REASONIX_RECOVERY_TEST_MODE;
57 if (mode === "gpu" || mode === "native") {
58 flushHangs = true; responses.push(0, 0);
59 app.emit("child-process-gone", {}, { type: "GPU", reason: "crashed", exitCode: 1 });
60 app.emit("child-process-gone", {}, { type: "GPU", reason: "crashed", exitCode: 1 });
61 if (mode === "native") console.log("Native recovery dialog ready; choose restart and confirm disposable draft loss.");
62 await until(() => lifecycle.currentPhase === "completed", mode === "native" ? 180_000 : 10_000);
63 } else if (mode === "renderer") {
64 main.browserWindow!.webContents.forcefullyCrashRenderer();
65 await until(() => reloads === 2);
66 main.browserWindow!.webContents.forcefullyCrashRenderer();
67 await until(() => prompts.length === 1);
68 } else if (mode === "software" || mode === "keep") {
69 responses.push(mode === "keep" ? 0 : 1); host.healthy();
70 await until(() => prompts.length === 1 && !host.recovery.isPrompting);
71 } else if (mode === "record") {
72 app.emit("child-process-gone", {}, { type: "GPU", reason: "crashed", exitCode: 1 });
73 } else if (mode === "previous") {
74 await until(() => prompts.length === 1 && !host.recovery.isPrompting);
75 } else if (mode === "during-dialog" || mode === "during-keep") {
76 const fail = () => app.emit("child-process-gone", {}, { type: "GPU", reason: "crashed", exitCode: 1 });
77 duringDialog = () => { duringDialog = undefined; fail(); };
78 if (mode === "during-keep") { responses.push(1); host.healthy(); }
79 else { fail(); fail(); }
80 await until(() => prompts.length === 1 && !host.recovery.isPrompting);
81 }
82 writeFileSync(join(profile, "result.json"), JSON.stringify(snapshot()));
83 app.exit(0);
84 }).catch(error => { console.error(error, snapshot()); app.exit(1); });
85
85 lines TYPESCRIPT