4052 lines · 149.8 KB · TypeScript
RawDownload
import { it as effectIt } from "@effect/vitest";import { DESKTOP_PREVIEW_RECORDING_CAPTURE_TRIGGER } from "@t3tools/contracts";import type { DesktopPreviewRecordingFrame } from "@t3tools/contracts";import { HostProcessPlatform } from "@t3tools/shared/hostProcess";import * as Cause from "effect/Cause";import * as Deferred from "effect/Deferred";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import * as FileSystem from "effect/FileSystem";import * as Fiber from "effect/Fiber";import * as Layer from "effect/Layer";import * as Logger from "effect/Logger";import * as Option from "effect/Option";import * as Path from "effect/Path";import * as Schema from "effect/Schema";import type * as Scope from "effect/Scope";import { TestClock } from "effect/testing";import { beforeEach, describe, expect, it, vi } from "vite-plus/test"; import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";import * as ElectronWindow from "../electron/ElectronWindow.ts";import * as BrowserSession from "./BrowserSession.ts";import * as PreviewManager from "./Manager.ts"; describe("fitPictureInPictureContentSize", () => {  it("preserves the PiP content area across aspect-ratio changes", () => {    expect(PreviewManager.fitPictureInPictureContentSize([480, 320], 16 / 9)).toEqual([523, 294]);    expect(PreviewManager.fitPictureInPictureContentSize([480, 320], 9 / 16)).toEqual([294, 523]);  });   it("does not collapse toward the minimum size when orientation changes repeatedly", () => {    const portrait = PreviewManager.fitPictureInPictureContentSize([523, 294], 9 / 16);    const landscape = PreviewManager.fitPictureInPictureContentSize(portrait, 16 / 9);     expect(portrait).toEqual([294, 523]);    expect(landscape).toEqual([523, 294]);  });}); describe("recordingFileExtension", () => {  it("derives the artifact extension from the recorder's actual mime type", () => {    expect(PreviewManager.recordingFileExtension("video/mp4;codecs=avc1.640028")).toBe("mp4");    expect(PreviewManager.recordingFileExtension("video/webm;codecs=vp9")).toBe("webm");    expect(PreviewManager.recordingFileExtension("video/x-matroska")).toBe("matroska");  });}); describe("isPreviewRefreshShortcut", () => {  const input = (overrides: Partial<Electron.Input> = {}) =>    ({      type: "keyDown",      key: "r",      meta: true,      control: false,      shift: false,      alt: false,      ...overrides,    }) as Electron.Input;   it("recognizes the platform refresh chord without matching modified variants", () => {    expect(PreviewManager.isPreviewRefreshShortcut(input())).toBe(true);    expect(PreviewManager.isPreviewRefreshShortcut(input({ meta: false, control: true }))).toBe(      true,    );    expect(PreviewManager.isPreviewRefreshShortcut(input({ shift: true }))).toBe(false);    expect(PreviewManager.isPreviewRefreshShortcut(input({ type: "keyUp" }))).toBe(false);  });}); describe("previewWindowOpenAction", () => {  const details = (overrides: {    readonly url?: string;    readonly disposition?: Electron.HandlerDetails["disposition"];  }) => ({    url: "https://accounts.google.com/o/oauth2/auth",    disposition: "new-window" as Electron.HandlerDetails["disposition"],    ...overrides,  });   it("opens a real window for scripted popups so the opener survives", () => {    // OAuth SDKs read a null `window.open()` as a blocked popup, and they need    // the opener alive to receive the credential back.    expect(PreviewManager.previewWindowOpenAction(details({}))).toBe("popup");    expect(      PreviewManager.previewWindowOpenAction(details({ url: "http://localhost:5173/auth" })),    ).toBe("popup");  });   it("keeps target=_blank links in the preview tab", () => {    expect(PreviewManager.previewWindowOpenAction(details({ disposition: "foreground-tab" }))).toBe(      "navigate",    );    expect(PreviewManager.previewWindowOpenAction(details({ disposition: "background-tab" }))).toBe(      "navigate",    );  });   it("does not hand a window to schemes that cannot be hardened", () => {    // A popup skips the `will-attach-webview` hardening, so it only gets a window    // when its preferences can be overridden. Chromium copies the guest's    // preferences for `about:blank` and forbids overriding them.    for (const url of [      "about:blank",      "javascript:alert(1)",      "file:///etc/passwd",      "vscode://vscode-remote/ssh-remote+box/tmp",      "not a url",    ]) {      expect(PreviewManager.previewWindowOpenAction(details({ url }))).toBe("navigate");    }  });}); const {  browserWindowConstructor,  createFromPath,  fromId,  getFocusedWebContents,  mkdir,  showItemInFolder,  webviewSend,  writeFile,  writeImage,} = vi.hoisted(() => ({  browserWindowConstructor: vi.fn(),  createFromPath: vi.fn((): { readonly isEmpty: () => boolean } => ({ isEmpty: () => false })),  fromId: vi.fn<(_id?: number) => Electron.WebContents | null>((_id?: number) => null),  getFocusedWebContents: vi.fn(() => null),  mkdir: vi.fn((_path: string) => undefined),  showItemInFolder: vi.fn(),  webviewSend: vi.fn(),  writeFile: vi.fn((_path: string, _data: Uint8Array) => undefined),  writeImage: vi.fn(),})); vi.mock("electron", () => ({  BrowserWindow: browserWindowConstructor,  clipboard: {    writeImage,  },  nativeImage: {    createFromPath,  },  shell: {    showItemInFolder,  },  session: {    fromPartition: vi.fn(),  },  webContents: {    fromId,    getFocusedWebContents,  },})); const browserSessionLayer = Layer.succeed(  BrowserSession.BrowserSession,  BrowserSession.BrowserSession.of({    getPartition: () => Effect.succeed("persist:t3code-preview-test"),    isPartition: (partition) => partition.startsWith("persist:t3code-preview-"),    getSession: () => Effect.die("unexpected getSession"),    clearCookies: () => Effect.void,    clearCache: () => Effect.void,  }),); const environmentLayer = Layer.succeed(  DesktopEnvironment.DesktopEnvironment,  DesktopEnvironment.DesktopEnvironment.of({    browserArtifactsDir: "/tmp/t3/dev/browser-artifacts",    dirname: "/tmp/t3/desktop",    path: {      join: (...parts: ReadonlyArray<string>) => parts.join("/"),    },  } as DesktopEnvironment.DesktopEnvironment["Service"]),); const fileSystemLayer = FileSystem.layerNoop({  makeDirectory: (path) =>    Effect.sync(() => {      mkdir(path);    }),  writeFile: (path, data) =>    Effect.sync(() => {      writeFile(path, data);    }),}); const layer = PreviewManager.layer.pipe(  Layer.provideMerge(browserSessionLayer),  Layer.provideMerge(environmentLayer),  Layer.provideMerge(fileSystemLayer),  Layer.provideMerge(Path.layer),  Layer.provideMerge(Layer.succeed(HostProcessPlatform, "darwin")),);const encodePreviewManagerError = Schema.encodeSync(PreviewManager.PreviewManagerError); const withManager = <A>(  use: (    manager: PreviewManager.PreviewManager["Service"],  ) => Effect.Effect<A, PreviewManager.PreviewManagerError, Scope.Scope>,) =>  Effect.gen(function* () {    const manager = yield* PreviewManager.PreviewManager;    return yield* use(manager);  }).pipe(Effect.provide(layer), Effect.scoped); interface TestCapturedPreviewImage {  readonly toJPEG: () => Buffer;  readonly getSize: () => { readonly width: number; readonly height: number };} type TestDisplayMediaHandler = (  request: { readonly frame: { readonly frameTreeNodeId: number } | null },  callback: (streams: { video?: unknown }) => void,) => void; interface TestHostWebContents {  readonly id: number;  readonly mainFrame: { readonly frameTreeNodeId: number };  readonly executeJavaScript: ReturnType<typeof vi.fn>;  readonly isDestroyed: () => boolean;  readonly session: {    readonly setDisplayMediaRequestHandler: ReturnType<typeof vi.fn>;  };  readonly displayMediaHandler: () => TestDisplayMediaHandler | undefined;} type TestPreviewWebContents = Electron.WebContents & {  readonly setBackgroundThrottling: ReturnType<typeof vi.fn<(enabled: boolean) => void>>;}; const makeTestHostWebContents = (): TestHostWebContents => {  let handler: TestDisplayMediaHandler | undefined;  return {    id: 7,    mainFrame: { frameTreeNodeId: 7 },    executeJavaScript: vi.fn(async () => true),    isDestroyed: () => false,    session: {      setDisplayMediaRequestHandler: vi.fn((next: TestDisplayMediaHandler) => {        handler = next;      }),    },    displayMediaHandler: () => handler,  };}; const makeTestPreviewWebContents = (  capturePage: () => Promise<TestCapturedPreviewImage>,  id = 42,  hostWebContents: TestHostWebContents = makeTestHostWebContents(),) => {  const setBackgroundThrottling = vi.fn<(enabled: boolean) => void>();  return {    id,    mainFrame: { routingId: id },    hostWebContents,    executeJavaScript: vi.fn(async () => ({ width: 1280, height: 720 })),    isDestroyed: () => false,    getType: () => "webview",    getURL: () => "https://example.com",    getTitle: () => "Example",    isLoading: () => false,    getZoomFactor: () => 1,    setZoomFactor: vi.fn(),    setAudioMuted: vi.fn(),    setBackgroundThrottling,    isCurrentlyAudible: () => false,    on: vi.fn(),    off: vi.fn(),    ipc: { on: vi.fn(), off: vi.fn() },    send: webviewSend,    navigationHistory: { canGoBack: () => false, canGoForward: () => false },    setIgnoreMenuShortcuts: vi.fn(),    setWindowOpenHandler: vi.fn(),    debugger: {      isAttached: () => false,      attach: vi.fn(),      sendCommand: vi.fn(async () => undefined),      on: vi.fn(),      off: vi.fn(),    },    capturePage,  } as unknown as TestPreviewWebContents;}; /** Two ready tabs (41, 42) sharing one window, so they contend for the single display-media slot. */const setupRecordingRaceTabs = (manager: PreviewManager.PreviewManager["Service"]) =>  Effect.gen(function* () {    const capturePage = vi.fn(async () => ({      toJPEG: () => Buffer.from("unused-recording-frame"),      getSize: () => ({ width: 1280, height: 720 }),    }));    const host = makeTestHostWebContents();    const destroyedIds = new Set<number>();    const makeWebContents = (id: number) =>      Object.assign(makeTestPreviewWebContents(capturePage, id, host), {        executeJavaScript: vi.fn(async () => ({ width: 1280, height: 720 })),        isDestroyed: () => destroyedIds.has(id),      });    const webContentsById = new Map([      [41, makeWebContents(41)],      [42, makeWebContents(42)],    ]);    fromId.mockImplementation((id) =>      id === undefined ? null : (webContentsById.get(id) ?? null),    );    yield* manager.createTab("tab_race_a");    yield* manager.createTab("tab_race_b");    yield* manager.registerWebview("tab_race_a", 41);    yield* manager.registerWebview("tab_race_b", 42);    const grants: Array<{ video?: unknown }> = [];    return {      host,      grants,      destroy: (id: number) => destroyedIds.add(id),      takeGrant: (frame = host.mainFrame) =>        host.displayMediaHandler()?.({ frame }, (value) => {          grants.push(value);        }),    };  }); const TEST_FAVICON = "data:image/png;base64,cG5n"; const makeSourcePng = (width = 1, height = 1): Buffer => {  const buffer = Buffer.alloc(24);  Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]).copy(buffer);  buffer.writeUInt32BE(width, 16);  buffer.writeUInt32BE(height, 20);  return buffer;}; const makeFaviconWebContents = (options?: {  readonly fetch?: (url: string, init?: RequestInit) => Promise<Response>;  readonly id?: number;  readonly rasterize?: (code: string) => Promise<unknown>;  readonly url?: string;}) => {  const sourcePng = makeSourcePng();  const listeners = new Map<string, (...args: never[]) => void>();  let currentUrl = options?.url ?? "http://localhost:3200/";  let destroyed = false;  let loading = false;  const fetch = vi.fn(    options?.fetch ??      (async () =>        new Response(new Uint8Array(sourcePng), {          headers: { "content-type": "image/png" },        })),  );  const executeJavaScriptInIsolatedWorld = vi.fn(    async (_worldId: number, scripts: ReadonlyArray<{ readonly code: string }>) =>      options?.rasterize ? options.rasterize(scripts[0]?.code ?? "") : TEST_FAVICON,  );  const reload = vi.fn();  const loadURL = vi.fn(async (url: string) => {    currentUrl = url;  });  const off = vi.fn();  const debuggerOff = vi.fn();  const webContents = {    id: options?.id ?? 42,    isDestroyed: () => destroyed,    getType: () => "webview",    getURL: () => currentUrl,    getTitle: () => "Preview",    isLoading: () => loading,    isDevToolsOpened: () => false,    getZoomFactor: () => 1,    setZoomFactor: vi.fn(),    setAudioMuted: vi.fn(),    isCurrentlyAudible: () => false,    reload,    reloadIgnoringCache: vi.fn(),    loadURL,    on: vi.fn((event: string, listener: (...args: never[]) => void) => {      listeners.set(event, listener);    }),    off,    ipc: { on: vi.fn(), off: vi.fn() },    send: webviewSend,    session: { fetch },    navigationHistory: { canGoBack: () => false, canGoForward: () => false },    setIgnoreMenuShortcuts: vi.fn(),    setWindowOpenHandler: vi.fn(),    executeJavaScriptInIsolatedWorld,    debugger: {      isAttached: () => false,      attach: vi.fn(),      sendCommand: vi.fn(async () => undefined),      on: vi.fn(),      off: debuggerOff,    },  };  return {    executeJavaScriptInIsolatedWorld,    fetch,    debuggerOff,    listeners,    loadURL,    off,    reload,    setDestroyed: (value: boolean) => {      destroyed = value;    },    setLoading: (value: boolean) => {      loading = value;    },    setUrl: (url: string) => {      currentUrl = url;    },    webContents: webContents as never,  };}; const settle = function* (until: () => boolean) {  for (let attempt = 0; attempt < 30 && !until(); attempt++) {    yield* Effect.promise(() => Promise.resolve());  }}; const makeTestPictureInPictureWindow = (loadURL: () => Promise<void> = async () => undefined) => {  const listeners = new Map<string, () => void>();  const webContentsListeners = new Map<string, () => void>();  const send = vi.fn();  let destroyed = false;  const webContents = {    on: vi.fn((event: string, listener: () => void) => {      webContentsListeners.set(event, listener);    }),    off: vi.fn((event: string) => {      webContentsListeners.delete(event);    }),    send,  };  const pictureInPictureWindow = {    isDestroyed: vi.fn(() => destroyed),    once: vi.fn((event: string, listener: () => void) => {      listeners.set(event, listener);    }),    setAlwaysOnTop: vi.fn(),    setVisibleOnAllWorkspaces: vi.fn(),    setAspectRatio: vi.fn(),    getContentSize: vi.fn(() => [480, 320]),    setContentSize: vi.fn(),    loadURL: vi.fn(loadURL),    showInactive: vi.fn(() => {      if (destroyed) throw new Error("Picture-in-picture window is closed.");    }),    close: vi.fn(() => {      if (destroyed) return;      destroyed = true;      listeners.get("closed")?.();    }),    get webContents() {      if (destroyed) throw new Error("Picture-in-picture window is closed.");      return webContents;    },  };  return { pictureInPictureWindow, send, webContentsListeners };}; describe("PreviewManager", () => {  beforeEach(() => {    browserWindowConstructor.mockReset();    fromId.mockClear();    getFocusedWebContents.mockReset();    getFocusedWebContents.mockReturnValue(null);    mkdir.mockClear();    writeFile.mockClear();    showItemInFolder.mockClear();    writeImage.mockClear();    createFromPath.mockClear();    webviewSend.mockClear();  });   effectIt.effect("keeps preview shortcuts out of the host window", () =>    withManager((manager) =>      Effect.gen(function* () {        const preview = makeFaviconWebContents();        const sendInputEvent = vi.fn();        const hostWebContents = { sendInputEvent };        Object.assign(preview.webContents, { hostWebContents });        fromId.mockReturnValue(preview.webContents);        yield* manager.setMainWindow({          isDestroyed: () => false,          once: vi.fn(),          webContents: hostWebContents,        } as never);        yield* manager.createTab("tab_keys");        yield* manager.registerWebview("tab_keys", 42);         expect(          (preview.webContents as Electron.WebContents).setIgnoreMenuShortcuts,        ).toHaveBeenCalledWith(true);        const beforeInput = preview.listeners.get("before-input-event")!;        for (const control of [false, true]) {          for (const key of ["k", ",", "w", "j", "q", "+", "a", "c", "v", "x"]) {

Lines 1–500 of 4052.