4333 lines · 161.1 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("isPreviewEditingShortcut", () => {  const input = (platform: NodeJS.Platform, key: string, overrides: Partial<Electron.Input> = {}) =>    ({      type: "keyDown",      key,      meta: platform === "darwin",      control: platform !== "darwin",      shift: false,      alt: false,      ...overrides,    }) as Electron.Input;   it.each(["darwin", "linux", "win32"] as const)(    "allows native editing chords on %s without allowing host shortcuts",    (platform) => {      for (const key of ["a", "c", "v", "x", "z", "V"]) {        expect(PreviewManager.isPreviewEditingShortcut(input(platform, key), platform)).toBe(true);      }      const redo =        platform === "win32" ? input(platform, "y") : input(platform, "z", { shift: true });      expect(PreviewManager.isPreviewEditingShortcut(redo, platform)).toBe(true);      expect(        PreviewManager.isPreviewEditingShortcut(          input(platform, "v", { shift: true, alt: platform === "darwin" }),          platform,        ),      ).toBe(true);       for (const key of ["k", ",", "w", "j", "q", "+", "=", "-", "0", "r", "F12"]) {        expect(PreviewManager.isPreviewEditingShortcut(input(platform, key), platform)).toBe(false);      }      for (const modifiers of [        { meta: false, control: false },        { meta: true, control: true },        { meta: platform !== "darwin", control: platform === "darwin" },        { alt: true },        { shift: true, alt: platform !== "darwin" },      ]) {        expect(          PreviewManager.isPreviewEditingShortcut(input(platform, "v", modifiers), platform),        ).toBe(false);      }      expect(        PreviewManager.isPreviewEditingShortcut(input(platform, "a", { shift: true }), platform),      ).toBe(false);    },  );   it("recognizes macOS Paste and Match Style when Option changes the key to a symbol", () => {    const pasteAndMatchStyle = input("darwin", "◊", { code: "KeyV", alt: true, shift: true });    expect(PreviewManager.isPreviewEditingShortcut(pasteAndMatchStyle, "darwin")).toBe(true);    for (const modifiers of [      { code: "KeyC" },      { alt: false },      { shift: false },      { control: true },    ]) {      expect(        PreviewManager.isPreviewEditingShortcut({ ...pasteAndMatchStyle, ...modifiers }, "darwin"),      ).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,  clipboardItemConstructor,  createFromPath,  fromId,  getFocusedWebContents,  mkdir,  showItemInFolder,  webviewSend,  writeFile,  writeClipboard,} = vi.hoisted(() => ({  browserWindowConstructor: vi.fn(),  clipboardItemConstructor: vi.fn(),  createFromPath: vi.fn((): { readonly isEmpty: () => boolean; readonly toPNG: () => Buffer } => ({    isEmpty: () => false,    toPNG: () => Buffer.from("png"),  })),  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),  writeClipboard: vi.fn(async () => undefined),})); vi.mock("electron", () => ({  BrowserWindow: browserWindowConstructor,  ClipboardItem: class {    constructor(data: Record<string, unknown>) {      clipboardItemConstructor(data);    }  },  clipboard: {    write: writeClipboard,  },  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>();

Lines 1–500 of 4333.