Skip to content

fix(upload): support iframe --selector and hidden input[type=file] uploads - #725

Open
NianJiuZst wants to merge 5 commits into
Tencent:mainfrom
NianJiuZst:fix/iframe-hidden-file-upload
Open

NianJiuZst wants to merge 5 commits into
Tencent:mainfrom
NianJiuZst:fix/iframe-hidden-file-upload

Conversation

@NianJiuZst

Copy link
Copy Markdown
Collaborator

Summary / Why

Fixes file upload failures on iframe-hosted upload widgets that pair a visible trigger button with a hidden <input type="file"> (such as Layui's display: none !important .layui-upload-file), where users previously encountered selector_not_found, element not visible, or input_not_ready on hidden/occluded windows:

  1. --selector stopped at the top-level document: resolveBackendNode only queried DOM.getDocument({ depth: 0 }) + DOM.querySelector / DOM.querySelectorAll, missing same-origin iframes and cross-origin OOPIFs.
  2. Direct <input type="file"> targets required a visual click: handleUpload (mode = "input") unconditionally went through withInputReady + clickResolvedTarget -> resolveNodeGeometry, failing with element not visible on display: none file inputs (out of sync with checkActionTarget, which already exempted tool.upload from !facts.visible).
  3. OOPIF & cross-frame chooser edge cases: OOPIF upload targets did not enable the child Page domain before Page.setInterceptFileChooserDialog, and file-input-transaction.ts rejected same-target cross-frame Page.fileChooserOpened activations.
  4. Tool guidance: CLI/DSH upload parameter descriptions could nudge agents toward matching hidden input[type=file] refs rather than clicking the visible upload button @eN ref or targeting <input type="file"> directly via --selector.

Changes / What

  • Cross-frame --selector resolution (interaction.ts, waits.ts, cooperation/controller.ts):
    • Keep the zero-extra-RPC main-document DOM fast path when non-strict selectors match in the top document; when unmatched or when strictSelector verifies uniqueness, query child frames in getFrameGraph order via isolated-world deep node serialization (maxNodeDepth: 0, includeShadowTree: "none").
    • Forward getFrameGraph in waits.ts's probeCdp so wait-for-element --selector also works across iframes / OOPIFs, while preserving main-document scope for expect preconditions (toolName === "precondition").
    • Re-evaluate mainDocumentAction in CooperationController.run after resolving selectors so child-frame selectors enforce frameEpoch invalidation (document_changed, effect_state: "none"), while main-document selectors remain unaffected by unrelated child-frame navigations.
  • Direct <input type="file"> assignment (file-input-transaction.ts, upload.ts):
    • Detect <input type="file"> targets during arm_input_probe, validate connectedness, !disabled / !inert, and multiple vs file count, and commit directly via DOM.setFileInputFiles — skipping withInputReady and clickResolvedTarget (works on display: none !important inputs and occluded/hidden background tabs).
  • OOPIF & cross-frame chooser interception (file-input-transaction.ts):
    • Call Page.enable on child OOPIF targets (target.sessionId) before Page.setInterceptFileChooserDialog so Page.fileChooserOpened fires reliably (including dynamically created detached <input type="file"> pickers), and accept single same-target Page.fileChooserOpened events across same-origin frames.
  • Docs, Changelogs & CI (upload.rs, files.md, DSH plugin tools, CHANGELOG*.md, ci.yml):
    • Clarify CLI, skill, and DSH plugin tool/parameter descriptions, update paired bilingual changelogs, and register the new real-Chrome regression suite in .github/workflows/ci.yml.

Verification

  • Real-browser end-to-end tests (BSK_CLICK_CHROME against Google Chrome):
    • apps/extension/src/tools/__tests__/upload-iframe.browser.test.ts (same-origin iframe & cross-origin OOPIF Layui-style button @ref upload, hidden/background-tab direct --selector upload to display: none !important input[type=file], detached dynamic input[type=file] in OOPIF, same-origin cross-frame trigger, !multiple & disabled rejections, cross-frame wait-for-element / click / ambiguity checks, and cooperative frameEpoch invalidation)
    • apps/extension/src/tools/__tests__/soft-detach-click.browser.test.ts
  • Targeted unit & contract checks:
    • file-transfer.test.ts, file-transfer-readiness.test.ts, target-policy.test.ts, action-checks.test.ts
    • pnpm lint, pnpm --filter @browser-skill/extension compile, pnpm protocol:check, cargo fmt --all -- --check, cargo clippy -p bsk --bin bsk --locked -- -D warnings, node scripts/check-skill-bundles.mjs, node scripts/check-crate-skill.mjs, node scripts/check-dsh-package.mjs, python3 scripts/check-changelog-sync.py

…loads

Why:
Uploading files on iframe-hosted widgets that pair a visible trigger button
with a hidden `<input type="file">` (such as Layui's `display: none !important`
`.layui-upload-file`) failed across multiple layers:
1. `resolveBackendNode` only queried the top-level document via
   `DOM.getDocument({ depth: 0 })`, returning `selector_not_found` for any
   `--selector` inside same-origin `iframe`s or cross-origin OOPIFs.
2. `handleUpload` (`mode = "input"`) always routed through `withInputReady` +
   `clickResolvedTarget` -> `resolveNodeGeometry`, failing with `element not
   visible` (or `input_not_ready` on hidden/occluded windows) even when the
   resolved target was itself an `<input type="file">` (out of sync with
   `checkActionTarget`, which already exempted `tool.upload` from `!visible`).
3. Cross-origin OOPIF uploads did not enable the child `Page` domain before
   `Page.setInterceptFileChooserDialog`, and `file-input-transaction.ts`
   rejected same-target cross-frame `Page.fileChooserOpened` activations.
4. CLI/DSH upload target descriptions could mislead agents into hunting for
   hidden `input[type=file]` refs instead of clicking the visible upload button
   `@eN` ref or passing an `<input type="file">` selector directly.

What:
- Extend `resolveBackendNode` (and forward `getFrameGraph` in `waits.ts`'s
  `probeCdp`) to search child frames in `getFrameGraph` order via isolated-world
  deep node serialization when the main document misses or `strictSelector`
  checks cross-frame uniqueness, while keeping `expect` preconditions
  main-document-scoped.
- Update `CooperationController.run` to re-evaluate `mainDocumentAction` after
  resolving selectors so child-frame selectors enforce `frameEpoch` safety while
  main-document selectors remain unaffected by unrelated iframe navigations.
- Detect direct `<input type="file">` targets inside
  `uploadThroughActivatedFileInput`, validate connectedness, enabled/non-inert
  state, and `multiple`, and commit via `DOM.setFileInputFiles` without
  clicking or requiring visual renderer frames.
- Enable `Page.enable` on OOPIF upload targets and accept single same-target
  `Page.fileChooserOpened` events (covering detached dynamic file inputs and
  same-origin cross-frame triggers).
- Clarify CLI, skill, and DSH plugin descriptions, update bilingual changelogs,
  and add real-Chrome end-to-end coverage in `upload-iframe.browser.test.ts`
  wired into CI.
Why:
- Minimal CDP debugger mocks (e.g. tabs.test.ts) return an empty object for
  Page.getFrameTree, causing ChromiumCdp.getFrameGraph to throw after main-doc
  DOM.querySelector already reported nodeId=0, turning selector_not_found into
  cdp_failed.
- packages/dsh-plugin-browserskill/skill/SKILL.md was 4449 LF bytes, which
  exceeded the 4500-byte entry budget by 32 bytes under CRLF line endings.

What:
- Extract listChildFrames() in resolveBackendNode to fall back cleanly to an
  empty child-frame list on non-abort/non-timeout/non-attachment-change frame
  tree errors while still propagating AbortError, CdpReadTimeoutError, and
  CdpAttachmentChangedError.
- Trim DSH SKILL.md to 4398 LF bytes (4481 CRLF bytes <= 4500) and align
  deferred interaction-details.md / cooperation.md selector scope notes across
  both CLI and DSH skills.
…ling

Why:
- DSH SKILL.md was already 4495 CRLF bytes on main, leaving only 5 bytes under
  the 4500-byte cap; raising maxEntryBytes to 4600 accommodates iframe selector
  guidance cleanly across LF and CRLF checkouts without artificial wording cuts.
- resolveBackendNode outer catch converted AbortError during child-frame lookup
  to cdp_failed instead of cancelled, and strictSelector still probed child
  frames when the main document already had >1 match.
- A few wheel / untilVisible / MCP tool descriptions still said "main-document
  CSS selector" even though those paths resolve through resolveBackendNode /
  wait-for-element across accessible iframes.

What:
- Raise the DSH skill entry-point budget from 4500 to 4600 across skill
  validators, build-skill-content, check-dsh-package, and skill tests, and
  restore full wording in packages/dsh-plugin-browserskill/skill/SKILL.md.
- Return { code: "cancelled" } on AbortError in resolveBackendNode's outer
  catch and short-circuit strictSelector child-frame traversal once
  totalMatches > 1.
- Align remaining wheel, step/plan untilVisible, and MCP tool selector
  descriptions across DSH plugin and bsk CLI.
…e-upload

# Conflicts:
#	CHANGELOG.md
#	CHANGELOG.zh-CN.md

This branch has not been deployed

No deployments
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