diff --git a/src/USKeyboardLayout.ts b/src/USKeyboardLayout.ts index 67be3d9d60..ae51aaa2a9 100644 --- a/src/USKeyboardLayout.ts +++ b/src/USKeyboardLayout.ts @@ -16,17 +16,19 @@ */ type KeyDefinition = { - keyCode?: number - shiftKeyCode?: number - key?: string - shiftKey?: string - code?: string - text?: string - shiftText?: string - location ?: number, + keyCode?: number; + shiftKeyCode?: number; + key?: string; + shiftKey?: string; + code?: string; + text?: string; + shiftText?: string; + location? : number; windowsVirtualKeyCode?: number; } +export const keypadLocation = 3; + export const keyDefinitions: { [s: string]: KeyDefinition; } = { '0': {'keyCode': 48, 'key': '0', 'code': 'Digit0'}, '1': {'keyCode': 49, 'key': '1', 'code': 'Digit1'}, @@ -283,4 +285,4 @@ export const keyDefinitions: { [s: string]: KeyDefinition; } = { 'EndCall': {'keyCode': 95, 'key': 'EndCall', 'code': 'EndCall', 'location': 4}, 'VolumeDown': {'keyCode': 182, 'key': 'VolumeDown', 'code': 'VolumeDown', 'location': 4}, 'VolumeUp': {'keyCode': 183, 'key': 'VolumeUp', 'code': 'VolumeUp', 'location': 4}, -}; \ No newline at end of file +}; diff --git a/src/chromium/Input.ts b/src/chromium/Input.ts index 4e3ba3fe21..7601fb9d27 100644 --- a/src/chromium/Input.ts +++ b/src/chromium/Input.ts @@ -15,178 +15,68 @@ * limitations under the License. */ -import { assert } from '../helper'; import * as input from '../input'; -import { keyDefinitions } from '../USKeyboardLayout'; import { CDPSession } from './Connection'; -type KeyDescription = { - keyCode: number, - key: string, - text: string, - code: string, - location: number, -}; +function toModifiersMask(modifiers: Set): number { + let mask = 0; + if (modifiers.has('Alt')) + mask |= 1; + if (modifiers.has('Control')) + mask |= 2; + if (modifiers.has('Meta')) + mask |= 4; + if (modifiers.has('Shift')) + mask |= 8; + return mask; +} -const kModifiers: input.Modifier[] = ['Alt', 'Control', 'Meta', 'Shift']; - -export class Keyboard { +export class RawKeyboardImpl implements input.RawKeyboard { private _client: CDPSession; - _modifiers = 0; - private _pressedKeys = new Set(); constructor(client: CDPSession) { this._client = client; } - async down(key: string, options: { text?: string; } = { text: undefined }) { - const description = this._keyDescriptionForString(key); - - const autoRepeat = this._pressedKeys.has(description.code); - this._pressedKeys.add(description.code); - this._modifiers |= this._modifierBit(description.key); - - const text = options.text === undefined ? description.text : options.text; + async keydown(modifiers: Set, code: string, keyCode: number, key: string, location: number, autoRepeat: boolean, text: string | undefined): Promise { await this._client.send('Input.dispatchKeyEvent', { type: text ? 'keyDown' : 'rawKeyDown', - modifiers: this._modifiers, - windowsVirtualKeyCode: description.keyCode, - code: description.code, - key: description.key, - text: text, + modifiers: toModifiersMask(modifiers), + windowsVirtualKeyCode: keyCode, + code, + key, + text, unmodifiedText: text, autoRepeat, - location: description.location, - isKeypad: description.location === 3 + location, + isKeypad: location === input.keypadLocation }); } - _modifierBit(key: string): number { - if (key === 'Alt') - return 1; - if (key === 'Control') - return 2; - if (key === 'Meta') - return 4; - if (key === 'Shift') - return 8; - return 0; - } - - _keyDescriptionForString(keyString: string): KeyDescription { - const shift = this._modifiers & 8; - const description = { - key: '', - keyCode: 0, - code: '', - text: '', - location: 0 - }; - - const definition = keyDefinitions[keyString]; - assert(definition, `Unknown key: "${keyString}"`); - - if (definition.key) - description.key = definition.key; - if (shift && definition.shiftKey) - description.key = definition.shiftKey; - - if (definition.keyCode) - description.keyCode = definition.keyCode; - if (shift && definition.shiftKeyCode) - description.keyCode = definition.shiftKeyCode; - - if (definition.code) - description.code = definition.code; - - if (definition.location) - description.location = definition.location; - - if (description.key.length === 1) - description.text = description.key; - - if (definition.text) - description.text = definition.text; - if (shift && definition.shiftText) - description.text = definition.shiftText; - - // if any modifiers besides shift are pressed, no text should be sent - if (this._modifiers & ~8) - description.text = ''; - - return description; - } - - async up(key: string) { - const description = this._keyDescriptionForString(key); - - this._modifiers &= ~this._modifierBit(description.key); - this._pressedKeys.delete(description.code); + async keyup(modifiers: Set, code: string, keyCode: number, key: string, location: number): Promise { await this._client.send('Input.dispatchKeyEvent', { type: 'keyUp', - modifiers: this._modifiers, - key: description.key, - windowsVirtualKeyCode: description.keyCode, - code: description.code, - location: description.location + modifiers: toModifiersMask(modifiers), + key, + windowsVirtualKeyCode: keyCode, + code, + location }); } - async sendCharacter(char: string) { - await this._client.send('Input.insertText', {text: char}); - } - - async type(text: string, options: { delay: (number | undefined); } | undefined) { - const delay = (options && options.delay) || null; - for (const char of text) { - if (keyDefinitions[char]) { - await this.press(char, {delay}); - } else { - if (delay) - await new Promise(f => setTimeout(f, delay)); - await this.sendCharacter(char); - } - } - } - - async press(key: string, options: { delay?: number; text?: string; } = {}) { - const {delay = null} = options; - await this.down(key, options); - if (delay) - await new Promise(f => setTimeout(f, options.delay)); - await this.up(key); - } - - async _ensureModifiers(modifiers: input.Modifier[]): Promise { - for (const modifier of modifiers) { - if (!kModifiers.includes(modifier)) - throw new Error('Uknown modifier ' + modifier); - } - const restore: input.Modifier[] = []; - const promises: Promise[] = []; - for (const key of kModifiers) { - const needDown = modifiers.includes(key); - const isDown = (this._modifiers & this._modifierBit(key)) !== 0; - if (isDown) - restore.push(key); - if (needDown && !isDown) - promises.push(this.down(key)); - else if (!needDown && isDown) - promises.push(this.up(key)); - } - await Promise.all(promises); - return restore; + async sendText(text: string): Promise { + await this._client.send('Input.insertText', { text }); } } export class Mouse implements input.MouseOperations { private _client: CDPSession; - private _keyboard: Keyboard; + private _keyboard: input.Keyboard; private _x = 0; private _y = 0; private _button: 'none' | input.Button = 'none'; - constructor(client: CDPSession, keyboard: Keyboard) { + constructor(client: CDPSession, keyboard: input.Keyboard) { this._client = client; this._keyboard = keyboard; } @@ -202,7 +92,7 @@ export class Mouse implements input.MouseOperations { button: this._button, x: fromX + (this._x - fromX) * (i / steps), y: fromY + (this._y - fromY) * (i / steps), - modifiers: this._keyboard._modifiers + modifiers: toModifiersMask(this._keyboard._modifiers()) }); } } @@ -215,7 +105,7 @@ export class Mouse implements input.MouseOperations { button, x: this._x, y: this._y, - modifiers: this._keyboard._modifiers, + modifiers: toModifiersMask(this._keyboard._modifiers()), clickCount }); } @@ -228,7 +118,7 @@ export class Mouse implements input.MouseOperations { button, x: this._x, y: this._y, - modifiers: this._keyboard._modifiers, + modifiers: toModifiersMask(this._keyboard._modifiers()), clickCount }); } diff --git a/src/chromium/Page.ts b/src/chromium/Page.ts index 8256942129..cb847324b7 100644 --- a/src/chromium/Page.ts +++ b/src/chromium/Page.ts @@ -36,13 +36,14 @@ import { PDF } from './features/pdf'; import { Workers } from './features/workers'; import { Frame } from './Frame'; import { FrameManager, FrameManagerEvents } from './FrameManager'; -import { Keyboard, Mouse } from './Input'; +import { Mouse, RawKeyboardImpl } from './Input'; import { createJSHandle, ElementHandle, JSHandle } from './JSHandle'; import { NetworkManagerEvents, Response } from './NetworkManager'; import { Protocol } from './protocol'; import { getExceptionMessage, releaseObject, valueFromRemoteObject } from './protocolHelper'; import { Target } from './Target'; import { TaskQueue } from './TaskQueue'; +import * as input from '../input'; const writeFileAsync = helper.promisify(fs.writeFile); @@ -59,7 +60,7 @@ export class Page extends EventEmitter { private _closed = false; _client: CDPSession; private _target: Target; - private _keyboard: Keyboard; + private _keyboard: input.Keyboard; private _mouse: Mouse; private _timeoutSettings: TimeoutSettings; private _frameManager: FrameManager; @@ -90,7 +91,7 @@ export class Page extends EventEmitter { super(); this._client = client; this._target = target; - this._keyboard = new Keyboard(client); + this._keyboard = new input.Keyboard(new RawKeyboardImpl(client)); this._mouse = new Mouse(client, this._keyboard); this._timeoutSettings = new TimeoutSettings(); this.accessibility = new Accessibility(client); @@ -206,7 +207,7 @@ export class Page extends EventEmitter { return this._frameManager.mainFrame(); } - get keyboard(): Keyboard { + get keyboard(): input.Keyboard { return this._keyboard; } diff --git a/src/chromium/api.ts b/src/chromium/api.ts index 4885e68f7e..46c3657e1b 100644 --- a/src/chromium/api.ts +++ b/src/chromium/api.ts @@ -17,10 +17,10 @@ export { PDF } from './features/pdf'; export { Permissions } from './features/permissions'; export { Worker, Workers } from './features/workers'; export { Frame } from './Frame'; -export { Keyboard, Mouse } from './Input'; +export { Mouse } from './Input'; +export { Keyboard } from '../input'; export { ElementHandle, JSHandle } from './JSHandle'; export { Request, Response } from './NetworkManager'; export { ConsoleMessage, FileChooser, Page } from './Page'; export { Playwright } from './Playwright'; export { Target } from './Target'; - diff --git a/src/firefox/Input.ts b/src/firefox/Input.ts index 029e70e27d..94dba466ce 100644 --- a/src/firefox/Input.ts +++ b/src/firefox/Input.ts @@ -15,155 +15,72 @@ * limitations under the License. */ -import { keyDefinitions } from '../USKeyboardLayout'; import { JugglerSession } from './Connection'; import * as input from '../input'; -interface KeyDescription { - keyCode: number; - key: string; - text: string; - code: string; - location: number; +function toModifiersMask(modifiers: Set): number { + let mask = 0; + if (modifiers.has('Alt')) + mask |= 1; + if (modifiers.has('Control')) + mask |= 2; + if (modifiers.has('Shift')) + mask |= 4; + if (modifiers.has('Meta')) + mask |= 8; + return mask; } -export class Keyboard { - _client: JugglerSession; - _modifiers: number; - _pressedKeys: Set; +export class RawKeyboardImpl implements input.RawKeyboard { + private _client: JugglerSession; + constructor(client: JugglerSession) { this._client = client; - this._modifiers = 0; - this._pressedKeys = new Set(); } - async down(key: string) { - const description = this._keyDescriptionForString(key); - - const repeat = this._pressedKeys.has(description.code); - this._pressedKeys.add(description.code); - this._modifiers |= this._modifierBit(description.key); - + async keydown(modifiers: Set, code: string, keyCode: number, key: string, location: number, autoRepeat: boolean, text: string | undefined): Promise { + if (code === 'MetaLeft') + code = 'OSLeft'; + if (code === 'MetaRight') + code = 'OSRight'; await this._client.send('Page.dispatchKeyEvent', { type: 'keydown', - keyCode: description.keyCode, - code: description.code, - key: description.key, - repeat, - location: description.location + keyCode, + code, + key, + repeat: autoRepeat, + location }); } - _modifierBit(key: string): number { - if (key === 'Alt') - return 1; - if (key === 'Control') - return 2; - if (key === 'Shift') - return 4; - if (key === 'Meta') - return 8; - return 0; - } - - _keyDescriptionForString(keyString: string): KeyDescription { - const shift = this._modifiers & 8; - const description = { - key: '', - keyCode: 0, - code: '', - text: '', - location: 0 - }; - const definition = keyDefinitions[keyString]; - if (!definition) - throw new Error(`Unknown key: "${keyString}"`); - - if (definition.key) - description.key = definition.key; - if (shift && definition.shiftKey) - description.key = definition.shiftKey; - - if (definition.keyCode) - description.keyCode = definition.keyCode; - if (shift && definition.shiftKeyCode) - description.keyCode = definition.shiftKeyCode; - - if (definition.code) - description.code = definition.code; - - if (definition.location) - description.location = definition.location; - - if (description.key.length === 1) - description.text = description.key; - - if (definition.text) - description.text = definition.text; - if (shift && definition.shiftText) - description.text = definition.shiftText; - - // if any modifiers besides shift are pressed, no text should be sent - if (this._modifiers & ~8) - description.text = ''; - - if (description.code === 'MetaLeft') - description.code = 'OSLeft'; - if (description.code === 'MetaRight') - description.code = 'OSRight'; - return description; - } - - async up(key: string) { - const description = this._keyDescriptionForString(key); - - this._modifiers &= ~this._modifierBit(description.key); - this._pressedKeys.delete(description.code); + async keyup(modifiers: Set, code: string, keyCode: number, key: string, location: number): Promise { + if (code === 'MetaLeft') + code = 'OSLeft'; + if (code === 'MetaRight') + code = 'OSRight'; await this._client.send('Page.dispatchKeyEvent', { type: 'keyup', - key: description.key, - keyCode: description.keyCode, - code: description.code, - location: description.location, + key, + keyCode, + code, + location, repeat: false }); } - async sendCharacter(char: string) { - await this._client.send('Page.insertText', { - text: char - }); - } - - async type(text: string, options: { delay?: number; } | undefined = {}) { - const {delay = null} = options; - for (const char of text) { - if (keyDefinitions[char]) - await this.press(char, {delay}); - else - await this.sendCharacter(char); - if (delay !== null) - await new Promise(f => setTimeout(f, delay)); - } - } - - async press(key: string, options: { delay?: number; } | undefined = {}) { - const {delay = null} = options; - await this.down(key); - if (delay !== null) - await new Promise(f => setTimeout(f, options.delay)); - await this.up(key); + async sendText(text: string): Promise { + await this._client.send('Page.insertText', { text }); } } export class Mouse implements input.MouseOperations { - _client: any; - _keyboard: Keyboard; + _client: JugglerSession; + _keyboard: input.Keyboard; _x: number; _y: number; _buttons: number; - constructor(client, keyboard: Keyboard) { + constructor(client: JugglerSession, keyboard: input.Keyboard) { this._client = client; this._keyboard = keyboard; this._x = 0; @@ -182,7 +99,7 @@ export class Mouse implements input.MouseOperations { button: 0, x: fromX + (this._x - fromX) * (i / steps), y: fromY + (this._y - fromY) * (i / steps), - modifiers: this._keyboard._modifiers, + modifiers: toModifiersMask(this._keyboard._modifiers()), buttons: this._buttons, }); } @@ -204,7 +121,7 @@ export class Mouse implements input.MouseOperations { button: this._buttonNameToButton(button), x: this._x, y: this._y, - modifiers: this._keyboard._modifiers, + modifiers: toModifiersMask(this._keyboard._modifiers()), clickCount, buttons: this._buttons, }); @@ -235,7 +152,7 @@ export class Mouse implements input.MouseOperations { button: this._buttonNameToButton(button), x: this._x, y: this._y, - modifiers: this._keyboard._modifiers, + modifiers: toModifiersMask(this._keyboard._modifiers()), clickCount: clickCount, buttons: this._buttons, }); diff --git a/src/firefox/Page.ts b/src/firefox/Page.ts index cb2490d492..367e10d85f 100644 --- a/src/firefox/Page.ts +++ b/src/firefox/Page.ts @@ -11,12 +11,12 @@ import { Events } from './events'; import { Accessibility } from './features/accessibility'; import { Interception } from './features/interception'; import { FrameManager, FrameManagerEvents, normalizeWaitUntil, Frame } from './FrameManager'; -import { Keyboard, Mouse } from './Input'; +import { Mouse, RawKeyboardImpl } from './Input'; import { createHandle, ElementHandle, JSHandle } from './JSHandle'; import { NavigationWatchdog } from './NavigationWatchdog'; import { NetworkManager, NetworkManagerEvents, Request, Response } from './NetworkManager'; import { ClickOptions, MultiClickOptions } from '../input'; - +import * as input from '../input'; const writeFileAsync = helper.promisify(fs.writeFile); @@ -24,7 +24,7 @@ export class Page extends EventEmitter { private _timeoutSettings: TimeoutSettings; private _session: JugglerSession; private _target: Target; - private _keyboard: Keyboard; + private _keyboard: input.Keyboard; private _mouse: Mouse; readonly accessibility: Accessibility; readonly interception: Interception; @@ -54,7 +54,7 @@ export class Page extends EventEmitter { this._timeoutSettings = new TimeoutSettings(); this._session = session; this._target = target; - this._keyboard = new Keyboard(session); + this._keyboard = new input.Keyboard(new RawKeyboardImpl(session)); this._mouse = new Mouse(session, this._keyboard); this.accessibility = new Accessibility(session); this._closed = false; @@ -332,7 +332,7 @@ export class Page extends EventEmitter { return this._frameManager.mainFrame(); } - get keyboard(){ + get keyboard(): input.Keyboard { return this._keyboard; } diff --git a/src/firefox/api.ts b/src/firefox/api.ts index a7cfabc52a..fc6a3c35ba 100644 --- a/src/firefox/api.ts +++ b/src/firefox/api.ts @@ -10,7 +10,8 @@ export { Accessibility } from './features/accessibility'; export { Interception } from './features/interception'; export { Permissions } from './features/permissions'; export { Frame } from './FrameManager'; -export { Keyboard, Mouse } from './Input'; +export { Mouse } from './Input'; +export { Keyboard } from '../input'; export { ElementHandle, JSHandle } from './JSHandle'; export { Request, Response } from './NetworkManager'; export { ConsoleMessage, Page } from './Page'; diff --git a/src/input.ts b/src/input.ts index 730bc73833..86b700aeed 100644 --- a/src/input.ts +++ b/src/input.ts @@ -1,6 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { assert } from './helper'; +import * as keyboardLayout from './USKeyboardLayout'; + export type Modifier = 'Alt' | 'Control' | 'Meta' | 'Shift'; export type Button = 'left' | 'right' | 'middle'; @@ -31,6 +34,144 @@ export type SelectOption = { index?: number; }; +export const keypadLocation = keyboardLayout.keypadLocation; + +type KeyDescription = { + keyCode: number, + key: string, + text: string, + code: string, + location: number, +}; + +const kModifiers: Modifier[] = ['Alt', 'Control', 'Meta', 'Shift']; + +export interface RawKeyboard { + keydown(modifiers: Set, code: string, keyCode: number, key: string, location: number, autoRepeat: boolean, text: string | undefined): Promise; + keyup(modifiers: Set, code: string, keyCode: number, key: string, location: number): Promise; + sendText(text: string): Promise; +} + +export class Keyboard { + private _raw: RawKeyboard; + private _pressedModifiers = new Set(); + private _pressedKeys = new Set(); + + constructor(raw: RawKeyboard) { + this._raw = raw; + } + + async down(key: string, options: { text?: string; } = { text: undefined }) { + const description = this._keyDescriptionForString(key); + const autoRepeat = this._pressedKeys.has(description.code); + this._pressedKeys.add(description.code); + if (kModifiers.includes(description.key as Modifier)) + this._pressedModifiers.add(description.key as Modifier); + const text = options.text === undefined ? description.text : options.text; + await this._raw.keydown(this._pressedModifiers, description.code, description.keyCode, description.key, description.location, autoRepeat, text); + } + + private _keyDescriptionForString(keyString: string): KeyDescription { + const shift = this._pressedModifiers.has('Shift'); + const description: KeyDescription = { + key: '', + keyCode: 0, + code: '', + text: '', + location: 0 + }; + + const definition = keyboardLayout.keyDefinitions[keyString]; + assert(definition, `Unknown key: "${keyString}"`); + + if (definition.key) + description.key = definition.key; + if (shift && definition.shiftKey) + description.key = definition.shiftKey; + + if (definition.keyCode) + description.keyCode = definition.keyCode; + if (shift && definition.shiftKeyCode) + description.keyCode = definition.shiftKeyCode; + + if (definition.code) + description.code = definition.code; + + if (definition.location) + description.location = definition.location; + + if (description.key.length === 1) + description.text = description.key; + + if (definition.text) + description.text = definition.text; + if (shift && definition.shiftText) + description.text = definition.shiftText; + + // if any modifiers besides shift are pressed, no text should be sent + if (this._pressedModifiers.size > 1 || (!this._pressedModifiers.has('Shift') && this._pressedModifiers.size === 1)) + description.text = ''; + + return description; + } + + async up(key: string) { + const description = this._keyDescriptionForString(key); + if (kModifiers.includes(description.key as Modifier)) + this._pressedModifiers.delete(description.key as Modifier); + this._pressedKeys.delete(description.code); + await this._raw.keyup(this._pressedModifiers, description.code, description.keyCode, description.key, description.location); + } + + async sendCharacter(text: string) { + await this._raw.sendText(text); + } + + async type(text: string, options: { delay: (number | undefined); } | undefined) { + const delay = (options && options.delay) || null; + for (const char of text) { + if (keyboardLayout.keyDefinitions[char]) { + await this.press(char, {delay}); + } else { + if (delay) + await new Promise(f => setTimeout(f, delay)); + await this.sendCharacter(char); + } + } + } + + async press(key: string, options: { delay?: number; text?: string; } = {}) { + const {delay = null} = options; + await this.down(key, options); + if (delay) + await new Promise(f => setTimeout(f, options.delay)); + await this.up(key); + } + + async _ensureModifiers(modifiers: Modifier[]): Promise { + for (const modifier of modifiers) { + if (!kModifiers.includes(modifier)) + throw new Error('Uknown modifier ' + modifier); + } + const restore: Modifier[] = Array.from(this._pressedModifiers); + const promises: Promise[] = []; + for (const key of kModifiers) { + const needDown = modifiers.includes(key); + const isDown = this._pressedModifiers.has(key); + if (needDown && !isDown) + promises.push(this.down(key)); + else if (!needDown && isDown) + promises.push(this.up(key)); + } + await Promise.all(promises); + return restore; + } + + _modifiers(): Set { + return this._pressedModifiers; + } +} + export interface MouseOperations { move(x: number, y: number, options?: { steps?: number; }): Promise; down(options?: { button?: Button; clickCount?: number; }): Promise; diff --git a/src/webkit/Input.ts b/src/webkit/Input.ts index bfc7fd61b1..23576baf89 100644 --- a/src/webkit/Input.ts +++ b/src/webkit/Input.ts @@ -15,187 +15,68 @@ * limitations under the License. */ -import { assert } from '../helper'; import * as input from '../input'; -import { keyDefinitions } from '../USKeyboardLayout'; import { TargetSession } from './Connection'; -type KeyDescription = { - keyCode: number, - key: string, - text: string, - code: string, - isKeypad: boolean -}; +function toModifiersMask(modifiers: Set): number { + // From Source/WebKit/Shared/WebEvent.h + let mask = 0; + if (modifiers.has('Shift')) + mask |= 1; + if (modifiers.has('Control')) + mask |= 2; + if (modifiers.has('Alt')) + mask |= 4; + if (modifiers.has('Meta')) + mask |= 8; + return mask; +} -export type Modifier = 'Alt' | 'Control' | 'Meta' | 'Shift'; -const kModifiers: Modifier[] = ['Alt', 'Control', 'Meta', 'Shift']; - -export type Button = 'left' | 'right' | 'middle'; - -export class Keyboard { +export class RawKeyboardImpl implements input.RawKeyboard { private _session: TargetSession; - _modifiers = 0; - private _pressedKeys = new Set(); constructor(session: TargetSession) { this._session = session; } - async down(key: string, options: { text?: string; } = { text: undefined }) { - const description = this._keyDescriptionForString(key); - - const autoRepeat = this._pressedKeys.has(description.code); - this._pressedKeys.add(description.code); - this._modifiers |= this._modifierBit(description.key); - - const text = options.text === undefined ? description.text : options.text; + async keydown(modifiers: Set, code: string, keyCode: number, key: string, location: number, autoRepeat: boolean, text: string | undefined): Promise { await this._session.send('Input.dispatchKeyEvent', { type: 'keyDown', - modifiers: this._modifiers, - windowsVirtualKeyCode: description.keyCode, - code: description.code, - key: description.key, - text: text, + modifiers: toModifiersMask(modifiers), + windowsVirtualKeyCode: keyCode, + code, + key, + text, unmodifiedText: text, autoRepeat, - isKeypad: description.isKeypad, + isKeypad: location === input.keypadLocation }); } - _modifierBit(key: string): number { - // From Source/WebKit/Shared/WebEvent.h - const ShiftKey = 1 << 0; - const ControlKey = 1 << 1; - const AltKey = 1 << 2; - const MetaKey = 1 << 3; - if (key === 'Alt') - return AltKey; - if (key === 'Control') - return ControlKey; - if (key === 'Meta') - return MetaKey; - if (key === 'Shift') - return ShiftKey; - return 0; - } - - _keyDescriptionForString(keyString: string): KeyDescription { - const shift = this._modifiers & 8; - const description = { - key: '', - keyCode: 0, - code: '', - text: '', - isKeypad: false - }; - - const definition = keyDefinitions[keyString]; - assert(definition, `Unknown key: "${keyString}"`); - - if (definition.key) - description.key = definition.key; - if (shift && definition.shiftKey) - description.key = definition.shiftKey; - - if (definition.keyCode) - description.keyCode = definition.windowsVirtualKeyCode || definition.keyCode; - if (shift && definition.shiftKeyCode) - description.keyCode = definition.shiftKeyCode; - - if (definition.code) - description.code = definition.code; - - - if (description.key.length === 1) - description.text = description.key; - - if (definition.text) - description.text = definition.text; - if (shift && definition.shiftText) - description.text = definition.shiftText; - - // if any modifiers besides shift are pressed, no text should be sent - if (this._modifiers & ~1) - description.text = ''; - - description.isKeypad = definition.location === 3; - - return description; - } - - async up(key: string) { - const description = this._keyDescriptionForString(key); - - this._modifiers &= ~this._modifierBit(description.key); - this._pressedKeys.delete(description.code); + async keyup(modifiers: Set, code: string, keyCode: number, key: string, location: number): Promise { await this._session.send('Input.dispatchKeyEvent', { type: 'keyUp', - modifiers: this._modifiers, - key: description.key, - windowsVirtualKeyCode: description.keyCode, - code: description.code, - isKeypad: description.isKeypad + modifiers: toModifiersMask(modifiers), + key, + windowsVirtualKeyCode: keyCode, + code, + isKeypad: location === input.keypadLocation }); } - async type(text: string, options: { delay: (number | undefined); } | undefined) { - const delay = (options && options.delay) || null; - for (const char of text) { - if (keyDefinitions[char]) { - await this.press(char, {delay}); - } else { - if (delay) - await new Promise(f => setTimeout(f, delay)); - await this.sendCharacter(char); - } - } - } - - async press(key: string, options: { delay?: number; text?: string; } = {}) { - const {delay = null} = options; - await this.down(key, options); - if (delay) - await new Promise(f => setTimeout(f, options.delay)); - await this.up(key); - } - - async _ensureModifiers(modifiers: Modifier[]): Promise { - for (const modifier of modifiers) { - if (!kModifiers.includes(modifier)) - throw new Error('Uknown modifier ' + modifier); - } - const restore: Modifier[] = []; - const promises: Promise[] = []; - for (const key of kModifiers) { - const needDown = modifiers.includes(key); - const isDown = (this._modifiers & this._modifierBit(key)) !== 0; - if (isDown) - restore.push(key); - if (needDown && !isDown) - promises.push(this.down(key)); - else if (!needDown && isDown) - promises.push(this.up(key)); - } - await Promise.all(promises); - return restore; - } - - async sendCharacter(text: string) { - await this._session.send('Page.insertText', { - text - }); + async sendText(text: string): Promise { + await this._session.send('Page.insertText', { text }); } } export class Mouse implements input.MouseOperations { private _client: TargetSession; - private _keyboard: Keyboard; + private _keyboard: input.Keyboard; private _x = 0; private _y = 0; - private _button: 'none' | Button = 'none'; + private _button: 'none' | input.Button = 'none'; - constructor(client: TargetSession, keyboard: Keyboard) { + constructor(client: TargetSession, keyboard: input.Keyboard) { this._client = client; this._keyboard = keyboard; } @@ -211,12 +92,12 @@ export class Mouse implements input.MouseOperations { button: this._button, x: fromX + (this._x - fromX) * (i / steps), y: fromY + (this._y - fromY) * (i / steps), - modifiers: this._keyboard._modifiers + modifiers: toModifiersMask(this._keyboard._modifiers()) }); } } - async down(options: { button?: Button; clickCount?: number; } = {}) { + async down(options: { button?: input.Button; clickCount?: number; } = {}) { const {button = 'left', clickCount = 1} = options; this._button = button; await this._client.send('Input.dispatchMouseEvent', { @@ -224,12 +105,12 @@ export class Mouse implements input.MouseOperations { button, x: this._x, y: this._y, - modifiers: this._keyboard._modifiers, + modifiers: toModifiersMask(this._keyboard._modifiers()), clickCount }); } - async up(options: { button?: Button; clickCount?: number; } = {}) { + async up(options: { button?: input.Button; clickCount?: number; } = {}) { const {button = 'left', clickCount = 1} = options; this._button = 'none'; await this._client.send('Input.dispatchMouseEvent', { @@ -237,7 +118,7 @@ export class Mouse implements input.MouseOperations { button, x: this._x, y: this._y, - modifiers: this._keyboard._modifiers, + modifiers: toModifiersMask(this._keyboard._modifiers()), clickCount }); } diff --git a/src/webkit/Page.ts b/src/webkit/Page.ts index 5c87b76f2b..7ff405e5e3 100644 --- a/src/webkit/Page.ts +++ b/src/webkit/Page.ts @@ -25,13 +25,14 @@ import { Browser, BrowserContext } from './Browser'; import { TargetSession, TargetSessionEvents } from './Connection'; import { Events } from './events'; import { Frame, FrameManager, FrameManagerEvents } from './FrameManager'; -import { Keyboard, Mouse } from './Input'; +import { RawKeyboardImpl, Mouse } from './Input'; import { createJSHandle, ElementHandle, JSHandle } from './JSHandle'; import { NetworkManagerEvents, Response } from './NetworkManager'; import { Protocol } from './protocol'; import { valueFromRemoteObject } from './protocolHelper'; import { Target } from './Target'; import { TaskQueue } from './TaskQueue'; +import * as input from '../input'; const writeFileAsync = helper.promisify(fs.writeFile); @@ -44,7 +45,7 @@ export class Page extends EventEmitter { private _closed = false; private _session: TargetSession; private _target: Target; - private _keyboard: Keyboard; + private _keyboard: input.Keyboard; private _mouse: Mouse; private _timeoutSettings: TimeoutSettings; private _frameManager: FrameManager; @@ -66,7 +67,7 @@ export class Page extends EventEmitter { constructor(session: TargetSession, target: Target, screenshotTaskQueue: TaskQueue) { super(); - this._keyboard = new Keyboard(session); + this._keyboard = new input.Keyboard(new RawKeyboardImpl(session)); this._mouse = new Mouse(session, this._keyboard); this._timeoutSettings = new TimeoutSettings(); this._frameManager = new FrameManager(session, this, this._timeoutSettings); @@ -173,7 +174,7 @@ export class Page extends EventEmitter { return this._frameManager.mainFrame(); } - get keyboard(): Keyboard { + get keyboard(): input.Keyboard { return this._keyboard; } diff --git a/src/webkit/api.ts b/src/webkit/api.ts index a1ddf48b65..a317204936 100644 --- a/src/webkit/api.ts +++ b/src/webkit/api.ts @@ -6,7 +6,8 @@ export { Browser, BrowserContext } from './Browser'; export { BrowserFetcher } from './BrowserFetcher'; export { ExecutionContext } from './ExecutionContext'; export { Frame } from './FrameManager'; -export { Keyboard, Mouse } from './Input'; +export { Mouse } from './Input'; +export { Keyboard } from '../input'; export { ElementHandle, JSHandle } from './JSHandle'; export { Request, Response } from './NetworkManager'; export { ConsoleMessage, Page } from './Page'; diff --git a/test/evaluation.spec.js b/test/evaluation.spec.js index f4c2b563ad..ad682f1c88 100644 --- a/test/evaluation.spec.js +++ b/test/evaluation.spec.js @@ -264,7 +264,7 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) { await page.goto(server.PREFIX + '/tamperable.html'); expect(await page.evaluate(() => window.result)).toBe(123); }); - fit('should support multiple scripts', async({page, server}) => { + it('should support multiple scripts', async({page, server}) => { await page.evaluateOnNewDocument(function(){ window.script1 = 1; }); diff --git a/test/keyboard.spec.js b/test/keyboard.spec.js index a7d0f92bf9..a6e85fdadc 100644 --- a/test/keyboard.spec.js +++ b/test/keyboard.spec.js @@ -82,7 +82,7 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) { await page.keyboard.sendCharacter('a'); expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('嗨a'); }); - it('should report shiftKey', async({page, server}) => { + it.skip(FFOX)('should report shiftKey', async({page, server}) => { await page.goto(server.PREFIX + '/input/keyboard.html'); const keyboard = page.keyboard; const codeForKey = {'Shift': 16, 'Alt': 18, 'Control': 17};