Skip to content

Commit

Permalink
chore: implement BiDi sendCharacter (#11000)
Browse files Browse the repository at this point in the history
  • Loading branch information
jrandolf committed Sep 22, 2023
1 parent 242004b commit c3bd8eb
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 52 deletions.
5 changes: 4 additions & 1 deletion docs/api/puppeteer.keyboard.down.md
Expand Up @@ -10,7 +10,10 @@ Dispatches a `keydown` event.

```typescript
class Keyboard {
down(key: KeyInput, options?: Readonly<KeyDownOptions>): Promise<void>;
abstract down(
key: KeyInput,
options?: Readonly<KeyDownOptions>
): Promise<void>;
}
```

Expand Down
2 changes: 1 addition & 1 deletion docs/api/puppeteer.keyboard.md
Expand Up @@ -9,7 +9,7 @@ Keyboard provides an api for managing a virtual keyboard. The high level api is
#### Signature:

```typescript
export declare class Keyboard
export declare abstract class Keyboard
```

## Remarks
Expand Down
5 changes: 4 additions & 1 deletion docs/api/puppeteer.keyboard.press.md
Expand Up @@ -10,7 +10,10 @@ Shortcut for [Keyboard.down()](./puppeteer.keyboard.down.md) and [Keyboard.up()]

```typescript
class Keyboard {
press(key: KeyInput, options?: Readonly<KeyPressOptions>): Promise<void>;
abstract press(
key: KeyInput,
options?: Readonly<KeyPressOptions>
): Promise<void>;
}
```

Expand Down
2 changes: 1 addition & 1 deletion docs/api/puppeteer.keyboard.sendcharacter.md
Expand Up @@ -10,7 +10,7 @@ Dispatches a `keypress` and `input` event. This does not send a `keydown` or `ke

```typescript
class Keyboard {
sendCharacter(char: string): Promise<void>;
abstract sendCharacter(char: string): Promise<void>;
}
```

Expand Down
5 changes: 4 additions & 1 deletion docs/api/puppeteer.keyboard.type.md
Expand Up @@ -10,7 +10,10 @@ Sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in t

```typescript
class Keyboard {
type(text: string, options?: Readonly<KeyboardTypeOptions>): Promise<void>;
abstract type(
text: string,
options?: Readonly<KeyboardTypeOptions>
): Promise<void>;
}
```

Expand Down
2 changes: 1 addition & 1 deletion docs/api/puppeteer.keyboard.up.md
Expand Up @@ -10,7 +10,7 @@ Dispatches a `keyup` event.

```typescript
class Keyboard {
up(key: KeyInput): Promise<void>;
abstract up(key: KeyInput): Promise<void>;
}
```

Expand Down
30 changes: 9 additions & 21 deletions packages/puppeteer-core/src/api/Input.ts
Expand Up @@ -87,7 +87,7 @@ export type KeyPressOptions = KeyDownOptions & KeyboardTypeOptions;
*
* @public
*/
export class Keyboard {
export abstract class Keyboard {
/**
* @internal
*/
Expand Down Expand Up @@ -120,10 +120,10 @@ export class Keyboard {
* is the commands of keyboard shortcuts,
* see {@link https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/editing/commands/editor_command_names.h | Chromium Source Code} for valid command names.
*/
async down(key: KeyInput, options?: Readonly<KeyDownOptions>): Promise<void>;
async down(): Promise<void> {
throw new Error('Not implemented');
}
abstract down(
key: KeyInput,
options?: Readonly<KeyDownOptions>
): Promise<void>;

/**
* Dispatches a `keyup` event.
Expand All @@ -132,10 +132,7 @@ export class Keyboard {
* See {@link KeyInput | KeyInput}
* for a list of all key names.
*/
async up(key: KeyInput): Promise<void>;
async up(): Promise<void> {
throw new Error('Not implemented');
}
abstract up(key: KeyInput): Promise<void>;

/**
* Dispatches a `keypress` and `input` event.
Expand All @@ -153,10 +150,7 @@ export class Keyboard {
*
* @param char - Character to send into the page.
*/
async sendCharacter(char: string): Promise<void>;
async sendCharacter(): Promise<void> {
throw new Error('Not implemented');
}
abstract sendCharacter(char: string): Promise<void>;

/**
* Sends a `keydown`, `keypress`/`input`,
Expand All @@ -181,13 +175,10 @@ export class Keyboard {
* if specified, is the time to wait between `keydown` and `keyup` in milliseconds.
* Defaults to 0.
*/
async type(
abstract type(
text: string,
options?: Readonly<KeyboardTypeOptions>
): Promise<void>;
async type(): Promise<void> {
throw new Error('Not implemented');
}

/**
* Shortcut for {@link Keyboard.down}
Expand All @@ -211,13 +202,10 @@ export class Keyboard {
* is the commands of keyboard shortcuts,
* see {@link https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/editing/commands/editor_command_names.h | Chromium Source Code} for valid command names.
*/
async press(
abstract press(
key: KeyInput,
options?: Readonly<KeyPressOptions>
): Promise<void>;
async press(): Promise<void> {
throw new Error('Not implemented');
}
}

/**
Expand Down
36 changes: 24 additions & 12 deletions packages/puppeteer-core/src/bidi/Input.ts
Expand Up @@ -20,11 +20,11 @@ import {type Point} from '../api/ElementHandle.js';
import {
Keyboard,
Mouse,
MouseButton,
Touchscreen,
type KeyDownOptions,
type KeyPressOptions,
type KeyboardTypeOptions,
MouseButton,
type MouseClickOptions,
type MouseMoveOptions,
type MouseOptions,
Expand All @@ -33,6 +33,7 @@ import {
import {type KeyInput} from '../common/USKeyboardLayout.js';

import {type BrowsingContext} from './BrowsingContext.js';
import {type BidiPage} from './Page.js';

const enum InputId {
Mouse = '__puppeteer_mouse',
Expand Down Expand Up @@ -285,19 +286,19 @@ const getBidiKeyValue = (key: KeyInput) => {
* @internal
*/
export class BidiKeyboard extends Keyboard {
#context: BrowsingContext;
#page: BidiPage;

constructor(context: BrowsingContext) {
constructor(page: BidiPage) {
super();
this.#context = context;
this.#page = page;
}

override async down(
key: KeyInput,
_options?: Readonly<KeyDownOptions>
): Promise<void> {
await this.#context.connection.send('input.performActions', {
context: this.#context.id,
await this.#page.connection.send('input.performActions', {
context: this.#page.mainFrame()._id,
actions: [
{
type: SourceActionsType.Key,
Expand All @@ -314,8 +315,8 @@ export class BidiKeyboard extends Keyboard {
}

override async up(key: KeyInput): Promise<void> {
await this.#context.connection.send('input.performActions', {
context: this.#context.id,
await this.#page.connection.send('input.performActions', {
context: this.#page.mainFrame()._id,
actions: [
{
type: SourceActionsType.Key,
Expand Down Expand Up @@ -352,8 +353,8 @@ export class BidiKeyboard extends Keyboard {
type: ActionType.KeyUp,
value: getBidiKeyValue(key),
});
await this.#context.connection.send('input.performActions', {
context: this.#context.id,
await this.#page.connection.send('input.performActions', {
context: this.#page.mainFrame()._id,
actions: [
{
type: SourceActionsType.Key,
Expand Down Expand Up @@ -404,8 +405,8 @@ export class BidiKeyboard extends Keyboard {
);
}
}
await this.#context.connection.send('input.performActions', {
context: this.#context.id,
await this.#page.connection.send('input.performActions', {
context: this.#page.mainFrame()._id,
actions: [
{
type: SourceActionsType.Key,
Expand All @@ -415,6 +416,17 @@ export class BidiKeyboard extends Keyboard {
],
});
}

override async sendCharacter(char: string): Promise<void> {
// Measures the number of code points rather than UTF-16 code units.
if ([...char].length > 1) {
throw new Error('Cannot send more than 1 character.');
}
const frame = await this.#page.focusedFrame();
await frame.isolatedRealm().evaluate(async char => {
document.execCommand('insertText', false, char);
}, char);
}
}

/**
Expand Down
31 changes: 30 additions & 1 deletion packages/puppeteer-core/src/bidi/Page.ts
Expand Up @@ -71,6 +71,7 @@ import {
} from './BrowsingContext.js';
import {type BidiConnection} from './Connection.js';
import {BidiDialog} from './Dialog.js';
import {BidiElementHandle} from './ElementHandle.js';
import {EmulationManager} from './EmulationManager.js';
import {BidiFrame, lifeCycleToReadinessState} from './Frame.js';
import {type BidiHTTPRequest} from './HTTPRequest.js';
Expand Down Expand Up @@ -201,7 +202,14 @@ export class BidiPage extends Page {
this.#emulationManager = new EmulationManager(browsingContext);
this.#mouse = new BidiMouse(this.mainFrame().context());
this.#touchscreen = new BidiTouchscreen(this.mainFrame().context());
this.#keyboard = new BidiKeyboard(this.mainFrame().context());
this.#keyboard = new BidiKeyboard(this);
}

/**
* @internal
*/
get connection(): BidiConnection {
return this.#connection;
}

override async setUserAgent(
Expand Down Expand Up @@ -282,6 +290,27 @@ export class BidiPage extends Page {
return mainFrame;
}

/**
* @internal
*/
async focusedFrame(): Promise<BidiFrame> {
using frame = await this.mainFrame()
.isolatedRealm()
.evaluateHandle(() => {
let frame: HTMLIFrameElement | undefined;
let win: Window | null = window;
while (win?.document.activeElement instanceof HTMLIFrameElement) {
frame = win.document.activeElement;
win = frame.contentWindow;
}
return frame;
});
if (!(frame instanceof BidiElementHandle)) {
return this.mainFrame();
}
return await frame.contentFrame();
}

override frames(): BidiFrame[] {
return Array.from(this.#frameTree.frames());
}
Expand Down
10 changes: 8 additions & 2 deletions test/TestExpectations.json
Expand Up @@ -648,10 +648,10 @@
"expectations": ["FAIL", "PASS"]
},
{
"testIdPattern": "[keyboard.spec] Keyboard should send a character with sendCharacter",
"testIdPattern": "[keyboard.spec] Keyboard should send a character with sendCharacter in iframe",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["FAIL"]
"expectations": ["TIMEOUT"]
},
{
"testIdPattern": "[launcher.spec] Launcher specs Puppeteer Browser.disconnect should reject navigation when browser closes",
Expand Down Expand Up @@ -2069,6 +2069,12 @@
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[keyboard.spec] Keyboard should send a character with sendCharacter in iframe",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[keyboard.spec] Keyboard should specify location",
"platforms": ["darwin", "linux", "win32"],
Expand Down

0 comments on commit c3bd8eb

Please sign in to comment.