Skip to content

fix(vscode): wire native file dialog for embedded-app Browse button#1235

Open
meetp06 wants to merge 1 commit into
rocketride-org:developfrom
meetp06:fix/RR-1011-dropper-browse-file-dialog
Open

fix(vscode): wire native file dialog for embedded-app Browse button#1235
meetp06 wants to merge 1 commit into
rocketride-org:developfrom
meetp06:fix/RR-1011-dropper-browse-file-dialog

Conversation

@meetp06

@meetp06 meetp06 commented Jun 11, 2026

Copy link
Copy Markdown

Summary

In the Dropper node UI, the Browse button inside the drop area did nothing — clicking anywhere else in the drop zone opened the picker, but the button itself was dead (#1011).

Root cause

The "Browse" button can't open an OS file picker from inside the sandboxed webview iframe, so it uses a host round-trip:

  1. DropZone Browse button → window.parent.postMessage({type:'requestFileDialog'}) (apps/dropper-ui/.../DropperContainer.tsx)
  2. The openLink webview bridge forwards it to the extension host: if (event.data.type === 'requestFileDialog') vscode.postMessage({type:'requestFileDialog'}) (ProjectProvider.ts:837)
  3. The bridge is ready to relay the response back to the iframe (ProjectProvider.ts:842)
  4. DropperContainer listens for nativeFilesSelected and adds the files

The host-side handler was the missing link. panel.webview.onDidReceiveMessage in openLink() only implemented requestPaste/copyText — there was no requestFileDialog case, and vscode.window.showOpenDialog is never called anywhere in the extension. So the forwarded request was received and silently dropped: no dialog, no nativeFilesSelected. Drag-and-drop and the drop-zone click kept working because those don't need the host (the drop bridge reads files in-browser; the click uses the in-iframe <input type=file>).

Fix

Add the missing requestFileDialog case to the host handler — open the native dialog, read the selected files, and post them back as nativeFilesSelected. It mirrors the existing clipboard and drag-drop bridges in the same place.

} else if (msg?.type === 'requestFileDialog') {
    const uris = await vscode.window.showOpenDialog({ canSelectMany: true, openLabel: 'Select' });
    if (!uris || uris.length === 0) return;
    const files = await Promise.all(uris.map(async (uri) => ({
        name: uri.path.split('/').pop() || 'file',
        type: '',
        lastModified: Date.now(),
        buffer: Array.from(await vscode.workspace.fs.readFile(uri)),
    })));
    panel.webview.postMessage({ type: 'nativeFilesSelected', files });
}

buffer is sent as number[] to survive webview message serialization; the shape ({name, type, lastModified, buffer}) matches exactly what the nativeFilesSelected listener consumes (new Uint8Array(f.buffer)).

Verification

  • Static trace confirms the full round-trip now completes end-to-end; the posted file shape matches the dropper-ui consumer exactly.
  • This completes a half-wired feature (the iframe request, the bridge forward, the bridge relay-back, and the iframe listener already existed — only the host handler was absent).
  • Not runtime-verified in-PR: building/running the VS Code extension wasn't possible in my environment, and there's no existing unit-test harness for ProjectProvider. A maintainer clicking Browse in the Dropper is the final check; CI covers TypeScript compilation.

Closes #1011

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added native file picker integration allowing users to select and import multiple files directly from VS Code at once.

…ocketride-org#1011)

The Dropper "Browse" button posts {type:'requestFileDialog'}, which the
openLink webview bridge forwards to the extension host. But the host's
onDidReceiveMessage only implemented clipboard paste/copy — it never
handled requestFileDialog. So the request arrived, no native dialog
opened, and nativeFilesSelected was never posted back: the button did
nothing, while clicking elsewhere in the drop zone (the in-iframe
<input type=file>) still worked. That matches the report exactly.

Add the missing requestFileDialog case: open vscode.window.showOpenDialog,
read the selected files, and post them back as nativeFilesSelected (buffers
as number[] so they survive webview message serialization). Mirrors the
existing clipboard and drag-drop bridges in the same handler.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

ProjectProvider's webview-to-host message bridge now handles a new requestFileDialog message type that opens VS Code's native file picker, reads the selected files from the workspace filesystem, serializes their contents as number arrays for transport, and sends them back to the webview as nativeFilesSelected. Errors during dialog or file read operations are caught and logged.

Changes

File Dialog Webview-to-Host Handler

Layer / File(s) Summary
File dialog request handler
apps/vscode/src/providers/ProjectProvider.ts
ProjectProvider.openLink adds handling for requestFileDialog messages: invokes vscode.window.showOpenDialog with multi-select, reads each chosen file via vscode.workspace.fs.readFile, converts buffers to number[] for serialization, posts nativeFilesSelected response back to webview, and logs any dialog or read errors.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

  • rocketride-org/rocketride-server#293: The webview-side requestFileDialog message emission and handling of the nativeFilesSelected response that this PR's host-side handler serves.

Suggested labels

module:vscode, module:ui

Suggested reviewers

  • jmaionchi
  • stepmikhaylov
  • Rod-Christensen

Poem

🐰 A dialog opens, files appear,
The Browse button's doubts now clear,
From webview whispers to VS Code's call,
Buffers serialized, none left behind at all! 📁✨

🚥 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 accurately describes the main change: wiring the native file dialog for the embedded-app Browse button, which is the primary objective of this PR.
Linked Issues check ✅ Passed The PR fully implements the required fix: it adds requestFileDialog handling in ProjectProvider.openLink to open vscode.window.showOpenDialog and post selected files back to the webview, directly addressing issue #1011.
Out of Scope Changes check ✅ Passed All changes in ProjectProvider.ts are directly scoped to implementing the requestFileDialog handler to resolve issue #1011; no out-of-scope modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@github-actions github-actions Bot added the module:vscode VS Code extension label Jun 11, 2026
@github-actions

Copy link
Copy Markdown
🤖 Internal: Discord sync marker

Auto-managed by the Discord notification workflow. Stores the linked Discord message ID. Do not edit or delete.

@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: 1

🤖 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 `@apps/vscode/src/providers/ProjectProvider.ts`:
- Around line 861-882: The host currently honors msg.type ===
'requestFileDialog' from any iframe and returns local file bytes via
panel.webview.postMessage, allowing arbitrary embedded origins (via openLink())
to trigger file selection and exfiltrate files; update ProjectProvider's message
handler to validate the message origin against a whitelist of trusted iframe
origins before handling 'requestFileDialog' (and similarly guard forwarding code
in the inline bridge script), e.g., check the source/origin of the incoming
webview message or an allowlist stored on the ProjectProvider instance and only
call vscode.window.showOpenDialog and
panel.webview.postMessage({type:'nativeFilesSelected',...}) when the origin
matches a trusted entry; follow repository ESLint/Prettier conventions when
adding the guard and reuse existing symbols like openLink(), panel.webview, and
the requestFileDialog/nativeFilesSelected message types.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4652d0ea-243f-4b6f-a21a-5b3328fce4bd

📥 Commits

Reviewing files that changed from the base of the PR and between 1995c64 and 9e6bcc1.

📒 Files selected for processing (1)
  • apps/vscode/src/providers/ProjectProvider.ts

Comment on lines +861 to +882
} else if (msg?.type === 'requestFileDialog') {
// The embedded app's "Browse" button can't open an OS file picker from
// inside the sandboxed iframe, so it posts {type:'requestFileDialog'} up to
// the bridge, which forwards it here. Open the native dialog on the host,
// read the chosen files, and post them back as {type:'nativeFilesSelected'};
// the bridge relays that into the iframe. Buffers go as number[] so they
// survive webview message serialization.
try {
const uris = await vscode.window.showOpenDialog({ canSelectMany: true, openLabel: 'Select' });
if (!uris || uris.length === 0) return;
const files = await Promise.all(
uris.map(async (uri) => ({
name: uri.path.split('/').pop() || 'file',
type: '',
lastModified: Date.now(),
buffer: Array.from(await vscode.workspace.fs.readFile(uri)),
}))
);
panel.webview.postMessage({ type: 'nativeFilesSelected', files });
} catch (error) {
this.logger.error(`[ProjectProvider] Native file dialog failed: ${error}`);
}

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

Gate native file-dialog bridging to trusted iframe origins.

At Line 861, the host accepts requestFileDialog solely by message type. In this flow, openLink() can embed arbitrary URLs, so any embedded origin can request local file selection and receive file bytes back through nativeFilesSelected. This expands local-file exfiltration capability beyond the Dropper origin.

Suggested hardening
@@
-		panel.webview.onDidReceiveMessage(async (msg) => {
+		const trustedOrigin = (() => {
+			try {
+				return new URL(url).origin;
+			} catch {
+				return '';
+			}
+		})();
+		panel.webview.onDidReceiveMessage(async (msg) => {
@@
-			} else if (msg?.type === 'requestFileDialog') {
+			} else if (msg?.type === 'requestFileDialog') {
+				if (msg?.origin !== trustedOrigin) {
+					this.logger.error(`[ProjectProvider] Blocked requestFileDialog from untrusted origin: ${String(msg?.origin)}`);
+					return;
+				}
@@

And in the inline bridge script where iframe messages are forwarded:

- if (event.data.type === 'requestFileDialog') vscode.postMessage({ type: 'requestFileDialog' });
+ if (event.data.type === 'requestFileDialog') vscode.postMessage({ type: 'requestFileDialog', origin: event.origin });

As per coding guidelines, apply repository ESLint/Prettier conventions when introducing this guard logic.

🤖 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 `@apps/vscode/src/providers/ProjectProvider.ts` around lines 861 - 882, The
host currently honors msg.type === 'requestFileDialog' from any iframe and
returns local file bytes via panel.webview.postMessage, allowing arbitrary
embedded origins (via openLink()) to trigger file selection and exfiltrate
files; update ProjectProvider's message handler to validate the message origin
against a whitelist of trusted iframe origins before handling
'requestFileDialog' (and similarly guard forwarding code in the inline bridge
script), e.g., check the source/origin of the incoming webview message or an
allowlist stored on the ProjectProvider instance and only call
vscode.window.showOpenDialog and
panel.webview.postMessage({type:'nativeFilesSelected',...}) when the origin
matches a trusted entry; follow repository ESLint/Prettier conventions when
adding the guard and reuse existing symbols like openLink(), panel.webview, and
the requestFileDialog/nativeFilesSelected message types.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

module:vscode VS Code extension

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dropper node UI: "Browse" button is unresponsive in file drop area

1 participant