Skip to content

Commit 3d3ee67

Browse files
JoelEinbinderCommit Bot
authored and
Commit Bot
committed
DevTools: Add commands option to Input.dispatchKeyEvent
Key events emulated with DevTools can now use the commands option to send editing commands that will be executed if the event is not canceled. This is important for shortcuts like Meta+A = SelectAll to work on a Mac. See puppeteer/puppeteer#1313 and microsoft/playwright#1067 Change-Id: Id258668bfc71ef9f7f47477ef9de3422ada1d4b5 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2211929 Commit-Queue: Joel Einbinder <[email protected]> Reviewed-by: Andrey Kosyakov <[email protected]> Reviewed-by: Dmitry Gozman <[email protected]> Reviewed-by: Yang Guo <[email protected]> Cr-Commit-Position: refs/heads/master@{#780848}
1 parent 47de01c commit 3d3ee67

File tree

5 files changed

+122
-2
lines changed

5 files changed

+122
-2
lines changed

content/browser/devtools/protocol/input_handler.cc

+13-2
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ class InputHandler::InputInjector
374374
}
375375

376376
void InjectKeyboardEvent(const NativeWebKeyboardEvent& keyboard_event,
377+
Maybe<Array<std::string>> commands,
377378
std::unique_ptr<DispatchKeyEventCallback> callback) {
378379
if (!widget_host_) {
379380
callback->sendFailure(Response::InternalError());
@@ -383,7 +384,15 @@ class InputHandler::InputInjector
383384
widget_host_->Focus();
384385
input_queued_ = false;
385386
pending_key_callbacks_.push_back(std::move(callback));
386-
widget_host_->ForwardKeyboardEvent(keyboard_event);
387+
ui::LatencyInfo latency;
388+
std::vector<blink::mojom::EditCommandPtr> edit_commands;
389+
if (commands.isJust()) {
390+
for (const std::string& command : *commands.fromJust())
391+
edit_commands.push_back(blink::mojom::EditCommand::New(command, ""));
392+
}
393+
394+
widget_host_->ForwardKeyboardEventWithCommands(keyboard_event, latency,
395+
std::move(edit_commands));
387396
if (!input_queued_) {
388397
pending_key_callbacks_.back()->sendSuccess();
389398
pending_key_callbacks_.pop_back();
@@ -537,6 +546,7 @@ void InputHandler::DispatchKeyEvent(
537546
Maybe<bool> is_keypad,
538547
Maybe<bool> is_system_key,
539548
Maybe<int> location,
549+
Maybe<Array<std::string>> commands,
540550
std::unique_ptr<DispatchKeyEventCallback> callback) {
541551
blink::WebInputEvent::Type web_event_type;
542552

@@ -609,7 +619,8 @@ void InputHandler::DispatchKeyEvent(
609619
else
610620
event.skip_in_browser = true;
611621

612-
EnsureInjector(widget_host)->InjectKeyboardEvent(event, std::move(callback));
622+
EnsureInjector(widget_host)
623+
->InjectKeyboardEvent(event, std::move(commands), std::move(callback));
613624
}
614625

615626
void InputHandler::InsertText(const std::string& text,

content/browser/devtools/protocol/input_handler.h

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class InputHandler : public DevToolsDomainHandler, public Input::Backend {
6060
Maybe<bool> is_keypad,
6161
Maybe<bool> is_system_key,
6262
Maybe<int> location,
63+
Maybe<Array<std::string>> commands,
6364
std::unique_ptr<DispatchKeyEventCallback> callback) override;
6465

6566
void InsertText(const std::string& text,

third_party/blink/public/devtools_protocol/browser_protocol.pdl

+4
Original file line numberDiff line numberDiff line change
@@ -3502,6 +3502,10 @@ domain Input
35023502
# Whether the event was from the left or right side of the keyboard. 1=Left, 2=Right (default:
35033503
# 0).
35043504
optional integer location
3505+
# Editing commands to send with the key event (e.g., 'selectAll') (default: []).
3506+
# These are related to but not equal the command names used in `document.execCommand` and NSStandardKeyBindingResponding.
3507+
# See https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/editing/commands/editor_command_names.h for valid command names.
3508+
experimental optional array of string commands
35053509

35063510
# This method emulates inserting text that doesn't come from a key press,
35073511
# for example an emoji keyboard or an IME.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Tests Input.dispatchKeyEvent commands option.
2+
3+
Sending "b"
4+
with commands ["selectAll"]
5+
hello world
6+
~~~~~~~~~~~^
7+
8+
Sending "Backspace"
9+
10+
^
11+
12+
Sending "c"
13+
c
14+
^
15+
16+
Canceling the next keydown
17+
18+
Sending "d"
19+
with commands ["selectAll"]
20+
c
21+
^
22+
23+
Sending "e"
24+
ce
25+
^
26+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
(async function(testRunner) {
2+
const {session, dp} = await testRunner.startBlank(`Tests Input.dispatchKeyEvent commands option.`);
3+
4+
await session.evaluate(`
5+
const textarea = document.createElement('textarea');
6+
document.body.appendChild(textarea);
7+
textarea.value = 'hello world';
8+
textarea.focus();
9+
10+
function selectedText() {
11+
const textarea = document.querySelector('textarea');
12+
return textarea.value + '\\n' + ' '.repeat(textarea.selectionStart) + '~'.repeat(textarea.selectionEnd-textarea.selectionStart) + '^'
13+
}
14+
`);
15+
16+
async function dumpErrorAndLogs(options) {
17+
testRunner.log('');
18+
testRunner.log(`Sending "${options.key}"`);
19+
if (options.commands && options.commands.length)
20+
testRunner.log(`with commands ${JSON.stringify(options.commands)}`)
21+
const message = await dp.Input.dispatchKeyEvent(options);
22+
if (message.error)
23+
testRunner.log('Error: ' + message.error.message);
24+
25+
testRunner.log(await session.evaluate(`selectedText()`));
26+
}
27+
28+
await dumpErrorAndLogs({
29+
type: 'keyDown',
30+
key: 'b',
31+
modifiers: 2,
32+
commands: ['selectAll'],
33+
windowsVirtualKeyCode: 66,
34+
code: 'KeyB'
35+
});
36+
await dumpErrorAndLogs({
37+
type: 'keyDown',
38+
windowsVirtualKeyCode: 8,
39+
key: 'Backspace',
40+
code: 'Backspace'
41+
});
42+
43+
await dumpErrorAndLogs({
44+
type: 'keyDown',
45+
key: 'c',
46+
text: 'c',
47+
unmodifiedText: 'c',
48+
commands: [],
49+
windowsVirtualKeyCode: 67,
50+
code: 'KeyC'
51+
});
52+
53+
testRunner.log('');
54+
testRunner.log('Canceling the next keydown');
55+
56+
await session.evaluate(`
57+
window.addEventListener('keydown', event => event.preventDefault(), {once: true});
58+
`);
59+
await dumpErrorAndLogs({
60+
type: 'keyDown',
61+
key: 'd',
62+
modifiers: 2,
63+
commands: ['selectAll'],
64+
windowsVirtualKeyCode: 67,
65+
code: 'KeyC'
66+
});
67+
await dumpErrorAndLogs({
68+
type: 'keyDown',
69+
key: 'e',
70+
text: 'e',
71+
unmodifiedText: 'e',
72+
commands: [],
73+
windowsVirtualKeyCode: 68,
74+
code: 'KeyE'
75+
});
76+
77+
testRunner.completeTest();
78+
});

0 commit comments

Comments
 (0)