feat(windowManager): enhance logging and window management for web and desktop runtimes; conditionally register lifecycle flush and handle child window creation based on runtime
This commit is contained in:
1 parent
0ce52dd085
commit
304e3b3a2d
5 files changed
+122
-16
No files matched your search
@@ -44,7 +44,7 @@ class BrowserWindow {
|
||||
position: "fixed",
|
||||
inset: "0",
|
||||
zIndex: "10000",
|
||||
display: "flex",
|
||||
display: options.visible === false ? "none" : "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
background: "rgba(0,0,0,.5)",
|
||||
@@ -96,6 +96,10 @@ class BrowserWindow {
|
||||
async setFocus() {
|
||||
this.frame.focus();
|
||||
}
|
||||
async setTitle(title: string) {
|
||||
this.frame.title = title;
|
||||
}
|
||||
async setFocusable() {}
|
||||
async isVisible() {
|
||||
return this.overlay.isConnected && this.overlay.style.display !== "none";
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ const browserWindow = {
|
||||
show: noop,
|
||||
hide: noop,
|
||||
setEnabled: noop,
|
||||
setFocusable: noop,
|
||||
setAlwaysOnTop: noop,
|
||||
setShadow: noop,
|
||||
setDecorations: noop,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
vi.mock("react-i18next", () => ({
|
||||
useTranslation: () => ({ t: (key: string) => key }),
|
||||
}));
|
||||
vi.mock("@/i18n", () => ({ default: { t: (key: string) => key } }));
|
||||
class FakeSocket {
|
||||
static OPEN = 1;
|
||||
static instances: FakeSocket[] = [];
|
||||
@@ -149,6 +150,100 @@ describe("browser backend boundary", () => {
|
||||
await child.close();
|
||||
expect(document.querySelector("iframe")).toBeNull();
|
||||
});
|
||||
it("opens settings through the window manager, waits for readiness, reuses and closes the dialog", async () => {
|
||||
vi.spyOn(window, "focus").mockImplementation(() => {});
|
||||
const { openSettings } = await import("../windowManager");
|
||||
const { browserEmit, browserListen } = await import("./events");
|
||||
const { CHILD_WINDOW_LIFECYCLE_EVENT } =
|
||||
await import("../childWindowProtocol");
|
||||
const command = vi.fn();
|
||||
const stop = await browserListen("settings-open-tab", command);
|
||||
const opening = openSettings("appearance");
|
||||
await waitFor(() => expect(document.querySelector("iframe")).toBeTruthy());
|
||||
const frame = document.querySelector("iframe")!;
|
||||
const url = new URL(frame.src);
|
||||
expect(url.pathname).toBe("/nyaterm/index.html");
|
||||
expect(url.searchParams.get("webWindowLabel")).toBe("settings");
|
||||
expect(frame.parentElement?.style.display).toBe("none");
|
||||
const identity = {
|
||||
label: "settings",
|
||||
token: url.searchParams.get("readyToken")!,
|
||||
};
|
||||
expect(identity.token).toBeTruthy();
|
||||
await browserEmit(CHILD_WINDOW_LIFECYCLE_EVENT, {
|
||||
...identity,
|
||||
phase: "shell-ready",
|
||||
});
|
||||
const child = await opening;
|
||||
expect(await child.isVisible()).toBe(true);
|
||||
expect(command).not.toHaveBeenCalled();
|
||||
await browserEmit(CHILD_WINDOW_LIFECYCLE_EVENT, {
|
||||
...identity,
|
||||
phase: "command-ready",
|
||||
command: "settings-open-tab",
|
||||
});
|
||||
expect(command).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
payload: { tab: "appearance", targetWindowLabel: "main" },
|
||||
}),
|
||||
);
|
||||
expect(await openSettings("general")).toBe(child);
|
||||
expect(document.querySelectorAll("iframe")).toHaveLength(1);
|
||||
await child.close();
|
||||
expect(document.querySelector("iframe")).toBeNull();
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
stop();
|
||||
});
|
||||
it("removes a browser child when bootstrap fails before readiness", async () => {
|
||||
vi.spyOn(window, "focus").mockImplementation(() => {});
|
||||
const { openChildWindow } = await import("../windowManager");
|
||||
const { browserEmit } = await import("./events");
|
||||
const { CHILD_WINDOW_LIFECYCLE_EVENT } =
|
||||
await import("../childWindowProtocol");
|
||||
const opening = openChildWindow({
|
||||
label: "new-session",
|
||||
title: "New session",
|
||||
url: "index.html?window=new-session&owner=main",
|
||||
});
|
||||
const failure = expect(opening).rejects.toThrow(
|
||||
"Child window did not finish rendering",
|
||||
);
|
||||
await waitFor(() => expect(document.querySelector("iframe")).toBeTruthy());
|
||||
const url = new URL(document.querySelector("iframe")!.src);
|
||||
await browserEmit(CHILD_WINDOW_LIFECYCLE_EVENT, {
|
||||
label: "new-session",
|
||||
token: url.searchParams.get("readyToken"),
|
||||
phase: "load-failed",
|
||||
stage: "bootstrap-import",
|
||||
});
|
||||
await failure;
|
||||
expect(document.querySelector("iframe")).toBeNull();
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
it("keeps browser logs in the console without sending unsupported persistence requests", async () => {
|
||||
const info = vi.spyOn(console, "info").mockImplementation(() => {});
|
||||
const error = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
const { logger } = await import("../logger");
|
||||
for (let index = 0; index < 60; index += 1) {
|
||||
logger.info({
|
||||
domain: "app.lifecycle",
|
||||
event: "test.entry",
|
||||
message: "Browser log",
|
||||
});
|
||||
}
|
||||
logger.error({
|
||||
domain: "ui.error",
|
||||
event: "test.error",
|
||||
message: "Browser error",
|
||||
});
|
||||
await logger.flush();
|
||||
window.dispatchEvent(new Event("pagehide"));
|
||||
expect(info).toHaveBeenCalledTimes(60);
|
||||
expect(error).toHaveBeenCalledTimes(1);
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
info.mockRestore();
|
||||
error.mockRestore();
|
||||
});
|
||||
it("shows the login gate and mounts the existing UI only after authentication", async () => {
|
||||
fetchMock.mockResolvedValueOnce(
|
||||
new Response(JSON.stringify({ error: "Sign in required" }), {
|
||||
|
||||
+3
-1
@@ -1,4 +1,5 @@
|
||||
import { invoke as tauriInvoke } from "@/lib/backend/api";
|
||||
import { runtime } from "@/lib/backend/runtime";
|
||||
import type { DiagnosticsLogLevel } from "@/types/global";
|
||||
|
||||
type LogLevel = "debug" | "info" | "warn" | "error";
|
||||
@@ -339,6 +340,7 @@ function emit(level: LogLevel, payload: LogPayload): void {
|
||||
|
||||
const entry = normalizePayload(level, payload);
|
||||
writeConsole(entry);
|
||||
if (runtime === "web") return;
|
||||
queue.push(entry);
|
||||
enforceQueueLimit();
|
||||
|
||||
@@ -373,4 +375,4 @@ export const logger = {
|
||||
createRequestId,
|
||||
};
|
||||
|
||||
registerLifecycleFlush();
|
||||
if (runtime === "desktop") registerLifecycleFlush();
|
||||
+18
-14
@@ -1,5 +1,6 @@
|
||||
import { emit, listen } from "@/lib/backend/api";
|
||||
import { WebviewWindow } from "@/lib/backend/platform/webviewWindow";
|
||||
import { runtime } from "@/lib/backend/runtime";
|
||||
import {
|
||||
availableMonitors,
|
||||
getCurrentWindow,
|
||||
@@ -740,20 +741,23 @@ async function openChildWindowInternal(opts: ChildWindowOptions) {
|
||||
);
|
||||
const listenerReadyMs = Math.round(performance.now() - startedAt);
|
||||
try {
|
||||
await invoke("open_child_window", {
|
||||
options: {
|
||||
label: opts.label,
|
||||
title: opts.title,
|
||||
url: appendChildWindowReadyToken(opts.url, readyToken),
|
||||
kind,
|
||||
parentLabel: opts.parentLabel ?? ownerMainWindowLabel,
|
||||
width: opts.width ?? 720,
|
||||
height: opts.height ?? 560,
|
||||
resizable: opts.resizable ?? true,
|
||||
alwaysOnTop: needsAlwaysOnTop(opts.label),
|
||||
stateKey: opts.stateKey,
|
||||
},
|
||||
});
|
||||
const options = {
|
||||
label: opts.label,
|
||||
title: opts.title,
|
||||
url: appendChildWindowReadyToken(opts.url, readyToken),
|
||||
kind,
|
||||
parentLabel: opts.parentLabel ?? ownerMainWindowLabel,
|
||||
width: opts.width ?? 720,
|
||||
height: opts.height ?? 560,
|
||||
resizable: opts.resizable ?? true,
|
||||
alwaysOnTop: needsAlwaysOnTop(opts.label),
|
||||
stateKey: opts.stateKey,
|
||||
};
|
||||
if (runtime === "web") {
|
||||
new WebviewWindow(opts.label, { ...options, visible: false });
|
||||
} else {
|
||||
await invoke("open_child_window", { options });
|
||||
}
|
||||
const invokeMs = Math.round(performance.now() - startedAt);
|
||||
|
||||
const win = await WebviewWindow.getByLabel(opts.label);
|
||||
|
||||
Reference in new issue
Block a user