Skip to main content
NativeInput is a CDP domain in every new session, pool browsers included. There’s nothing to turn on: connect to cdpUrl, open a page-level CDP session, send commands. It keeps its own cursor inside the browser: it doesn’t move the machine’s mouse or draw anything in the live view.

Quickstart

Any CDP client works, no SDK needed: send the commands on a session attached to the page. The domain name is case-sensitive (NativeInput, not nativeInput) and there’s no enable call. Input goes to the active tab; with several open, attach to the one you mean to drive. The examples below use the TypeScript input from the quickstart; in Python, input_session.send takes the same methods and parameters.

Mouse

There’s no single click command. Move, then press and release:
Always move before pressing a button or scrolling, especially after attaching or reconnecting. Viewport coordinates are the CSS pixels getBoundingClientRect() reports at default zoom, so an element’s center is a click target; the numbers in these examples are placeholders.

Keyboard

Text

Click into a field, then insert the text in one call. It goes to the focused text control as typed text, not as a DOM value or one key event per character. Any Unicode works, up to 16,384 UTF-8 bytes.
Release any held keys or buttons first; insertText refuses to run with a modifier down.

Keys and shortcuts

Keys are physical DOM codes: KeyA, Digit1, Enter, Tab, Escape, ArrowLeft, ShiftLeft, ControlLeft. Every keyDown needs its keyUp, and left and right modifiers are separate keys.
  • Keys follow a US layout plus the modifiers you hold. Pass key to override the character, for example { code: "KeyA", key: "ä" }. Use insertText for emoji.
  • A key repeats only when you send another keyDown, never on a timer.
  • Shortcuts reach Chrome’s own UI too: ControlLeft + KeyL focuses the address bar. Release the chord before inserting a URL, and wait for the navigation separately.
  • Keys and text go wherever Chrome’s focus is. After Ctrl+L or a click on the toolbar, focus stays in the address bar until you click into the page.
  • CapsLock is tracked per session and cleared by reset. NumLock, ScrollLock and IME or dead-key composition aren’t supported.

Coordinates and state

Every command answers { state }. getState reads it without changing anything.
Read width and height rather than assuming them, and read them again after a resize (window size sets the starting size). window coordinates aren’t screen coordinates, and screenshot pixels or CSS pixels under zoom or device emulation aren’t necessarily the same units. To show the cursor in your own viewer, place your indicator from the returned x and y, not your local mouse. If your image covers the same surface scaled but uncropped, that’s x * imageWidth / width and y * imageHeight / height. State isn’t pushed: read it from command responses or call getState.

One controller at a time

State belongs to the browser window, not your connection: a new connection starts from the last cursor position, coordinate space and focus. Each window takes input from one connection. The first command that sends input takes control; other connections can still call getState and see the same state with ownsControl: false. To hand over, call NativeInput.reset on the current controller or detach it. Reset releases anything held, clears CapsLock and gives up control, but keeps the cursor where it is: move again before the next click. Disconnecting does the same cleanup. Reset before moving a controlled tab to another window. Send commands one at a time and await each. A response means the input was dispatched, not that the page reacted: wait for navigation or elements with your framework’s usual waits.
A timeout doesn’t mean the input never arrived. Don’t retry clicks or text blindly; reconnect, check the page and getState, then decide what’s left to do.

Raw CDP

On the browser-level WebSocket, find the page with Target.getTargets, attach with Target.attachToTarget and flatten: true, and put the returned session ID on each command:
The reply carries the same id and sessionId, with the state in result.state; check for error first. On a WebSocket opened straight to the page target (cdpUrl with /devtools/browser/<id> replaced by /devtools/page/<targetId>), leave sessionId out. This CDP session ID is not the Driver sessionId you use with the API.

Commands

button is left, middle, right, back or forward. clickCount defaults to 1.

Troubleshooting

Native input drives the browser only, not the machine’s desktop. Native OS dialogs such as file pickers, pointer lock and raw relative mouse movement aren’t covered, and it isn’t guaranteed to look like physical hardware input to every site or widget.