Skip to content

Page automation primitives: snapshot/read/click/type/paste + trusted input - #468

Open
ianu82 wants to merge 3 commits into
pr/browser-types-logicfrom
pr/browser-dom-input
Open

Page automation primitives: snapshot/read/click/type/paste + trusted input#468
ianu82 wants to merge 3 commits into
pr/browser-types-logicfrom
pr/browser-dom-input

Conversation

@ianu82

@ianu82 ianu82 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What

The two automation layers the bridge builds on (part 2 of 5) — everything that acts on a web page, before any window management exists.

browser-dom-tools.ts — scripts that run inside tabs:

  • Snapshot: interactive elements (links, buttons, inputs) as an indexed list, visibility-filtered, capped, with a per-snapshot version token so a later click can prove it's aimed at the same page state.
  • Read: readability-style text extraction (strips chrome, prefers article/main, capped).
  • Click/type by index: scroll-into-view + real el.click(); native value setter with input/change events so React-controlled fields work.
  • Paste: a synthetic ClipboardEvent that spreadsheet apps (verified against Google Sheets) split into cell ranges from TSV text.

browser-input.ts — trusted CDP input for canvas apps that ignore synthetic events: pure key/modifier resolution (with aliases like left/return), and click/keys/text dispatched via webContents.debugger using keyDown so default actions (caret movement, shortcuts) actually fire.

Verification

  • 19 unit tests covering selector sets, index clamping, staleness tokens, JSON-escaping safety in generated scripts, key tables, and the exact CDP command sequences.
  • Proven live during development on a real Google Sheet: paste landed as a cell range; trusted clicks selected the intended cells.

For QA

Still nothing to click in the UI — this is plumbing. The acceptance evidence is in the follow-up PRs that put it in front of you; if you'd like a preview, the final PR's e2e exercises these exact paths.

Two standalone, independently testable layers the bridge builds on:

- browser-dom-tools: the scripts executed inside tabs — interactive-
  element snapshot (indexed, visibility-filtered, with a per-snapshot
  staleness version), readable-text extraction, click/type by snapshot
  index (native value setter + input/change events), scroll, and the
  synthetic ClipboardEvent paste that splits TSV into spreadsheet cells.
- browser-input: trusted CDP input for canvas-rendered apps — key/modifier
  resolution (pure), click/keys/text via webContents.debugger with
  keyDown so default actions fire.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 319e5861a0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/browser/browser-dom-tools.ts Outdated
Comment on lines +70 to +71
const text = (el.innerText || el.value || el.getAttribute('aria-label')
|| el.getAttribute('placeholder') || el.getAttribute('title') || '').trim().slice(0, 120);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Stop returning password values in snapshots

When a visible <input type="password"> has a saved or typed value, this expression falls through to el.value and serializes the secret as the element text returned by /snapshot to the loopback agent bridge. Hidden inputs are skipped, but password inputs are not, so visiting an autofilled login page exposes credentials; omit or mask values for password fields and prefer labels/placeholders instead.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — password input values are masked in snapshots (5b86a10).

Comment thread src/main/browser/browser-dom-tools.ts Outdated
const STASH = ${stashRef(stash)};
const els = [];
const refs = [];
const nodes = document.querySelectorAll('a,button,input,textarea,select,[role],[onclick],summary');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply the scan cap before collecting elements

On pages with a very large number of matching nodes, querySelectorAll(...) constructs the full static NodeList before the SCAN_MAX loop guard runs, so the snapshot path still does unbounded selector work and allocation despite the cap. A hostile or just huge page with many buttons/links can still hang the renderer; traverse incrementally, such as with a TreeWalker or chunking, so the cap is enforced before collecting all matches.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — the walk itself is capped instead of collecting the full NodeList first (5b86a10).

ianu82 added 2 commits July 22, 2026 17:32
Review follow-up (one P1): a filled password input's value could be
serialized into /snapshot text and flow to the agent — passwords now
fall back to labels/placeholders. The snapshot also traverses with a
TreeWalker so the scan cap binds before any full NodeList is built on
hostile pages.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant