Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
36 changes: 36 additions & 0 deletions packages/app/component-tests/browser-pane-restore.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,42 @@ story("opens a browser tab with ⌘T inside the pane and a session everywhere el
await expect(sessions).toHaveText("1")
})

story("reloads the browser tab with ⌘R inside the pane and leaves it to the window elsewhere", async ({ page }) => {
const root = page.getByTestId("browser-region-fixture")
const mac = await page.evaluate(() => /Mac|iPhone|iPad|iPod/.test(navigator.platform))
const outside = root.getByRole("button", { name: "Beta", exact: true })
const address = root.getByRole("combobox", { name: "Browser address", exact: true })
const reloads = root.getByTestId("browser-reloads")
await expect(root.getByText("Registrations: 1", { exact: true })).toBeVisible()
await outside.click()
await expect(root.getByText("Registrations: 2", { exact: true })).toBeVisible()
await root.getByRole("button", { name: "First inventory", exact: true }).click()
await expect(root.getByRole("tab", { name: "Preview", exact: true })).toHaveAttribute("aria-selected", "true")

// Returns whether the chord stays unhandled, so it reaches the window's Reload menu item.
const press = (target: typeof outside) =>
target.evaluate(
(element, meta) =>
element.dispatchEvent(
new KeyboardEvent("keydown", {
key: "r",
code: "KeyR",
metaKey: meta,
ctrlKey: !meta,
bubbles: true,
cancelable: true,
}),
),
mac,
)

expect(await press(outside)).toBe(true)
await expect(reloads).toHaveText("0")

expect(await press(address)).toBe(false)
await expect(reloads).toHaveText("1")
})

story("reopens a closed browser tab with ⌘⇧T after the side panel and a session tab elsewhere", async ({ page }) => {
const root = page.getByTestId("browser-region-fixture")
const mac = await page.evaluate(() => /Mac|iPhone|iPad|iPod/.test(navigator.platform))
Expand Down
4 changes: 2 additions & 2 deletions packages/gui-extensions/src/browser/chromium.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { createCdp, abortError, waitFor } from "./cdp"
import { createBrowserFiles } from "./files"
import { createDiagnostics } from "./diagnostics"
import { loadIcon } from "./icon"
import { pageLetter, pageShortcut, type PageShortcut } from "./keys"
import { pageLetter, pageReload, pageShortcut, type PageShortcut } from "./keys"
import { createProfiling, type Recording } from "./profiling"
import type { BrowserNetwork } from "./network"
import {
Expand Down Expand Up @@ -178,7 +178,7 @@ export function createBrowserPage(
contents.on("before-input-event", (event, input) => {
if (input.type !== "keyDown") return

if (input.key === "F5" && !input.meta && !input.control && !input.alt && !input.shift) {
if (pageReload(input, process.platform === "darwin")) {
event.preventDefault()
contents.reload()

Expand Down
30 changes: 29 additions & 1 deletion packages/gui-extensions/src/browser/keys.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect, test } from "bun:test"
import { pageLetter, pageShortcut, type PageKey } from "./keys"
import { pageLetter, pageReload, pageShortcut, type PageKey } from "./keys"

/** A key press with `mod` as the platform's modifier: Command on macOS, Control elsewhere. */
const press = (modifiers: string, key: string, mac: boolean, code = ""): PageKey => {
Expand Down Expand Up @@ -121,3 +121,31 @@ test("forwards browser and shell shortcuts while leaving page editing, find, rel

expect(results).toEqual(cases.map(([modifiers, key, mac, forwarded]) => [modifiers, key, mac, forwarded]))
})

test("reloads the focused page on F5 and the platform's reload chord, leaving other R chords to the window", () => {
const cases: [modifiers: string, key: string, mac: boolean, reloads: boolean, code?: string][] = [
["", "F5", true, true],
["", "F5", false, true],
["mod", "r", true, true],
["mod", "r", false, true],
// Dvorak: R is on the physical O key; the character decides.
["mod", "r", true, true, "KeyO"],
["mod", "\u043a", true, true, "KeyR"],
// ⇧⌘R toggles review, and macOS Control+R is the page's own.
["mod+shift", "R", true, false],
["mod+shift", "R", false, false],
["mod+alt", "r", true, false],
["ctrl", "r", true, false],
["mod", "F5", true, false],
["", "r", true, false],
]

const results = cases.map(([modifiers, key, mac, , code]) => [
modifiers,
key,
mac,
pageReload(press(modifiers, key, mac, code), mac),
])

expect(results).toEqual(cases.map(([modifiers, key, mac, reloads]) => [modifiers, key, mac, reloads]))
})
14 changes: 13 additions & 1 deletion packages/gui-extensions/src/browser/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,22 @@ export function pageLetter(input: Pick<Electron.Input, "key" | "code">) {
return input.code.slice(3).toLowerCase()
}

/**
* Whether a page key press is the system browser's reload: F5, or the platform's modifier with R. The focused page
* reloads itself; left to the window, the Reload menu item would reload the whole app.
*/
export function pageReload(input: PageKey, mac: boolean) {
if (input.alt || input.shift) return false

if (input.key === "F5") return !input.meta && !input.control

return (mac ? input.meta && !input.control : input.control && !input.meta) && pageLetter(input) === "r"
}

/**
* Returns the shortcut to forward to the app window when a focused page receives a key press that belongs to the
* browser pane or app shell, or `undefined` when the key belongs to the page (typing, editing, clipboard, find,
* reload, history, or in-page shortcuts like ⌘K and ⌘B).
* history, or in-page shortcuts like ⌘K and ⌘B).
*/
export function pageShortcut(input: PageKey, mac: boolean): PageShortcut | undefined {
const key = pageLetter(input)
Expand Down
6 changes: 6 additions & 0 deletions packages/gui-extensions/src/browser/panel.fixture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,8 @@ type RegionFixtureState = {
sessions: number
/** How many browser tabs the pane asked main to open. */
opens: number
/** How many page reloads the pane asked main for. */
reloads: number
reopens: number
switches: number
/** The page main reports, blank or loaded. */
Expand Down Expand Up @@ -678,6 +680,7 @@ export function mountBrowserRegion(input: RegionHost) {
opened: true,
sessions: 0,
opens: 0,
reloads: 0,
reopens: 0,
switches: 0,
blank: false,
Expand Down Expand Up @@ -849,6 +852,8 @@ export function mountBrowserRegion(input: RegionHost) {
command: async (value) => {
if (value.command.type === "tabs.open") setStore("opens", (count) => count + 1)

if (value.command.type === "reload") setStore("reloads", (count) => count + 1)

if (value.command.type !== "tabs.close") return

const tabID = value.command.tabID
Expand Down Expand Up @@ -1099,6 +1104,7 @@ export function mountBrowserRegion(input: RegionHost) {
<textarea aria-label="Composer" />
<p data-testid="new-sessions">{store.sessions}</p>
<p data-testid="browser-opens">{store.opens}</p>
<p data-testid="browser-reloads">{store.reloads}</p>
<p data-testid="session-reopens">{store.reopens}</p>
<p data-testid="session-switches">{store.switches}</p>
<p data-testid="add-shortcut">{shortcut()() || "none"}</p>
Expand Down
20 changes: 20 additions & 0 deletions packages/gui-extensions/src/browser/renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,26 @@ const setup: Setup<typeof definition> = (ctx) => {
}
})

// The system browser's reload shortcut. While the page has focus, the page claims it itself; here it covers focus
// elsewhere in the pane. Everywhere else mod+r keeps the window's Reload.
ctx.add(Command, (): Command | undefined => {
const pane = model()?.pane()

if (!pane) return undefined

return {
id: "reloadPage",
title: ctx.t("command.reload"),
group: ctx.t("command.category.view"),
bind: "mod+r",
scope: "#browser-panel",
hidden: true,
editable: true,
enabled: pane.visible(),
run: pane.reload,
}
})

// The system browser's address shortcut. While the page has focus, the page claims it and main forwards it; here it
// covers focus elsewhere in the pane. Ctrl+L focuses the composer outside the pane on Windows and Linux.
ctx.add(Command, (): Command | undefined => {
Expand Down
Loading