trusted-input
Trusted input helpers that work on either platform — the documented entry point for keystrokes, pointer moves and clicks that Obsidian actually believes.
Untrusted input (element.dispatchEvent(new MouseEvent('click'))) is silently ignored by CodeMirror, by the CSS :hover engine and by every e.isTrusted guard in Obsidian, so a test driving it exercises nothing while still passing. A trusted event has to come from outside the renderer, and how it gets there is platform-specific: Electron’s webContents.sendInputEvent on desktop, a CDP injection round-tripped through the harness’s host channel on mobile.
This module internalizes that split, as every public-facing API here must: it stays cross-platform-loadable — no platform-only module is on its import graph — and each helper defers to a Platform-gated call-time dynamic import() of the arm that fits. So a consumer writes await clickElement({ element }) once and never a platform check.
The arms remain importable directly, for a caller making a deliberate platform commitment: desktop-trusted-input and mobile-trusted-input. Prefer this facade otherwise.
Every helper must be awaited. The desktop arm has nothing to wait for, but the mobile arm’s injection is a round-trip to the host, so a missing await lets the assertion run before the input has landed. The signature is the same on both platforms precisely so that a suite growing a mobile lane does not have to be rewritten.
Not every helper survives the port. moveMouse, hoverElement and unhoverElement throw on mobile: a touch screen has no persistent pointer and :hover has no touch equivalent. They throw rather than no-op, because a silent no-op is exactly the false confidence trusted input exists to end. Gate those on Platform.isDesktopApp, or drive the element with clickElement instead.
Every Obsidian window is its own target. A popout is a separate Electron web contents, so input sent to the main window never reaches it. pressKey, clickMouse and moveMouse take an optional window (the main window by default); the element and editor helpers use the window that owns their element or editor. Mobile has no popouts, so any window but the main one throws there.
Functions
Section titled “Functions”| Function | Description |
|---|---|
| clickElement | Clicks the center of an element using trusted input — Electron’s sendInputEvent on desktop, a CDP touch tap in the WebView on mobile. The element-relative counterpart of clickMouse, mirroring the moveMouse / hoverElement split. Use clickMouse directly when the point to click is not the element’s center — the markdown editor’s margin, for instance, lies inside cm.scrollDOM but outside .cm-sizer, so no element’s center lands on it. |
| clickMouse | Clicks at the given viewport coordinates using trusted input, so the renderer synthesizes a real click (or contextmenu, for the right button) with isTrusted === true. The low-level primitive: one click at one point, with no waiting for any effect — callers poll their own readiness signal. Prefer clickElement for element-relative clicks. A real context menu actually opens, so a suite driving a right click must close it (or remove the leftover .menu element) before the next test. On mobile the button model does not survive the port: touch has no buttons, so 'left' (the default) is a tap, 'right' is the long-press that opens Obsidian Mobile’s context menu, and 'middle' throws. |
| hoverElement | Moves the pointer to the center of an element using trusted pointer input, then polls until the element actually matches :hover. Desktop only — it throws on mobile, where :hover has no touch equivalent. |
| moveMouse | Moves the pointer to the given viewport coordinates using a trusted pointer move, so :hover rules genuinely apply. The low-level primitive: a single move that does not wait for any state to settle. Prefer hoverElement / unhoverElement for element-relative moves. Desktop only — it throws on mobile, which has no persistent pointer to move. |
| pressKey | Presses a single key (optionally with modifiers) using trusted keyboard input, firing the full real key pipeline — keydown → keypress → beforeinput → input → keyup. It is delivered to the window’s DOM-focused element, so the caller focuses the target first and then awaits the expected effect; the press itself polls for nothing. On mobile the accepted keys are the named ones ('Enter', 'Escape', 'Tab', 'Backspace', 'Delete', the arrows) plus single printable characters — anything else throws rather than pressing nothing. |
| typeIntoEditor | Types text into a CodeMirror editor using trusted keyboard input. Focuses the editor (caret to the end) and presses every code point of the text — the same trusted sequence a real user produces — so the text reaches the document only if the editor genuinely holds focus. Then it polls until the document reflects the input, or a bounded timeout elapses (the expected outcome when the editor is read-only or focus was stolen). |
| unhoverElement | Moves the pointer to a point just outside an element’s bounding box using a trusted pointer move, then polls until the element no longer matches :hover. The inverse of hoverElement. When an element spans the full viewport (no point outside its box is reachable), use moveMouse directly to move the pointer to a known empty coordinate instead. Desktop only — it throws on mobile, for the same reason hoverElement does. |