Skip to content

fix(browser): pierce shadow DOM lookups efficiently - #1103

Open
s-salamatov wants to merge 3 commits into
moltis-org:mainfrom
s-salamatov:pr-1100-shadow-dom-review-fix
Open

fix(browser): pierce shadow DOM lookups efficiently#1103
s-salamatov wants to merge 3 commits into
moltis-org:mainfrom
s-salamatov:pr-1100-shadow-dom-review-fix

Conversation

@s-salamatov

Copy link
Copy Markdown
Contributor

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

  • Adds reusable shadow-piercing helpers:
    • __mDeepFind(sel) for first-match lookup across document and open shadow roots.
    • __mDeepCollect(sel) for collecting matching elements across document and open shadow roots.
  • Routes snapshot extraction through __mDeepCollect(selector) instead of a flat document.querySelectorAll(selector).
  • Routes ref-based operations through __mDeepFind('[data-moltis-ref="N"]'):
    • find_element_by_ref
    • focus_element_by_ref
    • scroll_element_into_view
    • manager element scroll
    • wait(ref=...)
    • screenshot highlight lookup
  • Routes highlight cleanup through __mDeepCollect('[data-moltis-ref]') so highlighted elements inside open shadow roots are cleaned up too.
  • Fixes the unresolved review feedback from fix(browser): pierce open shadow roots in snapshot and ref lookups #1100: __mDeepCollect now performs one DOM walk per root using querySelectorAll('*') plus el.matches(sel), instead of walking each root twice with both querySelectorAll(sel) and querySelectorAll('*').
  • Additional self-review fix: wait(selector=...) now uses __mDeepFind(selector) instead of flat document.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 later click, type, scroll, wait, or highlight operations.

The first implementation of __mDeepCollect fixed 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 test el.matches(sel) and recurse into el.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-prod in /home/iayyje/moltis-custom:

  • cargo fmt --all -- --check
  • cargo test -p moltis-browser
    • 101 passed; 0 failed
  • cargo clippy -p moltis-browser --all-targets -- -D warnings

Notes

This PR includes the original #1100 change plus the review follow-up commit 76a7882a fix(browser): tighten shadow DOM element lookups.

resumeparseeval and others added 2 commits June 3, 2026 20:18
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-apps

greptile-apps Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds two shadow-piercing JavaScript helpers (__mDeepFind / __mDeepCollect) and routes all snapshot extraction, ref-based lookups, highlight operations, and selector waits through them, fixing visibility of interactive elements inside open web-component shadow roots.

  • __mDeepFind replaces flat document.querySelector across find_element_by_ref, focus_element_by_ref, scroll_element_into_view, the element-scroll path, screenshot highlights, and wait(ref=…) / wait(selector=…).
  • __mDeepCollect replaces document.querySelectorAll in EXTRACT_ELEMENTS_JS and remove_highlights, so highlighted elements inside shadow roots are cleaned up.
  • Both helpers use a single querySelectorAll('*') walk per root (addressing the prior double-traversal review feedback), with el.matches(sel) inline and shadow-root recursion in the same loop.

Confidence Score: 5/5

Safe 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.

Important Files Changed

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

Comment thread crates/browser/src/snapshot.rs
@s-salamatov
s-salamatov marked this pull request as ready for review June 4, 2026 22:09
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.

2 participants