Z ZPI 文档 v0.3.8
介绍页

源码

packages/desktop/src/main/automation.ts

typescript175 行查看原文
import { readFileSync } from "node:fs";
import { isAbsolute, join } from "node:path";
import { app, BrowserWindow, clipboard, dialog, globalShortcut, Menu, Notification, shell } from "electron";

export const testMode = process.env.ZPI_TEST_MODE === "1";
export const hiddenTestMode = testMode && process.env.ZPI_TEST_INTERACTIVE !== "1";
// A versioned preflight marker also prevents launching old packaged apps without these guards.
export const automationContract = "zpi-desktop-isolation-v2";
const violations: string[] = [];
const windows = new Map<number, () => { visible: boolean; focused: boolean; focusable: boolean }>();
function forbidden(name: string): never {
  violations.push(name);
  throw new Error(`Desktop test blocked ${name}; use an explicit fake or the interactive suite`);
}

if (testMode) {
  Object.assign(globalThis, {
    __zpiAutomation: {
      contract: automationContract,
      violations,
      createWindow: createDesktopWindow,
      snapshot: () => [...windows.values()].map((read) => read()),
    },
  });
  const root = process.env.ZPI_TEST_DATA_DIR;
  if (
    !root ||
    !isAbsolute(root) ||
    readFileSync(join(root, ".zpi-test-owner"), "utf8") !== automationContract
  )
    throw new Error("Desktop tests must use the shared launcher and an owned data directory");
  app.setPath("userData", root);
  app.setPath("sessionData", join(root, "chromium"));
  app.commandLine.appendSwitch("mute-audio");
  app.commandLine.appendSwitch("use-mock-keychain");
  app.commandLine.appendSwitch("password-store", "basic");
  if (hiddenTestMode && process.platform === "darwin") app.setActivationPolicy("prohibited");

  // Install before app.whenReady(), renderer loading, or any application services.
  // Text stays inside this process. Other native formats fail closed.
  const text = new Map<string, string>();
  for (const key of Object.keys(clipboard)) {
    if (typeof clipboard[key as keyof typeof clipboard] === "function")
      Object.assign(clipboard, { [key]: () => forbidden(`clipboard.${key}`) });
  }
  clipboard.writeText = (value, type = "clipboard") => {
    text.set(type, value);
  };
  clipboard.readText = (type = "clipboard") => text.get(type) ?? "";
  clipboard.clear = (type = "clipboard") => {
    text.delete(type);
  };
  clipboard.availableFormats = () => ["text/plain"];
  app.getApplicationInfoForProtocol = async () => {
    throw new Error("No default application in desktop tests");
  };
  for (const key of ["openPath", "openExternal", "showItemInFolder", "beep", "trashItem"] as const)
    Object.assign(shell, { [key]: () => forbidden(`shell.${key}`) });
  for (const key of [
    "showOpenDialog",
    "showOpenDialogSync",
    "showSaveDialog",
    "showSaveDialogSync",
    "showMessageBox",
    "showMessageBoxSync",
    "showErrorBox",
    "showCertificateTrustDialog",
  ] as const)
    Object.assign(dialog, { [key]: () => forbidden(`dialog.${key}`) });
  // Notification delivery and sound bridge remain real; only OS presentation is intercepted.
  Notification.prototype.show = () => {};
  for (const key of ["register", "registerAll"] as const)
    Object.assign(globalShortcut, { [key]: () => forbidden(`globalShortcut.${key}`) });
  app.relaunch = () => forbidden("app.relaunch");
  Menu.prototype.popup = () => forbidden("Menu.popup");
  if (hiddenTestMode) {
    for (const key of ["show", "focus"] as const)
      Object.assign(app, { [key]: () => forbidden(`app.${key}`) });
    if (app.dock) {
      app.dock.show = async () => forbidden("dock.show");
      app.dock.bounce = () => forbidden("dock.bounce");
    }
  }
  app.on("session-created", (session) => {
    session.setPermissionRequestHandler((_, __, reply) => reply(false));
    session.setPermissionCheckHandler(() => false);
    session.on("will-download", (event) => event.preventDefault());
  });
  app.on("web-contents-created", (_, contents) => {
    contents.openDevTools = () => forbidden("webContents.openDevTools");
    for (const key of ["copy", "cut", "paste", "pasteAndMatchStyle"] as const)
      contents[key] = () => forbidden(`webContents.${key}`);
    contents.setWindowOpenHandler(() => ({ action: "deny" }));
    contents.on("before-input-event", (event, input) => {
      if ((input.meta || input.control) && ["c", "v", "x"].includes(input.key.toLowerCase()))
        event.preventDefault();
    });
    contents.on("select-bluetooth-device", (event, _, callback) => {
      event.preventDefault();
      callback("");
    });
  });
}

/** Every app/fixture BrowserWindow goes through this factory, including future child windows. */
export function createDesktopWindow(options: Electron.BrowserWindowConstructorOptions): BrowserWindow {
  const window = new BrowserWindow({
    ...options,
    ...(hiddenTestMode
      ? { show: false, focusable: false, skipTaskbar: true, modal: false, fullscreen: false, kiosk: false }
      : {}),
    webPreferences: {
      ...options.webPreferences,
      ...(testMode
        ? {
            devTools: false,
            disableDialogs: true,
            disableHtmlFullscreenWindowResize: true,
            additionalArguments: [
              ...(options.webPreferences?.additionalArguments ?? []),
              "--zpi-test-isolation",
            ],
          }
        : {}),
      ...(hiddenTestMode ? { backgroundThrottling: false } : {}),
    },
  });
  if (testMode) {
    const visible = window.isVisible.bind(window);
    const focused = window.isFocused.bind(window);
    const focusable = window.isFocusable.bind(window);
    windows.set(window.id, () => ({ visible: visible(), focused: focused(), focusable: focusable() }));
    window.once("closed", () => windows.delete(window.id));
  }
  if (hiddenTestMode) {
    for (const key of [
      "show",
      "showInactive",
      "focus",
      "restore",
      "minimize",
      "maximize",
      "moveTop",
      "previewFile",
    ] as const)
      window[key] = () => forbidden(`window.${key}`);
    const setEnabled = window.setEnabled.bind(window);
    window.setEnabled = (value) => {
      if (!value) forbidden("window.setEnabled(false)");
      setEnabled(true);
    };
    const setFocusable = window.setFocusable.bind(window);
    window.setFocusable = (value) => {
      if (value) forbidden("window.setFocusable");
      setFocusable(false);
    };
    for (const key of ["setFullScreen", "setSimpleFullScreen", "setKiosk"] as const)
      window[key] = (value: boolean) => {
        if (value) forbidden(`window.${key}`);
      };
    window.flashFrame = (value) => {
      if (value) forbidden("window.flashFrame");
    };
    window.on("show", () => {
      window.hide();
      forbidden("window show event");
    });
    window.on("focus", () => {
      window.blur();
      forbidden("window focus event");
    });
  }
  return window;
}