Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 179 additions & 0 deletions apps/desktop/src/preview/Manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1797,6 +1797,173 @@ describe("PreviewManager", () => {
),
);

effectIt.effect("keeps window unthrottled until the final frame capture stops", () =>
withManager((manager) =>
Effect.gen(function* () {
const setBackgroundThrottling = vi.fn();
const capturePage = vi.fn(async () => ({
toJPEG: () => Buffer.from("recording-frame"),
getSize: () => ({ width: 1280, height: 720 }),
}));
const webContentsById = new Map([
[41, makeTestPreviewWebContents(capturePage, 41)],
[42, makeTestPreviewWebContents(capturePage, 42)],
]);
fromId.mockImplementation((id) =>
id === undefined ? null : (webContentsById.get(id) ?? null),
);

yield* manager.createTab("tab_capture_throttling_1");
yield* manager.createTab("tab_capture_throttling_2");
yield* manager.registerWebview("tab_capture_throttling_1", 41);
yield* manager.registerWebview("tab_capture_throttling_2", 42);
yield* manager.setMainWindow({
isDestroyed: () => false,
once: vi.fn(),
webContents: { setBackgroundThrottling },
} as never);

yield* manager.startRecording("tab_capture_throttling_1");
yield* manager.startRecording("tab_capture_throttling_2");
expect(setBackgroundThrottling.mock.calls).toEqual([[false]]);

yield* manager.stopRecording("tab_capture_throttling_1");
expect(setBackgroundThrottling.mock.calls).toEqual([[false]]);

yield* manager.stopRecording("tab_capture_throttling_2");
expect(setBackgroundThrottling.mock.calls).toEqual([[false], [true]]);
}),
),
);

effectIt.effect("does not commit capture transitions when throttling updates fail", () =>
withManager((manager) =>
Effect.gen(function* () {
const setBackgroundThrottling = vi.fn<(enabled: boolean) => void>();
const capturePage = vi.fn(async () => ({
toJPEG: () => Buffer.from("recording-frame"),
getSize: () => ({ width: 1280, height: 720 }),
}));
fromId.mockReturnValue(makeTestPreviewWebContents(capturePage));

yield* manager.createTab("tab_capture_throttling_failure");
yield* manager.registerWebview("tab_capture_throttling_failure", 42);
yield* manager.setMainWindow({
isDestroyed: () => false,
once: vi.fn(),
webContents: { setBackgroundThrottling },
} as never);

setBackgroundThrottling.mockImplementationOnce(() => {
throw new Error("start throttling update failed");
});
const failedStart = yield* Effect.exit(
manager.startRecording("tab_capture_throttling_failure"),
);
expect(Exit.isFailure(failedStart)).toBe(true);

yield* manager.startRecording("tab_capture_throttling_failure");
expect(setBackgroundThrottling.mock.calls).toEqual([[false], [false]]);

setBackgroundThrottling.mockImplementationOnce(() => {
throw new Error("stop throttling update failed");
});
const failedStop = yield* Effect.exit(
manager.stopRecording("tab_capture_throttling_failure"),
);
expect(Exit.isFailure(failedStop)).toBe(true);

yield* manager.stopRecording("tab_capture_throttling_failure");
expect(setBackgroundThrottling.mock.calls).toEqual([[false], [false], [true], [true]]);
}),
),
);

effectIt.effect("does not publish a replacement window when capture reconciliation fails", () =>
withManager((manager) =>
Effect.gen(function* () {
const setBackgroundThrottling = vi.fn(() => {
throw new Error("replacement throttling update failed");
});
const capturePage = vi.fn(async () => ({
toJPEG: () => Buffer.from("recording-frame"),
getSize: () => ({ width: 1280, height: 720 }),
}));
fromId.mockReturnValue(makeTestPreviewWebContents(capturePage));

yield* manager.createTab("tab_capture_replacement_failure");
yield* manager.registerWebview("tab_capture_replacement_failure", 42);
yield* manager.startRecording("tab_capture_replacement_failure");

const failedReplacement = yield* Effect.exit(
manager.setMainWindow({
isDestroyed: () => false,
once: vi.fn(),
webContents: { setBackgroundThrottling },
} as never),
);
expect(Exit.isFailure(failedReplacement)).toBe(true);

yield* manager.stopRecording("tab_capture_replacement_failure");
expect(setBackgroundThrottling.mock.calls).toEqual([[false]]);
}),
),
);

effectIt.effect("releases frame capture when the main window closes", () =>
withManager((manager) =>
Effect.gen(function* () {
let closeMainWindow: (() => void) | undefined;
const firstWindowThrottling = vi.fn();
const replacementWindowThrottling = vi.fn();
const capturePage = vi.fn(async () => ({
toJPEG: () => Buffer.from("recording-frame"),
getSize: () => ({ width: 1280, height: 720 }),
}));
const webContentsById = new Map([
[42, makeTestPreviewWebContents(capturePage, 42)],
[43, makeTestPreviewWebContents(capturePage, 43)],
]);
fromId.mockImplementation((id) =>
id === undefined ? null : (webContentsById.get(id) ?? null),
);

yield* manager.createTab("tab_window_close_recording");
yield* manager.createTab("tab_window_close_race");
yield* manager.registerWebview("tab_window_close_recording", 42);
yield* manager.registerWebview("tab_window_close_race", 43);
yield* manager.setMainWindow({
isDestroyed: () => false,
once: vi.fn((event: string, listener: () => void) => {
if (event === "closed") closeMainWindow = listener;
}),
webContents: { setBackgroundThrottling: firstWindowThrottling },
} as never);
yield* manager.startRecording("tab_window_close_recording");
expect(firstWindowThrottling.mock.calls).toEqual([[false]]);

closeMainWindow?.();
const racedStart = yield* Effect.exit(manager.startRecording("tab_window_close_race"));
expect(Exit.isFailure(racedStart)).toBe(true);
if (Exit.isFailure(racedStart)) {
expect(Option.getOrThrow(Cause.findErrorOption(racedStart.cause))).toMatchObject({
_tag: "PreviewMainWindowClosedError",
tabId: "tab_window_close_race",
});
}
yield* Effect.yieldNow;
yield* Effect.yieldNow;

yield* manager.setMainWindow({
isDestroyed: () => false,
once: vi.fn(),
webContents: { setBackgroundThrottling: replacementWindowThrottling },
} as never);
expect(replacementWindowThrottling).not.toHaveBeenCalled();
}),
),
);

effectIt.effect("captures hidden preview recordings independently for concurrent tabs", () =>
withManager((manager) =>
Effect.gen(function* () {
Expand Down Expand Up @@ -2106,13 +2273,16 @@ describe("PreviewManager", () => {
effectIt.effect("shares background frame capture between recording and picture-in-picture", () =>
withManager((manager) =>
Effect.gen(function* () {
const setBackgroundThrottling = vi.fn();
const mainWindowWebContents = { setBackgroundThrottling };
const jpeg = Buffer.from("shared-preview-frame");
const capturePage = vi.fn(async () => ({
toJPEG: () => jpeg,
getSize: () => ({ width: 1280, height: 720 }),
}));
fromId.mockReturnValue({
id: 42,
hostWebContents: mainWindowWebContents,
isDestroyed: () => false,
getType: () => "webview",
getURL: () => "https://example.com",
Expand Down Expand Up @@ -2165,6 +2335,12 @@ describe("PreviewManager", () => {
const states: PreviewManager.PreviewTabState[] = [];
const recordingFrames: DesktopPreviewRecordingFrame[] = [];

yield* manager.setMainWindow({
isDestroyed: () => false,
once: vi.fn(),
webContents: mainWindowWebContents,
} as never);

yield* manager.subscribeStateChanges((_tabId, state) =>
Effect.sync(() => {
states.push(state);
Expand All @@ -2179,6 +2355,7 @@ describe("PreviewManager", () => {
yield* manager.registerWebview("tab_pip", 42);
yield* manager.openPictureInPicture("tab_pip");

expect(setBackgroundThrottling.mock.calls).toEqual([[false]]);
expect(browserWindowConstructor).toHaveBeenCalledWith(
expect.objectContaining({
alwaysOnTop: true,
Expand Down Expand Up @@ -2224,6 +2401,7 @@ describe("PreviewManager", () => {
expect(recordingFrames).toHaveLength(1);

yield* manager.stopRecording("tab_pip");
expect(setBackgroundThrottling.mock.calls).toEqual([[false]]);
const framesBeforePictureInPictureOnlyTick = pictureInPictureSend.mock.calls.length;
yield* TestClock.adjust(100);
expect(capturePage).toHaveBeenCalledTimes(3);
Expand All @@ -2233,6 +2411,7 @@ describe("PreviewManager", () => {
expect(recordingFrames).toHaveLength(1);

yield* manager.closePictureInPicture("tab_pip");
expect(setBackgroundThrottling.mock.calls).toEqual([[false], [true]]);
expect(pictureInPictureWindow.close).toHaveBeenCalledOnce();
expect(states.at(-1)?.pictureInPicture).toBe(false);
const capturesAfterClose = capturePage.mock.calls.length;
Expand Down
Loading
Loading