Skip to content

feat(cua-driver-rs)(windows): bookmark-URL UIA bypass for page.execute_javascript - #1667

Merged
f-trycua merged 4 commits into
mainfrom
windows-page-bookmark-exec
May 23, 2026
Merged

feat(cua-driver-rs)(windows): bookmark-URL UIA bypass for page.execute_javascript#1667
f-trycua merged 4 commits into
mainfrom
windows-page-bookmark-exec

Conversation

@f-trycua

@f-trycua f-trycua commented May 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

Three commits. Adds a zero-config, no-launch-flag primary path for page.execute_javascript on Windows, exploiting an empirically validated UIA bypass in Chromium's bookmark URL fields. Falls through to the existing CDP path (env-var-discovered port) on any failure.

This is the durable answer to a problem that surfaced after PR #1666 merged:

  • Chrome 136 (May 2025) silently broke --remote-debugging-port against the user's real browser profile (cookies / sessions / extensions invalidated unless --user-data-dir points at a throwaway dir) — so the CDP-via-env-var path PR feat(cua-driver-rs): unify browser_eval + page into one cross-platform tool #1666 shipped is effectively broken for real-world use on modern Chromium.
  • We need a path that works on a normally-launched browser on Chrome 136+ without forcing the user to accept a throwaway profile.

The mechanism

Empirically confirmed on Edge 148.0.3967.70 AND Chrome 148.0.7778.179 during a PowerShell battery this session:

UIA ValuePattern::SetValue on the URL Edit element inside the browser's Edit-bookmark dialog accepts arbitrary javascript: URLs without scrubbing. The literal URL persists into the bookmark store. Invoking the bookmark from the bookmarks bar via UIA InvokePattern::Invoke executes the JS in the active tab's DOM context.

Why this works: Chromium's OmniboxView::OnAfterPossibleChange strips javascript: schemes only when a "came from user input" flag is set (paste / type / drop). UIA programmatic writes don't trip the flag — but more importantly here, the bookmark URL field doesn't even live on that code path. It's part of the bookmark-edit dialog which preserves the literal URL because bookmarklets (javascript: URLs as bookmarks) have been a documented Web feature since ~1995. Closing this would break a real feature, not just a security accident.

Result-readback channel

The injected JS wraps user code in:

javascript:(function(_orig){
  try {
    var __r = (function(){ <USER_JS> })();
    try { __r = JSON.stringify(__r); } catch(e) { __r = String(__r); }
    document.title = 'CUA:' + (__r || '');
    setTimeout(function(){ document.title = _orig; }, 500);
  } catch(e) {
    document.title = 'CUA_ERR:' + (e && e.message ? e.message : String(e));
    setTimeout(function(){ document.title = _orig; }, 500);
  }
})(document.title);

document.title mutations propagate to UIA's NamePropertyId on the window root within ~50ms; poll, strip the CUA: / CUA_ERR: prefix, JSON.parse the rest. Title is restored after 500ms.

Cross-Chromium support

Empirically validated on Chrome 148 (UIA AutomationId comparison vs Edge 148):

Surface Edge 148 Chrome 148 Strategy
Bookmarks bar entry ListItem Button + view_NNNN autogen id find_bookmark matches both via OrCondition(ListItem, Button)
Right-click context menu "Edit" "Edit" "Edit..." wait_for_menu_item does ellipsis-tolerant match
Edit dialog window "Edit favorite" "Edit bookmark" wait_for_window accepts a slice of names
Edit dialog URL field Name="Favorite URL" Name="Bookmark URL" find_edit_in_dialog accepts a slice of names (matches by Name + AutomationId fallback)
Favorites/Bookmarks bar toggle Ctrl+Shift+B Ctrl+Shift+B unified
Bookmarks bar name "Favorites bar" "Bookmarks bar" find_favorites_bar accepts both via case-insensitive match

No browser-specific code paths; one unified implementation that detects via UIA Name / ControlType variants.

Implementation

  • crates/platform-windows/src/tools/page_bookmark.rs (new, ~800 lines) — try_bookmark_exec driver + wrap_javascript URL builder + supporting UIA walkers + 3 unit tests on the wrapper shape.
  • crates/platform-windows/src/tools/page.rsexecute_javascript now calls try_bookmark_exec first, falls through to the existing CUA_DRIVER_CDP_PORT env-var CDP path on any failure. Error message updated to mention both paths.
  • crates/platform-windows/src/tools/mod.rs — module registration.
  • Skills/cua-driver-rs/WINDOWS.md — documents the new behaviour + the one-time bookmark-creation user gesture (see Out-of-scope below).

Out-of-scope follow-ups (deliberate)

  1. Auto-creation of the cua-driver-eval bookmark. First use requires the user to manually create a bookmark named exactly cua-driver-eval (any URL, gets overwritten on first call). The error message explains. Auto-creation requires UIA-driving a new tab → edge://favorites / chrome://bookmarks → Add-bookmark button → fill the Add dialog → close the tab. This is the most fragile part of the spec (multiple dialog states, browser-specific entry points) and was deferred to keep this PR focused.
  2. Live UIA integration test against a running browser. Currently coverage is wrap-shape only via 3 unit tests. Real end-to-end would need a Windows CI runner that can launch a browser; that's its own infra change.
  3. Firefox. Different UIA structure; CDP-via-flag stays as the fallback there (and Firefox doesn't have the Chrome 136 issue).

Test plan

  • cargo build --release -p cua-driver clean (0 warnings) on Windows
  • cargo test --release -p platform-windows page_bookmark green (3/3 new tests)
  • cargo test --release -p cua-driver --test mcp_protocol_test green (28/28)
  • Smoke on Edge 148 with the user's pre-created cua-driver-eval bookmark
  • Smoke on Chrome 148 with the user's pre-created cua-driver-eval bookmark
  • Reviewer verifies the CDP fallback path still triggers when bookmark exec fails (cross-platform cargo check for mcp_protocol_test's expectations)

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced dual-path JavaScript execution for Windows: bookmarks as primary method with automatic fallback to Chrome DevTools Protocol for improved reliability and flexibility.
    • Enhanced error messaging with actionable guidance when JavaScript execution encounters issues.
  • Documentation

    • Added Windows platform documentation detailing browser instance text extraction, DOM query capabilities, and JavaScript execution workflows.

Review Change Stack

f-trycua and others added 3 commits May 23, 2026 19:25
…_javascript

Adds tools/page_bookmark.rs implementing try_bookmark_exec() — a
zero-config path for running JavaScript in a Chromium-family browser
on Windows. The primitive UIA-edits a `cua-driver-eval` bookmark's
URL field to a javascript: bookmarklet, invokes the bookmark via
InvokePattern, and reads the result back from document.title.

Wired into WindowsPageBackend::execute_javascript as a prefix on the
existing CDP path: bookmark exec is tried first, any failure logs at
DEBUG and falls through to the CUA_DRIVER_CDP_PORT-based CDP fallback
unchanged. A process-wide mutex serialises bookmark-exec calls so
concurrent invocations don't race on the single bookmark URL.

The wrap_javascript() helper produces a try/catch IIFE that
JSON.stringifies the result and restores the original tab title
500 ms after execution. Three unit tests cover the wrapper shape,
newline stripping, and the title-suffix parser.

Why bookmark exec: Chromium scrubs `javascript:` URLs from the
omnibox but not from bookmarks, because bookmarklets are a
documented Web-platform feature. Mechanism empirically validated on
Edge 148.0.3967.70.

Auto-creation of the bookmark (drive omnibox to edge://favorites →
click Add favorite → fill dialog) is not yet wired up; users create
the bookmark manually for now (any URL — the driver overwrites it).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… exec

Adds a Windows-flavored section to WINDOWS.md describing the four
`page` actions and the two-tier execute_javascript dispatch
(bookmark-URL UIA bypass first, CDP fallback second). Spells out
the requirements for the bookmark path (a pre-existing
`cua-driver-eval` bookmark on the Favorites bar) and the
concurrency rule (process-wide serialisation on the bookmark URL).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…+ Chrome)

Extend the bookmark-URL UIA bypass to work on Chrome 148 alongside
Edge 148, based on an empirical UIA-tree comparison battery run this
session.

Patches:

1. `find_bookmark` — accept ListItem OR Button ControlType (OrCondition).
   Edge surfaces favorites-bar entries as ListItem; Chrome surfaces them
   as Button with an autogenerated AutomationId. Filter by Name afterward
   so we don't pick up the overflow chevron / "Add favorite" entry.

2. `wait_for_window` + `wait_for_menu_item` — accept a slice of names
   instead of a single name, with case-insensitive + substring +
   ellipsis-tolerant matching. Edge's right-click menu shows "Edit";
   Chrome shows "Edit...". Edge's edit dialog is "Edit favorite"; Chrome's
   is "Edit bookmark".

3. Edit-dialog URL field selector — match `"Favorite URL"` (Edge),
   `"Bookmark URL"` (Chrome), or "URL" (Chrome's chrome://bookmarks
   add-flow Edit, which has Name="URL" and AutomationId="input" — Name
   wins because find_edit_in_dialog's matching is case-insensitive +
   substring).

Build clean (0 warnings on x86_64-pc-windows-msvc); the 3
page_bookmark unit tests still pass.

Auto-creation of the cua-driver-eval bookmark stays out of scope —
covered by the existing actionable error message. Manual one-time
setup: any browser, any URL, name it `cua-driver-eval`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@vercel

vercel Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Ignored Ignored Preview May 23, 2026 7:45pm

Request Review

@coderabbitai

coderabbitai Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ef26b6ed-4012-4c43-80a9-53c6cc39210a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements a Windows-specific JavaScript execution bypass for the CUA driver: when CDP is unavailable, code first attempts to execute JavaScript via a UI Automation–driven bookmarklet in Edge/Chromium, falling back to CDP when bookmark execution fails. The change includes full UIA integration, result marshaling, mutex serialization for concurrency safety, and comprehensive documentation.

Changes

Windows JavaScript Execution via UI Automation Bookmarklet

Layer / File(s) Summary
Documentation and Module Integration
libs/cua-driver-rs/Skills/cua-driver-rs/WINDOWS.md, libs/cua-driver-rs/crates/platform-windows/src/tools/mod.rs, libs/cua-driver-rs/crates/platform-windows/src/tools/page.rs
Documented the page tool, execute_javascript two-tier dispatch (bookmark-first, then CDP fallback), and the rationale for bookmark-based execution. Declared the Windows-only page_bookmark module and updated page.rs to show the new control flow calling try_bookmark_exec before CDP.
Public Entry Points and Wrapper
libs/cua-driver-rs/crates/platform-windows/src/tools/page_bookmark.rs (lines 1–149)
Exported async try_bookmark_exec that coordinates bookmark execution with a process-wide mutex and spawn_blocking, and wrap_javascript that embeds user code into a javascript: URL, capturing results/errors via document.title.
Core Execution and Window Initialization
libs/cua-driver-rs/crates/platform-windows/src/tools/page_bookmark.rs (lines 151–279)
Implements the blocking execution flow: COM/UIA init, window resolution by PID, favorites bar toggling, bookmark lookup, URL update, invocation, and completion polling. Includes COM apartment handling and HWND validation.
Favorites Bar and Bookmark Navigation
libs/cua-driver-rs/crates/platform-windows/src/tools/page_bookmark.rs (lines 280–387)
Helpers to discover the favorites bar by Name or control-type fallback, toggle visibility via synthesized keys, locate bookmark items by combining control types with name matching, and safely extract UIA strings.
Dialog Manipulation and URL Update
libs/cua-driver-rs/crates/platform-windows/src/tools/page_bookmark.rs (lines 388–651)
Core UIA driving logic to right-click the bookmark, wait for the Edit menu, open the dialog, locate the URL edit field by Name/AutomationId, update the javascript: URL, invoke Save/Done, and provide UIA pattern helpers (ValuePattern, InvokePattern).
Active Tab Detection and Marker Polling
libs/cua-driver-rs/crates/platform-windows/src/tools/page_bookmark.rs (lines 652–737)
Active tab detection via SelectionItemPattern with fallback to the first tab, and marker-polling that waits for the tab title to include CUA: or CUA_ERR: prefix and extracts the payload up to Chromium's title suffix.
Unit Tests
libs/cua-driver-rs/crates/platform-windows/src/tools/page_bookmark.rs (lines 739–786)
Tests for wrap_javascript structure, newline stripping, and marker extraction with Chromium title variant handling.

🎯 4 (Complex) | ⏱️ ~45 minutes

🐰 A bookmark leaps where CDP may fail,
UIA guides through dialog's tale,
Mutex guards the async dance,
Document.title holds results' chance,
JavaScript flows, no relaunch need!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main feature: a bookmark-URL UIA bypass mechanism for Windows page.execute_javascript, which is the primary change across all modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch windows-page-bookmark-exec

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@libs/cua-driver-rs/crates/platform-windows/src/tools/page_bookmark.rs`:
- Around line 735-736: The current marker extraction uses after.find(" - ")
which stops at the first " - " and corrupts payloads that legitimately contain
that sequence; change the logic in the page bookmark parsing to locate the
separator from the end (use after.rfind(" - ") or equivalent) so only the final
delimiter is used when computing payload_end, and then return
after[..payload_end].to_owned() as before; update the code handling payload_end
and the variable after in the function that performs marker extraction to use
the last-occurrence search.

In `@libs/cua-driver-rs/Skills/cua-driver-rs/WINDOWS.md`:
- Around line 618-620: The docs list "select" as supported for query_dom but the
backend mapping is missing; either remove "select" from WINDOWS.md or add a
mapping in the backend by updating control_type_for_tag in page.rs to map the
"select" tag to the appropriate UIA control type (e.g., ComboBox/List
equivalent), and update any unit tests or docs to keep behavior and
documentation consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4f916caf-b8f0-4b4f-932b-ed086ecfa63c

📥 Commits

Reviewing files that changed from the base of the PR and between 94f23d0 and 719a27f.

📒 Files selected for processing (4)
  • libs/cua-driver-rs/Skills/cua-driver-rs/WINDOWS.md
  • libs/cua-driver-rs/crates/platform-windows/src/tools/mod.rs
  • libs/cua-driver-rs/crates/platform-windows/src/tools/page.rs
  • libs/cua-driver-rs/crates/platform-windows/src/tools/page_bookmark.rs

Comment on lines +138 to +140
var __r = (function(){{ {user_js} }})();\
try {{ __r = JSON.stringify(__r); }} catch(e) {{ __r = String(__r); }}\
document.title = 'CUA:' + (__r === undefined ? '' : __r);\

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In JavaScript, what is the return value of (function(){ 1 + 1 })()and how does it differ fromeval("1 + 1")?

💡 Result:

Return value of (function(){ 1 + 1 })(): 2 What eval("1 + 1") returns: 2 (as the completion value of evaluating the expression) How they differ: 1) Evaluation mechanism/inputs - The IIFE is a function call where the code 1 + 1 is part of the already-parsed program; execution is straightforward and returns the function’s return value. - eval("1 + 1") takes source code as a string, parses it as a script, executes it, and returns its completion value [1]. 2) Scope/privileges (important practical difference) - eval() executes the given string with the privileges of the caller and can access the scope where it was invoked (i.e., it is “dangerous” and differs from simply running already-written code) [2]. - The IIFE runs like normal JavaScript code in its own function scope; it doesn’t require parsing arbitrary strings at runtime. So for these particular inputs, both evaluate the expression and produce the numeric result 2, but eval does runtime parsing/execution from a string and has special scope/security behavior, while an IIFE is just a normal function that returns a value [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
node -e "console.log('(function(){ 1+1 })() =', (function(){ 1+1 })()); console.log('eval(\"1+1\") =', eval('1+1')); console.log('(function(){ 1+1; })() =', (function(){ 1+1; })()); console.log('(function(){ return 1+1 })() =', (function(){ return 1+1 })());"

Repository: trycua/cua

Length of output: 172


🌐 Web query:

JavaScript function body without return last expression return value (function(){1+1})() returns undefined

💡 Result:

Result: (function{1+1}) returns undefined. Explanation (what happens in JS): 1) The function body contains the expression 1+1, but it is not preceded by a return statement. 2) If execution of a function reaches the end without encountering a return statement (or without a return expression), JavaScript returns undefined by default [1]. 3) The ECMAScript specification defines that a return statement with no expression (i.e., return;) yields a return value of undefined [2]. In your case there is no return statement at all, so execution “falls off the end” and the effective return value to the caller is undefined [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the exact wrapper code around the referenced lines
FILE="libs/cua-driver-rs/crates/platform-windows/src/tools/page_bookmark.rs"
if [ -f "$FILE" ]; then
  echo "---- $FILE (lines 110-170) ----"
  nl -ba "$FILE" | sed -n '110,170p'
fi

# Find marker extraction + wrapper helpers and show their definitions
echo "---- Searching for extract_marker / wrap_javascript ----"
rg -n "extract_marker|wrap_javascript|try_bookmark_exec|user_js" "$FILE" || true

# Locate unit tests in the repo and show the relevant ones
echo "---- Searching for tests referencing bookmark/page_bookmark ----"
rg -n "page_bookmark|wrap_javascript|extract_marker" libs/cua-driver-rs/crates/platform-windows/src/tools -S || true

# If tests are in a separate file/module, find them by name
rg -n "extract_marker" libs/cua-driver-rs -S || true

Repository: trycua/cua

Length of output: 211


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="libs/cua-driver-rs/crates/platform-windows/src/tools/page_bookmark.rs"

echo "---- $FILE (lines 120-170) ----"
if [ -f "$FILE" ]; then
  # line numbers without nl: use awk
  awk 'NR>=120 && NR<=170 {printf "%5d:%s\n", NR, $0}' "$FILE"
fi

echo "---- $FILE (around user_js_json / wrap_javascript) ----"
if [ -f "$FILE" ]; then
  rg -n "extract_marker|wrap_javascript|try_bookmark_exec|user_js" "$FILE" || true
fi

echo "---- Searching for tests referencing page_bookmark/wrapper helpers ----"
# Limit search to this crate first
rg -n "page_bookmark|wrap_javascript|extract_marker" libs/cua-driver-rs/crates/platform-windows -S || true

echo "---- Searching for JS wrapper string patterns ----"
rg -n "CUA:|JSON.stringify|document.title|function\\s*\\(\\s*\\{\\s*\\$\\{user_js\\}|function\\s*\\(\\s*\\{\\s*\\{user_js\\}\\s*\\}" libs/cua-driver-rs/crates/platform-windows/src/tools/page_bookmark.rs -S || true

Repository: trycua/cua

Length of output: 8154


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="libs/cua-driver-rs/crates/platform-windows/src/tools/page_bookmark.rs"

echo "---- extract_marker implementation (lines 700-790) ----"
awk 'NR>=700 && NR<=790 {printf "%5d:%s\n", NR, $0}' "$FILE"

echo "---- wrap_javascript tests (lines ~730-790) ----"
awk 'NR>=730 && NR<=790 {printf "%5d:%s\n", NR, $0}' "$FILE"

Repository: trycua/cua

Length of output: 6309


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="libs/cua-driver-rs/crates/platform-windows/src/tools/page_bookmark.rs"

echo "---- try_bookmark_exec docs (lines 60-120) ----"
awk 'NR>=60 && NR<=140 {printf "%5d:%s\n", NR, $0}' "$FILE"

Repository: trycua/cua

Length of output: 4271


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="libs/cua-driver-rs/crates/platform-windows/src/tools/page.rs"
echo "---- Searching for try_bookmark_exec call sites ----"
rg -n "try_bookmark_exec\\(" libs/cua-driver-rs/crates/platform-windows/src/tools -S

echo "---- Inspect page.rs around the call ----"
# show a reasonable window around the match
rg -n "try_bookmark_exec\\(" "$FILE" -S | cut -d: -f1 | head -n 5 | while read -r ln; do
  start=$((ln-20)); end=$((ln+40));
  awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {printf "%5d:%s\n", NR, $0}' "$FILE"
done

Repository: trycua/cua

Length of output: 3677


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find all call sites of execute_javascript (to see whether callers add `return`)
rg -n "execute_javascript\\(" libs/cua-driver-rs -S

# If there are JS evaluation request handlers building the JS string, inspect them too
rg -n "cdp\\.runtime\\.evaluate|runtime\\.evaluate|evaluate\\(.*javascript|bookmark_exec" libs/cua-driver-rs -S

# Also search for tests asserting behavior for expression vs statements
rg -n "1\\s*\\+\\s*1|return 1\\s*\\+\\s*1|eval\\(\"1\\s*\\+\\s*1\"|expression" libs/cua-driver-rs -S

Repository: trycua/cua

Length of output: 6367


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="libs/cua-driver-rs/Skills/cua-driver-rs/WINDOWS.md"
echo "---- WINDOWS.md around wrapper contract (620-690) ----"
awk 'NR>=620 && NR<=700 {printf "%5d:%s\n", NR, $0}' "$FILE"

FILE2="libs/cua-driver-rs/Skills/cua-driver-rs/WEB_APPS.md"
echo "---- WEB_APPS.md around execute javascript 1+1 (300-360) ----"
awk 'NR>=300 && NR<=360 {printf "%5d:%s\n", NR, $0}' "$FILE2"

Repository: trycua/cua

Length of output: 7048


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="libs/cua-driver-rs/crates/platform-windows/src/tools/page_bookmark.rs"

echo "---- try_bookmark_exec tail / return path (lines ~650-760) ----"
awk 'NR>=650 && NR<=760 {printf "%5d:%s\n", NR, $0}' "$FILE"

echo "---- try_bookmark_exec around title polling loop (lines ~180-260) ----"
awk 'NR>=180 && NR<=260 {printf "%5d:%s\n", NR, $0}' "$FILE"

Repository: trycua/cua

Length of output: 8778


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="libs/cua-driver-rs/crates/mcp-server/src/page.rs"
# show around the execute_javascript handling
rg -n "execute_javascript\\(|let js\\s*=|javascript\\s*:\\s*|Runtime\\.evaluate|bookmark_exec" "$FILE" -S
echo "---- snippet around line 220-270 ----"
awk 'NR>=210 && NR<=270 {printf "%5d:%s\n", NR, $0}' "$FILE"

Repository: trycua/cua

Length of output: 3388


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect macOS execute_javascript backend
FILE="libs/cua-driver-rs/crates/platform-macos/src/tools/page.rs"
echo "---- macOS execute_javascript (around execute_javascript) ----"
rg -n "async fn execute_javascript" "$FILE"
awk 'NR>=70 && NR<=140 {printf "%5d:%s\n", NR, $0}' "$FILE"

# Inspect Linux execute_javascript backend
FILE="libs/cua-driver-rs/crates/platform-linux/src/tools/page.rs"
echo "---- Linux execute_javascript (around execute_javascript) ----"
rg -n "async fn execute_javascript|execute_javascript" "$FILE"
awk 'NR>=90 && NR<=170 {printf "%5d:%s\n", NR, $0}' "$FILE"

# Inspect CDP evaluate usage (common semantics)
FILE="libs/cua-driver-rs/crates/mcp-server/src/cdp.rs"
echo "---- cdp.rs evaluate helper (around Runtime.evaluate) ----"
rg -n "Runtime\\.evaluate|expression" "$FILE"
awk 'NR>=1 && NR<=120 {printf "%5d:%s\n", NR, $0}' "$FILE"

Repository: trycua/cua

Length of output: 12344


Fix Windows bookmark JS wrapper completion semantics and prevent marker truncation for " - " in results

  • wrap_javascript sets var __r = (function(){ {user_js} })(); with no return, so expression-only JS (e.g. 1+1) yields undefined, producing document.title = 'CUA:' and an empty payload—unlike CDP/macOS execute_javascript behavior.
  • extract_marker cuts the payload at the first " - " after CUA:/CUA_ERR:; if the JSON-stringified result contains " - " (common in strings), it will be truncated before the Chromium suffix. Current tests only cover Chromium’s title suffix, not payloads containing " - ".
Suggested fix (for completion values)
- var __r = (function(){{ {user_js} }})();
+ var __r = (0, eval)({user_js_json});

Pass user_js_json as a JSON-escaped JS string literal from Rust (e.g., via serde_json::to_string(user_js)), so both return ... blocks and bare expressions are handled consistently.

Comment on lines +735 to +736
let payload_end = after.find(" - ").unwrap_or(after.len());
after[..payload_end].to_owned()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Marker extraction truncates valid payloads containing -.

At Line 735-Line 736, trimming at the first " - " will corrupt results like CUA:"a - b" (or any JSON string containing that delimiter). This can silently return wrong JS results.

Suggested fix
 fn extract_marker(s: &str, prefix: &str) -> String {
     let start = s.find(prefix).unwrap_or(0);
     let after = &s[start..];
-    // Trim trailing " - <browser>" if Chromium appended it.
-    let payload_end = after.find(" - ").unwrap_or(after.len());
-    after[..payload_end].to_owned()
+    // Trim only a trailing browser suffix, not payload content.
+    if let Some((left, right)) = after.rsplit_once(" - ") {
+        let right_lc = right.to_ascii_lowercase();
+        let looks_like_browser =
+            right_lc.contains("edge")
+                || right_lc.contains("chrome")
+                || right_lc.contains("chromium")
+                || right_lc.contains("brave")
+                || right_lc.contains("opera");
+        if looks_like_browser {
+            return left.to_owned();
+        }
+    }
+    after.to_owned()
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let payload_end = after.find(" - ").unwrap_or(after.len());
after[..payload_end].to_owned()
fn extract_marker(s: &str, prefix: &str) -> String {
let start = s.find(prefix).unwrap_or(0);
let after = &s[start..];
// Trim only a trailing browser suffix, not payload content.
if let Some((left, right)) = after.rsplit_once(" - ") {
let right_lc = right.to_ascii_lowercase();
let looks_like_browser =
right_lc.contains("edge")
|| right_lc.contains("chrome")
|| right_lc.contains("chromium")
|| right_lc.contains("brave")
|| right_lc.contains("opera");
if looks_like_browser {
return left.to_owned();
}
}
after.to_owned()
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/cua-driver-rs/crates/platform-windows/src/tools/page_bookmark.rs` around
lines 735 - 736, The current marker extraction uses after.find(" - ") which
stops at the first " - " and corrupts payloads that legitimately contain that
sequence; change the logic in the page bookmark parsing to locate the separator
from the end (use after.rfind(" - ") or equivalent) so only the final delimiter
is used when computing payload_end, and then return
after[..payload_end].to_owned() as before; update the code handling payload_end
and the variable after in the function that performs marker extraction to use
the last-occurrence search.

Comment on lines +618 to +620
- **`query_dom`** — CSS-selector → UIA `ControlType` match. Supports
simple tag selectors (`a`, `button`, `input`, `h1`-`h6`, `img`,
`li`, `p`, `span`, `select`), `tag#id`, `[role=…]`. **Does not**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

select is documented as supported, but the backend does not implement it.

Line 619-Line 620 list select as supported for query_dom, but control_type_for_tag in libs/cua-driver-rs/crates/platform-windows/src/tools/page.rs currently has no select mapping. Please either remove select from this list or add the mapping in code so docs and behavior match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/cua-driver-rs/Skills/cua-driver-rs/WINDOWS.md` around lines 618 - 620,
The docs list "select" as supported for query_dom but the backend mapping is
missing; either remove "select" from WINDOWS.md or add a mapping in the backend
by updating control_type_for_tag in page.rs to map the "select" tag to the
appropriate UIA control type (e.g., ComboBox/List equivalent), and update any
unit tests or docs to keep behavior and documentation consistent.

Three findings, all valid:

1. wrap_javascript silently returned undefined for CDP-style callers.
   The IIFE wrapper required users to write `return X` themselves —
   plain `document.title` got executed as an expression statement
   inside the function body, the IIFE returned undefined, and the
   readback channel emitted `CUA:` with an empty payload. CDP's
   `Runtime.evaluate` semantics (which the macOS page tool matches
   via Apple Events) treat the user JS as a script — last expression
   is the value. Switched the wrapper to `eval('<user_js>')`:
     - user JS is embedded as a single-quoted JS string literal
     - backslashes + single-quotes escaped before embedding
     - newlines still pre-replaced with spaces for one-line URL
   Now matches CDP semantics exactly.

2. extract_marker silently corrupted payloads containing ` - `.
   The previous `find(" - ")` truncation killed any payload with that
   sequence (e.g. CUA:"a - b" returned CUA:"a). Switched to
   `rsplit_once(" - ")` keyed on a known browser-name suffix list
   (edge / chrome / chromium / brave / arc / vivaldi / opera) — only
   strips the trailing browser tag, never touches payload content.

3. control_type_for_tag was missing `select`. WINDOWS.md listed it
   as supported but page.rs had no mapping, so `select` queries
   would fall through `parse_selector`'s "unparseable" guard and
   reject with a misleading error. Added `"select" =>
   UIA_ComboBoxControlTypeId` — correct HTML→UIA mapping for
   `<select>` elements, which Chromium surfaces as ComboBox in its
   UIA tree.

Test changes:
- Replaced wrap_javascript_emits_expected_shape's input from a
  function-body string to an expression, asserting eval('...') is in
  the output.
- Added wrap_javascript_escapes_single_quotes and
  wrap_javascript_escapes_backslashes for the new escaping path.
- Renamed extract_marker_handles_chromium_title_suffix →
  extract_marker_strips_chromium_suffix; added
  extract_marker_preserves_dash_in_payload covering the bug class.

Build clean (0 warnings); 6/6 page_bookmark unit tests pass; 28/28
mcp_protocol_test pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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