Keep browser input from submitting Paseo prompts (#1982)
* fix(desktop): keep browser input out of the composer Unhandled webview keys could be redispatched into the active host window, allowing agent Enter to submit a draft prompt. Give pages first refusal for ordinary shortcuts and contain automation at the guest boundary. * fix(desktop): tighten browser shortcut validation * fix(desktop): respect browser shortcut ownership * fix(desktop): retain browser keys across windows * fix(desktop): scope browser webviews by host * Reshape browser shortcut ownership Use the browser webview registry as the single guest identity authority, scope browser operations to their host window, publish chord continuations only while pending, and restore focus to the originating browser after command center dismissal. * Fix host-scoped browser shortcut follow-ups * Fix browser keyboard review follow-ups * Fix browser keyboard lifecycle regressions * Fix browser shortcut review regressions * Fix desktop browser review regressions * Fix browser shortcut frame regressions * Fix rebase integration regressions * Preserve browser-native shortcut ownership * Isolate browser shortcuts and automation by context
This commit is contained in:
@@ -18,6 +18,16 @@ export interface KeyboardShortcutContext {
|
||||
commandCenterOpen: boolean;
|
||||
}
|
||||
|
||||
export interface KeyboardShortcutInput {
|
||||
key: string;
|
||||
code: string;
|
||||
altKey: boolean;
|
||||
ctrlKey: boolean;
|
||||
metaKey: boolean;
|
||||
shiftKey: boolean;
|
||||
repeat: boolean;
|
||||
}
|
||||
|
||||
export interface KeyboardShortcutMatch {
|
||||
action: KeyboardActionId;
|
||||
payload: KeyboardShortcutPayload;
|
||||
@@ -1060,8 +1070,8 @@ export function buildEffectiveBindings(overrides: Record<string, string>): Parse
|
||||
|
||||
// --- Matching engine ---
|
||||
|
||||
function parseDigit(event: KeyboardEvent): number | null {
|
||||
const code = event.code ?? "";
|
||||
function parseDigit(event: KeyboardShortcutInput): number | null {
|
||||
const code = event.code;
|
||||
if (code.startsWith("Digit")) {
|
||||
const value = Number(code.slice("Digit".length));
|
||||
return Number.isFinite(value) && value >= 1 && value <= 9 ? value : null;
|
||||
@@ -1070,14 +1080,14 @@ function parseDigit(event: KeyboardEvent): number | null {
|
||||
const value = Number(code.slice("Numpad".length));
|
||||
return Number.isFinite(value) && value >= 1 && value <= 9 ? value : null;
|
||||
}
|
||||
const key = event.key ?? "";
|
||||
const key = event.key;
|
||||
if (key >= "1" && key <= "9") {
|
||||
return Number(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function matchesKeyOrCode(combo: KeyCombo, event: KeyboardEvent): boolean {
|
||||
function matchesKeyOrCode(combo: KeyCombo, event: KeyboardShortcutInput): boolean {
|
||||
if (combo.key === undefined) {
|
||||
return event.code === combo.code;
|
||||
}
|
||||
@@ -1095,7 +1105,7 @@ function matchesKeyOrCode(combo: KeyCombo, event: KeyboardEvent): boolean {
|
||||
return combo.codeFallback === true && event.code === combo.code;
|
||||
}
|
||||
|
||||
function matchesCombo(combo: KeyCombo, event: KeyboardEvent, isMac: boolean): boolean {
|
||||
function matchesCombo(combo: KeyCombo, event: KeyboardShortcutInput, isMac: boolean): boolean {
|
||||
if (combo.mod) {
|
||||
if (isMac) {
|
||||
if (!event.metaKey) return false;
|
||||
@@ -1118,7 +1128,10 @@ function matchesCombo(combo: KeyCombo, event: KeyboardEvent, isMac: boolean): bo
|
||||
return matchesKeyOrCode(combo, event);
|
||||
}
|
||||
|
||||
function matchesWhen(when: ShortcutWhen | undefined, context: KeyboardShortcutContext): boolean {
|
||||
export function matchesKeyboardShortcutContext(
|
||||
when: ShortcutWhen | undefined,
|
||||
context: KeyboardShortcutContext,
|
||||
): boolean {
|
||||
if (!when) return true;
|
||||
if (when.mac !== undefined && when.mac !== context.isMac) return false;
|
||||
if (when.desktop !== undefined && when.desktop !== context.isDesktop) return false;
|
||||
@@ -1136,7 +1149,7 @@ function matchesWhen(when: ShortcutWhen | undefined, context: KeyboardShortcutCo
|
||||
|
||||
function resolvePayload(
|
||||
def: ShortcutPayloadDef | undefined,
|
||||
event: KeyboardEvent,
|
||||
event: KeyboardShortcutInput,
|
||||
): KeyboardShortcutPayload {
|
||||
if (!def) return null;
|
||||
switch (def.type) {
|
||||
@@ -1187,7 +1200,7 @@ function helpMatchesPlatform(
|
||||
|
||||
function buildMatchFromBinding(
|
||||
binding: ParsedShortcutBinding,
|
||||
event: KeyboardEvent,
|
||||
event: KeyboardShortcutInput,
|
||||
): KeyboardShortcutMatch {
|
||||
return {
|
||||
action: binding.action,
|
||||
@@ -1198,7 +1211,7 @@ function buildMatchFromBinding(
|
||||
}
|
||||
|
||||
function resolveInitialChordStep(input: {
|
||||
event: KeyboardEvent;
|
||||
event: KeyboardShortcutInput;
|
||||
context: KeyboardShortcutContext;
|
||||
chordState: ChordState;
|
||||
onChordReset: () => void;
|
||||
@@ -1220,7 +1233,7 @@ function resolveInitialChordStep(input: {
|
||||
if (!matchesCombo(firstCombo, event, context.isMac)) {
|
||||
continue;
|
||||
}
|
||||
if (!matchesWhen(binding.when, context)) {
|
||||
if (!matchesKeyboardShortcutContext(binding.when, context)) {
|
||||
continue;
|
||||
}
|
||||
if (binding.parsedChord.length > 1) {
|
||||
@@ -1252,7 +1265,7 @@ function resolveInitialChordStep(input: {
|
||||
}
|
||||
|
||||
function resolveAdvancingChordStep(input: {
|
||||
event: KeyboardEvent;
|
||||
event: KeyboardShortcutInput;
|
||||
context: KeyboardShortcutContext;
|
||||
chordState: ChordState;
|
||||
onChordReset: () => void;
|
||||
@@ -1278,7 +1291,7 @@ function resolveAdvancingChordStep(input: {
|
||||
if (!matchesCombo(combo, event, context.isMac)) {
|
||||
continue;
|
||||
}
|
||||
if (!matchesWhen(binding.when, context)) {
|
||||
if (!matchesKeyboardShortcutContext(binding.when, context)) {
|
||||
continue;
|
||||
}
|
||||
if (chordState.step + 1 === binding.parsedChord.length) {
|
||||
@@ -1317,7 +1330,7 @@ function resolveAdvancingChordStep(input: {
|
||||
}
|
||||
|
||||
export function resolveKeyboardShortcut(input: {
|
||||
event: KeyboardEvent;
|
||||
event: KeyboardShortcutInput;
|
||||
context: KeyboardShortcutContext;
|
||||
chordState: ChordState;
|
||||
onChordReset: () => void;
|
||||
|
||||
Reference in New Issue
Block a user