Skip to content
Open
7 changes: 4 additions & 3 deletions apps/desktop/src/preview/Manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,10 @@ const isPreviewRefreshShortcut = (input: Electron.Input, platform: NodeJS.Platfo
!input.shift &&
!input.alt;

// Every guest of a focused window reports focus; GuestInput handles guests without a focused frame.
export const holdsKeyboardFocus = (contents: Electron.WebContents): boolean =>
contents.isFocused() && contents.focusedFrame !== null;

export const isPreviewEditingShortcut = (
input: Electron.Input,
platform: NodeJS.Platform,
Expand Down Expand Up @@ -1444,9 +1448,6 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function
}).pipe(Effect.ignore),
);
};
// Every guest of a focused window reports focus; GuestInput handles guests without a focused frame.
const holdsKeyboardFocus = (contents: Electron.WebContents) =>
contents.isFocused() && contents.focusedFrame !== null;
const syncMenuShortcuts = (contents: Electron.WebContents, input: Electron.Input): void => {
if (input.type !== "keyDown") return;
contents.setIgnoreMenuShortcuts(
Expand Down
58 changes: 51 additions & 7 deletions apps/desktop/src/window/DesktopWindow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ vi.mock("electron", async (importOriginal) => ({
},
]),
},
webContents: {
getAllWebContents: vi.fn(() => []),
},
}));

import * as DesktopAssets from "../app/DesktopAssets.ts";
Expand Down Expand Up @@ -1044,12 +1047,35 @@ describe("DesktopWindow", () => {
}),
);

it.effect("maps macOS horizontal swipes to available history navigation", () =>
it.effect("maps macOS horizontal swipes to the focused Browser tab or the app history", () =>
Effect.gen(function* () {
const fakeWindow = makeFakeBrowserWindow();
const createCount = yield* Ref.make(0);
const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
const layer = layerTest({ window: fakeWindow.window, createCount, mainWindow });
const guestHistory = {
canGoBack: vi.fn(() => true),
canGoForward: vi.fn(() => true),
goBack: vi.fn(),
goForward: vi.fn(),
};
const guest = {
hostWebContents: fakeWindow.window.webContents,
isDestroyed: () => false,
isFocused: () => true,
focusedFrame: {} as Electron.WebFrameMain | null,
navigationHistory: guestHistory,
};
const navigations = () => ({
app: [
fakeWindow.navigationHistory.goBack.mock.calls.length,
fakeWindow.navigationHistory.goForward.mock.calls.length,
],
browserTab: [
guestHistory.goBack.mock.calls.length,
guestHistory.goForward.mock.calls.length,
],
});

yield* Effect.gen(function* () {
const desktopWindow = yield* DesktopWindow.DesktopWindow;
Expand All @@ -1060,19 +1086,37 @@ describe("DesktopWindow", () => {
const history = fakeWindow.navigationHistory;

swipe({}, "left");
assert.equal(history.goBack.mock.calls.length, 1);
assert.equal(history.goForward.mock.calls.length, 0);
swipe({}, "right");
assert.equal(history.goForward.mock.calls.length, 1);

swipe({}, "up");
swipe({}, "down");
assert.deepEqual(navigations(), { app: [1, 1], browserTab: [0, 0] });

using _all = vi
.mocked(Electron.webContents.getAllWebContents)
.mockReturnValue([
fakeWindow.window.webContents,
guest as unknown as Electron.WebContents,
]);
swipe({}, "left");
swipe({}, "right");
assert.deepEqual(
navigations(),
{ app: [1, 1], browserTab: [1, 1] },
"swipe back/forward should navigate the focused Browser tab, not the app router",
);

// Every guest of a focused window reports isFocused(); focus back in the app clears its frame.
guest.focusedFrame = null;
history.canGoBack.mockReturnValue(false);
history.canGoForward.mockReturnValue(false);
swipe({}, "left");
swipe({}, "right");
assert.equal(history.goBack.mock.calls.length, 1);
assert.equal(history.goForward.mock.calls.length, 1);
assert.deepEqual(navigations(), { app: [1, 1], browserTab: [1, 1] });
history.canGoBack.mockReturnValue(true);
history.canGoForward.mockReturnValue(true);
swipe({}, "left");
swipe({}, "right");
assert.deepEqual(navigations(), { app: [2, 2], browserTab: [1, 1] });
}).pipe(Effect.provide(layer));
}),
);
Expand Down
10 changes: 9 additions & 1 deletion apps/desktop/src/window/DesktopWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -747,7 +747,15 @@ export const make = Effect.gen(function* () {
if (environment.platform === "darwin") {
// Mouse drivers can deliver Back/Forward as macOS swipe gestures instead of buttons.
window.on("swipe", (_event, direction) => {
const history = window.webContents.navigationHistory;
const focusedGuest = Electron.webContents
.getAllWebContents()
.find(
(contents) =>
contents.hostWebContents === window.webContents &&
!contents.isDestroyed() &&
PreviewManager.holdsKeyboardFocus(contents),
);
const history = (focusedGuest ?? window.webContents).navigationHistory;
if (direction === "left" && history.canGoBack()) history.goBack();
else if (direction === "right" && history.canGoForward()) history.goForward();
});
Expand Down
Loading