Fix copying webview image via context menu (#292567)#320234
Open
Parasaran-Python wants to merge 1 commit into
Open
Fix copying webview image via context menu (#292567)#320234Parasaran-Python wants to merge 1 commit into
Parasaran-Python wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds support for copying images from the webview context menu by writing the image bitmap to the clipboard (with fallbacks), and updates the CSP to match the changed inline script.
Changes:
- Updated CSP
script-srchash for the modified inline script - Tracked the image element under the last context-menu invocation
- Implemented image-to-clipboard copy flow (canvas → refetch blob → copy URL)
e719746 to
bdfadde
Compare
Right-clicking an image in a webview and choosing "Copy" copied nothing. The
webview context menu "Copy" action runs `document.execCommand('copy')`, which
only copies the current selection; a right-clicked image has no selection, so
nothing was placed on the clipboard.
Remember the image the context menu was opened on, and when "Copy" is invoked
on it, write the image bitmap to the clipboard directly. The copy runs inside
the content frame's window (where the `clipboard-write` permission and origin
apply): it draws the image to a canvas and uses
`navigator.clipboard.write([new ClipboardItem(...)])`, falling back to
re-fetching the image bytes (for cross-origin/tainted images) and finally to
copying the image url as text. A short focus-retry loop handles the clipboard
API's requirement that the document be focused.
Also updates the inline-script CSP hash to match the new preload script.
bdfadde to
ed907af
Compare
Comment on lines
+111
to
+118
| const clipboard = frameWindow.navigator.clipboard; | ||
| if (!clipboard || typeof ClipboardItem === 'undefined') { | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| await clipboard.write([new ClipboardItem({ | ||
| 'image/png': new Promise((resolve, reject) => { |
Comment on lines
+1264
to
+1268
| // Remember if the context menu was opened on an image so that the "Copy" action | ||
| // can copy the image even when there is no active selection. | ||
| const targetElement = /** @type {HTMLElement | null} */ (e.target); | ||
| lastContextMenuImageTarget = /** @type {HTMLImageElement | undefined} */ (targetElement?.closest?.('img') ?? undefined); | ||
|
|
Comment on lines
+1356
to
+1364
| if (data === 'copy' && contentWindow && lastContextMenuImageTarget && lastContextMenuImageTarget.ownerDocument === contentDocument) { | ||
| const image = lastContextMenuImageTarget; | ||
| lastContextMenuImageTarget = undefined; | ||
| const selection = contentDocument.getSelection(); | ||
| if (!selection || selection.isCollapsed) { | ||
| copyImageToClipboard(contentWindow, image); | ||
| return; | ||
| } | ||
| } |
Comment on lines
+119
to
+122
| const canvas = frameDocument.createElement('canvas'); | ||
| canvas.width = image.naturalWidth; | ||
| canvas.height = image.naturalHeight; | ||
| const context = canvas.getContext('2d'); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #292567
Problem
Right-clicking an image in a webview and choosing Copy from the context menu copied nothing to the clipboard. The only workaround was to manually select the image first and then copy.
The webview context-menu Copy action runs
document.execCommand('copy'), which only copies the current selection. A right-clicked image creates no selection, soexecCommand('copy')succeeds but copies empty content — the image never reaches the clipboard.Fix
In the webview preload (
src/vs/workbench/contrib/webview/browser/pre/index.html):execCommand('copy').The copy runs inside the content frame's window, where the
clipboard-writepermission and the frame's origin apply. It draws the image to a canvas and usesnavigator.clipboard.write([new ClipboardItem(...)]), with fallbacks:image/png(primary).writeText(src)as a last resort.A short focus-retry loop handles the clipboard API's requirement that the document be focused (the copy can fire while focus is still returning to the webview). This mirrors the existing, proven image-copy implementation in the Markdown preview, generalized so every webview benefits with no extension changes.
The inline-script CSP hash in the preload is updated to match the new script content.
Testing
Verified with the reporter's reproduction extension (
vscode-image-copy-to-clipboard-webview): right-click image → Copy → paste now yields the image bitmap, without the select-first workaround. Also confirmed copying selected text and non-image right-clicks behave as before, and the webview loads with no CSP violations.