fix(browser): pierce shadow DOM lookups efficiently - #1103
Conversation
The browser tool located interactive elements with a flat
`document.querySelectorAll` (snapshot collection) and resolved refs with
`document.querySelector('[data-moltis-ref="N"]')`. Neither crosses shadow-DOM
boundaries, so elements rendered inside web components — e.g. Salesforce
Lightning login fields — were invisible to `snapshot` and unreachable by
`click`/`type`/`scroll`/`wait`/highlight. Against such pages the agent could
never see or fill the controls and would loop on empty DOM queries.
Add two shadow-piercing JS helpers, `__mDeepFind` (querySelector) and
`__mDeepCollect` (querySelectorAll), that recurse into open shadow roots, and
route every element lookup through them:
- snapshot element collection, find_element_by_ref, focus_element_by_ref,
scroll_element_into_view
- manager scroll, wait (ref branch), highlight_element, remove_highlights
Helpers are defined idempotently (`window.x = window.x || (...)`) so repeated
injection into the global eval scope doesn't throw a redeclaration error —
notably `wait()` re-evaluates its check on every poll. Closed shadow roots
(`mode: 'closed'`) cannot be pierced from page script and are skipped.
Verified end-to-end against PG&E's Salesforce-Lightning login: snapshot now
returns the username/password textboxes that flat queries missed.
Greptile SummaryThis PR adds two shadow-piercing JavaScript helpers (
Confidence Score: 5/5Safe to merge — all ref-based and selector-based lookups are consistently routed through the new shadow-piercing helpers, the helpers are idempotent, and the single-walk traversal is correct. Every call site previously using flat document.querySelector / document.querySelectorAll has been updated, and the window.x = window.x || fn guard makes repeated injection idempotent. Five new unit tests lock in the single-walk property and correct routing. No files require special attention.
|
| Filename | Overview |
|---|---|
| crates/browser/src/snapshot.rs | Adds DEEP_FIND_FN / DEEP_COLLECT_FN constants with single-walk shadow-piercing helpers; routes EXTRACT_ELEMENTS_JS and FIND_BY_REF_JS through them; adds four unit tests verifying idempotence, single-walk property, and correct routing. |
| crates/browser/src/manager.rs | Routes all manager-side ref and selector lookups (scroll, wait, highlight, remove_highlights) through the new deep helpers; adds a source-level regression test for the wait selector path. |
Reviews (2): Last reviewed commit: "fix(browser): avoid double traversal in ..." | Re-trigger Greptile
Summary
This PR is an alternative/update path for #1100 because I could not push a follow-up commit to the original PR head repository (
resumeparseeval/mycelium). It keeps the original behavior from #1100 and adds the review fixes on top.The browser snapshot and ref-based lookup paths now pierce open shadow roots so controls inside web components, such as Salesforce Lightning login fields, are visible to the browser tool and reachable by ref-based actions.
What changed
__mDeepFind(sel)for first-match lookup acrossdocumentand open shadow roots.__mDeepCollect(sel)for collecting matching elements acrossdocumentand open shadow roots.__mDeepCollect(selector)instead of a flatdocument.querySelectorAll(selector).__mDeepFind('[data-moltis-ref="N"]'):find_element_by_reffocus_element_by_refscroll_element_into_viewwait(ref=...)__mDeepCollect('[data-moltis-ref]')so highlighted elements inside open shadow roots are cleaned up too.__mDeepCollectnow performs one DOM walk per root usingquerySelectorAll('*')plusel.matches(sel), instead of walking each root twice with bothquerySelectorAll(sel)andquerySelectorAll('*').wait(selector=...)now uses__mDeepFind(selector)instead of flatdocument.querySelector(selector), so selector waits work consistently for open-shadow-root content.Root cause
The existing browser tool used flat DOM queries for both snapshot collection and several ref-resolution paths. Flat
document.querySelector*calls do not cross shadow DOM boundaries, so elements rendered inside open web-component shadow roots were invisible to snapshots and unreachable by laterclick,type,scroll,wait, or highlight operations.The first implementation of
__mDeepCollectfixed visibility but did two full traversals per root: one traversal to collect selector matches and another traversal to find shadow hosts. The review comment correctly pointed out that this is unnecessary. A single all-elements traversal can both testel.matches(sel)and recurse intoel.shadowRoot.User impact
Pages built with web components expose interactive controls to the browser tool when those controls live in open shadow roots. Agents can now see and operate on those elements through the normal snapshot/ref workflow instead of looping on empty or incomplete DOM results.
Closed shadow roots remain intentionally unsupported because page JavaScript cannot pierce them.
Validation
Ran on
sgn-mp-eu-1-prodin/home/iayyje/moltis-custom:cargo fmt --all -- --checkcargo test -p moltis-browser101 passed; 0 failedcargo clippy -p moltis-browser --all-targets -- -D warningsNotes
This PR includes the original #1100 change plus the review follow-up commit
76a7882a fix(browser): tighten shadow DOM element lookups.