Dragging a component from the gallery into the canvas had no effect when the QuickMock app was running inside the VS Code extension on macOS. The drag preview followed the cursor correctly, but releasing over the canvas did not insert the shape.
The same flow worked correctly in:
- A standalone browser on macOS.
- The VS Code extension on Linux and Windows.
Root cause
The QuickMock editor inside the extension lives in a doubly-nested frame structure.
The drag-and-drop implementation uses pragmatic-drag-and-drop, which relies on the HTML5 native Drag and Drop API (dragover, dragenter, drop).
On macOS, Chromium routes drag events to the <iframe> element in the parent webview shell instead of into the iframe's own document. This means the gallery's dragstart originates inside the iframe, but during the drag operation the inner document never receives any tracking events.
pragmatic-drag-and-drop's drop targets therefore never activate, dropTargets is empty when the drag ends, and the callback that would insert the shape is never invoked.
This is a documented platform issue:
microsoft/vscode#193558 — "Only on macOS, some drag-and-drop interactions fail in webview" (open, labeled bug, help wanted, and webview).
microsoft/vscode#147148, microsoft/vscode#139111 — same family of issues, now closed.
microsoft/vscode#256444 — related drag interception across webviews.
- Chromium issues
251718 and 936299 — HTML5 drag events not delivered to cross-origin / nested iframes.
electron/electron#... — specific drag handling issues in Electron webviews (CoreDragCreate).
VS Code maintainers have explicitly stated they do not currently plan to fix it, so the workaround has to live on the consumer side.
How the diagnosis was confirmed
We instrumented every layer with capture-phase sniffers:
window
document
document.body
- The canvas drop target inside the iframe
- A parallel sniffer in the webview shell
During a failing drag operation on macOS:
- The iframe's document received no
dragover, dragenter, or drop events. Only dragstart and the terminal dragend were observed.
- The webview shell received the entire stream of
dragover events with:
target: 'IFRAME'
- Correct cursor coordinates
This confirmed that the events existed at the OS / shell level but were not being propagated into the iframe's content document.
It also demonstrated that the shell still had access to the drag stream, making it possible to bridge the missing events manually.
Fix
A small message-passing bridge was implemented between the webview shell and the inner iframe, scoped specifically to the gallery → canvas drop flow.
1. Drag lifecycle events
When a drag starts in the gallery, the iframe posts:
{ type: 'qm:drag-start' }
When the drag ends, it posts:
2. Shell-side drag interception
The shell tracks whether a gallery drag is active.
While active:
- The shell calls
preventDefault() on dragover (required for the browser to emit drop).
- On
drop, the shell posts:
{
type: 'qm:gallery-drop',
x,
y
}
back to the iframe, with coordinates translated relative to the iframe bounds.
3. Iframe-side drop reconstruction
A new hook inside the iframe listens for:
The hook:
- Converts the coordinates using the same logic as the existing
pragmatic-drag-and-drop path.
- Calls
addNewShape(...) directly.
This bypasses the missing native drag propagation while preserving the existing insertion logic and coordinate system.
Dragging a component from the gallery into the canvas had no effect when the QuickMock app was running inside the VS Code extension on macOS. The drag preview followed the cursor correctly, but releasing over the canvas did not insert the shape.
The same flow worked correctly in:
Root cause
The QuickMock editor inside the extension lives in a doubly-nested frame structure.
The drag-and-drop implementation uses
pragmatic-drag-and-drop, which relies on the HTML5 native Drag and Drop API (dragover,dragenter,drop).On macOS, Chromium routes drag events to the
<iframe>element in the parent webview shell instead of into the iframe's own document. This means the gallery'sdragstartoriginates inside the iframe, but during the drag operation the inner document never receives any tracking events.pragmatic-drag-and-drop's drop targets therefore never activate,dropTargetsis empty when the drag ends, and the callback that would insert the shape is never invoked.This is a documented platform issue:
microsoft/vscode#193558— "Only on macOS, some drag-and-drop interactions fail in webview" (open, labeledbug,help wanted, andwebview).microsoft/vscode#147148,microsoft/vscode#139111— same family of issues, now closed.microsoft/vscode#256444— related drag interception across webviews.251718and936299— HTML5 drag events not delivered to cross-origin / nested iframes.electron/electron#...— specific drag handling issues in Electron webviews (CoreDragCreate).VS Code maintainers have explicitly stated they do not currently plan to fix it, so the workaround has to live on the consumer side.
How the diagnosis was confirmed
We instrumented every layer with capture-phase sniffers:
windowdocumentdocument.bodyDuring a failing drag operation on macOS:
dragover,dragenter, ordropevents. Onlydragstartand the terminaldragendwere observed.dragoverevents with:target: 'IFRAME'This confirmed that the events existed at the OS / shell level but were not being propagated into the iframe's content document.
It also demonstrated that the shell still had access to the drag stream, making it possible to bridge the missing events manually.
Fix
A small message-passing bridge was implemented between the webview shell and the inner iframe, scoped specifically to the gallery → canvas drop flow.
1. Drag lifecycle events
When a drag starts in the gallery, the iframe posts:
When the drag ends, it posts:
2. Shell-side drag interception
The shell tracks whether a gallery drag is active.
While active:
preventDefault()ondragover(required for the browser to emitdrop).drop, the shell posts:back to the iframe, with coordinates translated relative to the iframe bounds.
3. Iframe-side drop reconstruction
A new hook inside the iframe listens for:
The hook:
pragmatic-drag-and-droppath.addNewShape(...)directly.This bypasses the missing native drag propagation while preserving the existing insertion logic and coordinate system.