chore(bidi): use original key name when computing bidi value

Use orignal key name from the API, to not lose information due to double mapping.
We previously switched to using code for pw -> bidi mapping(https://github.com/microsoft/playwright/pull/34246), but it does not work for `H` and `h`:

```
  ControlLeft => code:ControlLeft key:Control
  ControlRight => code:ControlRight key:Control
  H => code:KeyH key:H
  h => code:KeyH key:h
```
This commit is contained in:
Yury Semikhatsky 2025-01-13 16:07:26 -08:00
parent 454b6f938d
commit ef4b4043c3
6 changed files with 36 additions and 31 deletions

View file

@ -31,16 +31,15 @@ export class RawKeyboardImpl implements input.RawKeyboard {
this._session = session; this._session = session;
} }
async keydown(modifiers: Set<types.KeyboardModifier>, code: string, keyCode: number, keyCodeWithoutLocation: number, key: string, location: number, autoRepeat: boolean, text: string | undefined): Promise<void> { async keydown(modifiers: Set<types.KeyboardModifier>, keyName: string, description: input.KeyDescription, autoRepeat: boolean): Promise<void> {
const actions: bidi.Input.KeySourceAction[] = []; const actions: bidi.Input.KeySourceAction[] = [];
actions.push({ type: 'keyDown', value: getBidiKeyValue(code) }); actions.push({ type: 'keyDown', value: getBidiKeyValue(keyName) });
// TODO: add modifiers?
await this._performActions(actions); await this._performActions(actions);
} }
async keyup(modifiers: Set<types.KeyboardModifier>, code: string, keyCode: number, keyCodeWithoutLocation: number, key: string, location: number): Promise<void> { async keyup(modifiers: Set<types.KeyboardModifier>, keyName: string, description: input.KeyDescription): Promise<void> {
const actions: bidi.Input.KeySourceAction[] = []; const actions: bidi.Input.KeySourceAction[] = [];
actions.push({ type: 'keyUp', value: getBidiKeyValue(code) }); actions.push({ type: 'keyUp', value: getBidiKeyValue(keyName) });
await this._performActions(actions); await this._performActions(actions);
} }

View file

@ -7,18 +7,18 @@
/* eslint-disable curly */ /* eslint-disable curly */
export const getBidiKeyValue = (code: string) => { export const getBidiKeyValue = (keyName: string) => {
switch (code) { switch (keyName) {
case '\r': case '\r':
case '\n': case '\n':
code = 'Enter'; keyName = 'Enter';
break; break;
} }
// Measures the number of code points rather than UTF-16 code units. // Measures the number of code points rather than UTF-16 code units.
if ([...code].length === 1) { if ([...keyName].length === 1) {
return code; return keyName;
} }
switch (code) { switch (keyName) {
case 'Cancel': case 'Cancel':
return '\uE001'; return '\uE001';
case 'Help': case 'Help':
@ -228,6 +228,6 @@ export const getBidiKeyValue = (code: string) => {
case 'Quote': case 'Quote':
return '"'; return '"';
default: default:
throw new Error(`Unknown key: "${code}"`); throw new Error(`Unknown key: "${keyName}"`);
} }
}; };

View file

@ -50,14 +50,15 @@ export class RawKeyboardImpl implements input.RawKeyboard {
return commands.map(c => c.substring(0, c.length - 1)); return commands.map(c => c.substring(0, c.length - 1));
} }
async keydown(modifiers: Set<types.KeyboardModifier>, code: string, keyCode: number, keyCodeWithoutLocation: number, key: string, location: number, autoRepeat: boolean, text: string | undefined): Promise<void> { async keydown(modifiers: Set<types.KeyboardModifier>, keyName: string, description: input.KeyDescription, autoRepeat: boolean): Promise<void> {
const { code, key, location, text } = description;
if (code === 'Escape' && await this._dragManger.cancelDrag()) if (code === 'Escape' && await this._dragManger.cancelDrag())
return; return;
const commands = this._commandsForCode(code, modifiers); const commands = this._commandsForCode(code, modifiers);
await this._client.send('Input.dispatchKeyEvent', { await this._client.send('Input.dispatchKeyEvent', {
type: text ? 'keyDown' : 'rawKeyDown', type: text ? 'keyDown' : 'rawKeyDown',
modifiers: toModifiersMask(modifiers), modifiers: toModifiersMask(modifiers),
windowsVirtualKeyCode: keyCodeWithoutLocation, windowsVirtualKeyCode: description.keyCodeWithoutLocation,
code, code,
commands, commands,
key, key,
@ -69,12 +70,13 @@ export class RawKeyboardImpl implements input.RawKeyboard {
}); });
} }
async keyup(modifiers: Set<types.KeyboardModifier>, code: string, keyCode: number, keyCodeWithoutLocation: number, key: string, location: number): Promise<void> { async keyup(modifiers: Set<types.KeyboardModifier>, keyName: string, description: input.KeyDescription): Promise<void> {
const { code, key, location } = description;
await this._client.send('Input.dispatchKeyEvent', { await this._client.send('Input.dispatchKeyEvent', {
type: 'keyUp', type: 'keyUp',
modifiers: toModifiersMask(modifiers), modifiers: toModifiersMask(modifiers),
key, key,
windowsVirtualKeyCode: keyCodeWithoutLocation, windowsVirtualKeyCode: description.keyCodeWithoutLocation,
code, code,
location location
}); });

View file

@ -61,13 +61,15 @@ export class RawKeyboardImpl implements input.RawKeyboard {
this._client = client; this._client = client;
} }
async keydown(modifiers: Set<types.KeyboardModifier>, code: string, keyCode: number, keyCodeWithoutLocation: number, key: string, location: number, autoRepeat: boolean, text: string | undefined): Promise<void> { async keydown(modifiers: Set<types.KeyboardModifier>, keyName: string, description: input.KeyDescription, autoRepeat: boolean): Promise<void> {
let text = description.text;
// Firefox will figure out Enter by itself // Firefox will figure out Enter by itself
if (text === '\r') if (text === '\r')
text = ''; text = '';
const { code, key, location } = description;
await this._client.send('Page.dispatchKeyEvent', { await this._client.send('Page.dispatchKeyEvent', {
type: 'keydown', type: 'keydown',
keyCode: keyCodeWithoutLocation, keyCode: description.keyCodeWithoutLocation,
code, code,
key, key,
repeat: autoRepeat, repeat: autoRepeat,
@ -76,11 +78,12 @@ export class RawKeyboardImpl implements input.RawKeyboard {
}); });
} }
async keyup(modifiers: Set<types.KeyboardModifier>, code: string, keyCode: number, keyCodeWithoutLocation: number, key: string, location: number): Promise<void> { async keyup(modifiers: Set<types.KeyboardModifier>, keyName: string, description: input.KeyDescription): Promise<void> {
const { code, key, location } = description;
await this._client.send('Page.dispatchKeyEvent', { await this._client.send('Page.dispatchKeyEvent', {
type: 'keyup', type: 'keyup',
key, key,
keyCode: keyCodeWithoutLocation, keyCode: description.keyCodeWithoutLocation,
code, code,
location, location,
repeat: false repeat: false

View file

@ -22,7 +22,7 @@ import type { CallMetadata } from './instrumentation';
export const keypadLocation = keyboardLayout.keypadLocation; export const keypadLocation = keyboardLayout.keypadLocation;
type KeyDescription = { export type KeyDescription = {
keyCode: number, keyCode: number,
keyCodeWithoutLocation: number, keyCodeWithoutLocation: number,
key: string, key: string,
@ -35,8 +35,8 @@ type KeyDescription = {
const kModifiers: types.KeyboardModifier[] = ['Alt', 'Control', 'Meta', 'Shift']; const kModifiers: types.KeyboardModifier[] = ['Alt', 'Control', 'Meta', 'Shift'];
export interface RawKeyboard { export interface RawKeyboard {
keydown(modifiers: Set<types.KeyboardModifier>, code: string, keyCode: number, keyCodeWithoutLocation: number, key: string, location: number, autoRepeat: boolean, text: string | undefined): Promise<void>; keydown(modifiers: Set<types.KeyboardModifier>, keyName: string, description: KeyDescription, autoRepeat: boolean): Promise<void>;
keyup(modifiers: Set<types.KeyboardModifier>, code: string, keyCode: number, keyCodeWithoutLocation: number, key: string, location: number): Promise<void>; keyup(modifiers: Set<types.KeyboardModifier>, keyName: string, description: KeyDescription): Promise<void>;
sendText(text: string): Promise<void>; sendText(text: string): Promise<void>;
} }
@ -55,8 +55,7 @@ export class Keyboard {
this._pressedKeys.add(description.code); this._pressedKeys.add(description.code);
if (kModifiers.includes(description.key as types.KeyboardModifier)) if (kModifiers.includes(description.key as types.KeyboardModifier))
this._pressedModifiers.add(description.key as types.KeyboardModifier); this._pressedModifiers.add(description.key as types.KeyboardModifier);
const text = description.text; await this._raw.keydown(this._pressedModifiers, key, description, autoRepeat);
await this._raw.keydown(this._pressedModifiers, description.code, description.keyCode, description.keyCodeWithoutLocation, description.key, description.location, autoRepeat, text);
} }
private _keyDescriptionForString(str: string): KeyDescription { private _keyDescriptionForString(str: string): KeyDescription {
@ -77,7 +76,7 @@ export class Keyboard {
if (kModifiers.includes(description.key as types.KeyboardModifier)) if (kModifiers.includes(description.key as types.KeyboardModifier))
this._pressedModifiers.delete(description.key as types.KeyboardModifier); this._pressedModifiers.delete(description.key as types.KeyboardModifier);
this._pressedKeys.delete(description.code); this._pressedKeys.delete(description.code);
await this._raw.keyup(this._pressedModifiers, description.code, description.keyCode, description.keyCodeWithoutLocation, description.key, description.location); await this._raw.keyup(this._pressedModifiers, key, description);
} }
async insertText(text: string) { async insertText(text: string) {

View file

@ -59,12 +59,13 @@ export class RawKeyboardImpl implements input.RawKeyboard {
this._session = session; this._session = session;
} }
async keydown(modifiers: Set<types.KeyboardModifier>, code: string, keyCode: number, keyCodeWithoutLocation: number, key: string, location: number, autoRepeat: boolean, text: string | undefined): Promise<void> { async keydown(modifiers: Set<types.KeyboardModifier>, keyName: string, description: input.KeyDescription, autoRepeat: boolean): Promise<void> {
const parts = []; const parts = [];
for (const modifier of (['Shift', 'Control', 'Alt', 'Meta']) as types.KeyboardModifier[]) { for (const modifier of (['Shift', 'Control', 'Alt', 'Meta']) as types.KeyboardModifier[]) {
if (modifiers.has(modifier)) if (modifiers.has(modifier))
parts.push(modifier); parts.push(modifier);
} }
const { code, keyCode, key, text } = description;
parts.push(code); parts.push(code);
const shortcut = parts.join('+'); const shortcut = parts.join('+');
let commands = macEditingCommands[shortcut]; let commands = macEditingCommands[shortcut];
@ -80,18 +81,19 @@ export class RawKeyboardImpl implements input.RawKeyboard {
unmodifiedText: text, unmodifiedText: text,
autoRepeat, autoRepeat,
macCommands: commands, macCommands: commands,
isKeypad: location === input.keypadLocation isKeypad: description.location === input.keypadLocation
}); });
} }
async keyup(modifiers: Set<types.KeyboardModifier>, code: string, keyCode: number, keyCodeWithoutLocation: number, key: string, location: number): Promise<void> { async keyup(modifiers: Set<types.KeyboardModifier>, keyName: string, description: input.KeyDescription): Promise<void> {
const { code, key } = description;
await this._pageProxySession.send('Input.dispatchKeyEvent', { await this._pageProxySession.send('Input.dispatchKeyEvent', {
type: 'keyUp', type: 'keyUp',
modifiers: toModifiersMask(modifiers), modifiers: toModifiersMask(modifiers),
key, key,
windowsVirtualKeyCode: keyCode, windowsVirtualKeyCode: description.keyCode,
code, code,
isKeypad: location === input.keypadLocation isKeypad: description.location === input.keypadLocation
}); });
} }