fix(app): forward ⌘L from a focused browser page to the address field (#57965)

This commit is contained in:
Kit Langton
2026-10-21 19:30:47 -07:00
committed by GitHub
Unverified
parent f6958ddb38
commit e8f0b3e7bf
13 changed files with 49 additions and 66 deletions
@@ -429,6 +429,45 @@ story("⌘T and ⌘W follow focus: side tabs inside the side panel, session tabs
await expect(sessions).toHaveText("1")
})
story("⌘L selects the browser address from the page and the pane, and nowhere else", async ({ page }) => {
const root = page.getByTestId("browser-region-fixture")
const mod = (await page.evaluate(() => /Mac|iPhone|iPad|iPod/.test(navigator.platform))) ? "Meta" : "Control"
const outside = root.getByRole("button", { name: "Beta", exact: true })
const address = root.getByRole("combobox", { name: "Browser address", exact: true })
const editor = page.frameLocator('iframe[title="Browser page"]').getByRole("textbox", { name: "Editor page input" })
const selected = () =>
address.evaluate(
(input: HTMLInputElement) =>
document.activeElement === input &&
input.value.length > 0 &&
input.selectionStart === 0 &&
input.selectionEnd === input.value.length,
)
await expect(root.getByText("Registrations: 1", { exact: true })).toBeVisible()
await outside.click()
await root.getByRole("button", { name: "First inventory", exact: true }).click()
await expect(root.getByTestId("selected")).toHaveText(/^browser:/)
// In the main column ⌘L does not reach into the side panel.
await outside.focus()
await outside.press(`${mod}+l`)
await expect(address).not.toBeFocused()
// A focused page hands ⌘L to the pane, as it does ⌘T and ⌘W, without typing it.
await editor.click()
await editor.press(`${mod}+l`)
await expect.poll(selected).toBe(true)
await expect(editor).not.toHaveValue(/l$/)
// On the pane's own surface too.
await page.locator('#browser-panel [data-component="browser-bar"]').click({ position: { x: 2, y: 2 } })
await expect(address).not.toBeFocused()
await page.keyboard.press(`${mod}+l`)
await expect.poll(selected).toBe(true)
})
story("keeps ⌘T in the browser pane after a click on its loading surface", async ({ page }) => {
const root = page.getByTestId("browser-region-fixture")
const mod = (await page.evaluate(() => /Mac|iPhone|iPad|iPod/.test(navigator.platform))) ? "Meta" : "Control"
@@ -535,14 +535,6 @@ story("adds the whole page to the session and keeps the draft when the button is
await expect(root.getByTestId("fixture-comments").getByRole("listitem")).toHaveText([/: Check the header layout$/])
})
story("focuses the address field when the page uses the address shortcut", async ({ page }) => {
const root = page.getByTestId("browser-pane-fixture")
await root.getByRole("button", { name: "Address shortcut", exact: true }).click()
const address = root.getByRole("combobox", { name: "Browser address", exact: true })
await expect(address).toBeFocused()
await expect(address).toHaveJSProperty("selectionEnd", "https://alpha.example/".length)
})
story("shows the page's cookies in the site information and clears them", async ({ page }) => {
const root = page.getByTestId("browser-pane-fixture")
await root.getByRole("button", { name: "Site information", exact: true }).click()
@@ -136,8 +136,6 @@ export function createBrowserPage(
detail?: (detail: PageDetail) => void
/** The page's zoom changed, which Chromium applies to every page of the same origin in the partition. */
zoomed?: () => void
/** The user pressed the address shortcut while the page had focus. */
address?: () => void
/** Hands a browser or shell shortcut to the app window. */
shortcut?: (shortcut: PageShortcut) => void
/** The page took keyboard focus. */
@@ -214,14 +212,6 @@ export function createBrowserPage(
return
}
// The system browser's address shortcut. The app's own focus moves to the address field.
if (!input.shift && pageLetter(input) === "l") {
event.preventDefault()
options.address?.()
return
}
const direction = zoomKey(input)
if (!direction) return
@@ -110,7 +110,6 @@ function fixture(strip: string[] = []) {
preview: (path) => routed.preview.push(path),
inspect: (event) => routed.inspect.push(event),
page: () => undefined,
address: () => undefined,
shortcut: () => undefined,
focused: () => undefined,
})
@@ -58,8 +58,6 @@ export function createConnection(input: {
inspect: (event: InspectEvent) => void
/** A page's icon or zoom changed. */
page: (event: PageEvent) => void
/** The user pressed the address shortcut while the page had focus. */
address: (tabID: Browser.TabID) => void
/** A focused page handed a browser or shell shortcut to the window. */
shortcut: (event: ShortcutEvent) => void
/** The user put keyboard focus in a page. */
@@ -162,8 +160,6 @@ export function createConnection(input: {
if (event.type === "page") return input.page(event)
if (event.type === "address") return input.address(event.tabID)
if (event.type === "shortcut") return input.shortcut(event)
if (event.type === "focused") return input.focused()
@@ -50,8 +50,6 @@ export const PaneEvent = Schema.Union([
icon: Schema.optionalKey(detail(MAX_ICON_URL).check(Schema.isStartingWith("data:image/"))),
zoom: Schema.Finite.check(Schema.isBetween({ minimum: 0.25, maximum: 5 })),
}),
// The page asked for the address field, with the platform's address shortcut.
Schema.Struct({ type: Schema.Literal("address"), tabID: Browser.TabID }),
// The user put keyboard focus in the page, outside the window's own document.
Schema.Struct({ type: Schema.Literal("focused"), tabID: Browser.TabID }),
// The page handed a browser or shell shortcut to the window, which runs it as its own key press.
@@ -45,6 +45,10 @@ test("forwards browser and shell shortcuts while leaving page editing, find, rel
["mod", "w", true, "w"],
["mod", "w", false, "w"],
["mod", "\u0432", true, "w", "KeyW"],
// The address field, as in a browser.
["mod", "l", true, "l"],
["mod", "l", false, "l"],
["mod", "l", true, "l", "KeyP"],
["mod", "n", true, "n"],
["mod", "n", false, "n"],
["mod", "1", true, "1"],
@@ -98,7 +102,7 @@ test("forwards browser and shell shortcuts while leaving page editing, find, rel
["mod", "a", false, undefined],
["mod", "z", false, undefined],
["mod", "y", false, undefined],
// Find, reload, history, and the pane's own address, picker and zoom chords.
// Find, reload, history, and the pane's own picker and zoom chords.
["mod", "f", true, undefined],
["mod", "g", true, undefined],
["mod+shift", "G", true, undefined],
@@ -106,7 +110,6 @@ test("forwards browser and shell shortcuts while leaving page editing, find, rel
["mod", "r", false, undefined],
["mod", "[", true, undefined],
["mod", "]", true, undefined],
["mod", "l", true, undefined],
["mod+shift", "C", true, undefined],
["mod", "=", true, undefined],
["mod", "-", true, undefined],
+2 -2
View File
@@ -11,8 +11,8 @@ export type PageShortcut = {
}
// Unshifted chords with the platform's modifier that belong to the app shell or browser pane rather than the page:
// opening, closing or switching tabs, opening settings, and toggling the file tree.
const shellKeys = ["t", "w", "n", "1", "2", "3", "4", "5", "6", "7", "8", "9", ",", "\\"]
// opening, closing or switching tabs, focusing the address field, opening settings, and toggling the file tree.
const shellKeys = ["t", "w", "l", "n", "1", "2", "3", "4", "5", "6", "7", "8", "9", ",", "\\"]
// Shifted chords with the platform's modifier that belong to the app shell rather than the page: reopening a tab,
// switching side-panel tabs, toggling the side panel, opening the command palette, or running session commands. New Window and Close Window
@@ -99,7 +99,6 @@ export function createModel(ctx: SetupContext<typeof definition>) {
const live = new Map<string, Live>()
const listeners = new Map<string, (event: PaneEvent) => void>()
const inspectors = new Map<string, Set<(event: InspectEvent) => void>>()
const addressed = new Map<string, Set<(tabID: Browser.TabID) => void>>()
// Sessions where the user just opened a tab; its pane focuses the address field once the blank page shows.
const opening = new Set<string>()
// Each tab's last recorded page, so a state that repeats it does not write the history again.
@@ -218,7 +217,6 @@ export function createModel(ctx: SetupContext<typeof definition>) {
if (visits) history.set({ visits })
},
address: (tabID) => addressed.get(id)?.forEach((listener) => listener(tabID)),
// Only the pane on screen holds the page that had focus.
shortcut: (event) => {
if (sessions.current()?.key === id) panes()[0]?.press(event)
@@ -498,18 +496,6 @@ export function createModel(ctx: SetupContext<typeof definition>) {
recorded.clear()
history.set({ visits: [] })
},
/** The page asking for the address field with its shortcut. */
onAddress(session: Session, listener: (tabID: Browser.TabID) => void) {
const set = addressed.get(session.key) ?? new Set()
set.add(listener)
addressed.set(session.key, set)
return () => {
set.delete(listener)
if (!set.size) addressed.delete(session.key)
}
},
pending,
/**
* Tab IDs to list in the side panel: the desktop's inventory, limited to tabs stored in the strip. Until the first
+1 -7
View File
@@ -504,16 +504,10 @@ export function createBrowserPane(input: {
if (entry.pages.has(id)) report(entry, { type: "page", tabID: id, ...value })
},
zoomed: () => entry.pages.forEach((page) => page.detail()),
address: () => {
if (!entry.pages.has(id) || entry.win.isDestroyed()) return
// The page held keyboard focus; the app's address field needs it back.
entry.win.webContents.focus()
publish(entry, { type: "address", tabID: id })
},
shortcut: (shortcut) => {
if (!entry.pages.has(id) || entry.focusedTabID !== id || entry.win.isDestroyed()) return
// As for the address shortcut: the command runs in the app, which may move focus there.
// The page held keyboard focus; the command runs in the app, which may move focus there, as ⌘L does.
if (entry.win.isFocused()) entry.win.webContents.focus()
publish(entry, { type: "shortcut", ...shortcut })
},
@@ -495,9 +495,6 @@ export function mountBrowserPane(input: PaneHost) {
<button onClick={() => emit(store.session, { type: "page", tabID: current().id, icon, zoom: store.zoom })}>
Page icon
</button>
<button onClick={() => emit(store.session, { type: "address", tabID: current().id })}>
Address shortcut
</button>
</nav>
<p>Captures: {store.captures}</p>
<p>Zoom: {Math.round(store.zoom * 100)}</p>
@@ -451,17 +451,6 @@ export default function SessionBrowserPane(props: {
),
)
// The page's address shortcut moves focus to this field when the page is the one on screen.
createKeyed(
() => props.session.key,
(sessionKey) =>
onCleanup(
props.model.onAddress({ key: sessionKey }, (tabID) => {
if (tabID === state()?.id && visible()) focusAddress()
}),
),
)
// A tab the user just opened starts in its address field, once its blank page is on screen. Syncs DOM focus.
createKeyed(
() => (visible() && empty() ? state()?.id : undefined),
@@ -92,8 +92,8 @@ const setup: Setup<typeof definition> = (ctx) => {
}
})
// 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.
// The system browser's address shortcut, in the pane and, forwarded like mod+t and mod+w, from a page with focus.
// Ctrl+L focuses the composer outside the pane on Windows and Linux.
ctx.add(Command, (): Command | undefined => {
const pane = model()?.pane()