Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions webview-ui/src/components/chat/ChatTextArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ import { ZooCodeAuthBadge } from "./ZooCodeAuthBadge"
import { usePromptHistory } from "./hooks/usePromptHistory"
import { disabledChatControlClassName, enabledChatControlClassName } from "./chatControlStyles"

// Data transfer types that signal a file-oriented drag rather than an in-webview
// text selection drag. "Files" covers drags from the OS file manager; the
// remaining types are set by VS Code when dragging resources from the Explorer
// or editor tabs, in local and remote workspaces alike (see fillEditorsDragData
// in VS Code's workbench drag-and-drop code).
const FILE_DRAG_DATA_TRANSFER_TYPES = [
"Files",
"text/uri-list",
"ResourceURIs",
"application/vnd.code.files",
"application/vnd.code.uri-list",
]

const isFileDrag = (dataTransfer: DataTransfer): boolean =>
FILE_DRAG_DATA_TRANSFER_TYPES.some((type) => dataTransfer.types.includes(type))
Comment on lines +39 to +53

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two of these five types can never match a real drag in the webview, and the matching itself is case-sensitive against an API that lowercases.

  • Chromium exposes custom drag types in DataTransfer.types lowercased. VS Code's own containsDragType helper (vs/platform/dnd/browser/dnd.ts) lowercases both sides for exactly this reason, with the comment "somehow the types are lowercase". So types.includes("ResourceURIs") can never succeed.
  • The spelling is also stale: VS Code's DataTransfers.RESOURCES is "ResourceURLs", not "ResourceURIs" (verified in vs/base/browser/dnd.ts on current main and 1.96; this repo pins VS Code types at 1.100).
  • application/vnd.code.files is not a VS Code type. fillEditorsDragData sets text/plain, text/uri-list, application/vnd.code.uri-list (INTERNAL_URI_LIST), ResourceURLs, and CodeEditors; the internal constants are CodeDataTransfers.EDITORS = "CodeEditors" and CodeDataTransfers.FILES = "CodeFiles".

The fix still works today because Explorer/editor-tab drags also carry application/vnd.code.uri-list and text/uri-list, which happen to be all-lowercase and match exactly. But the two dead entries give false confidence — the new tests pass only because they synthesize exact-case payloads Chromium cannot produce — and acceptance would silently regress to the issue #2139 behavior if VS Code ever stops setting the uri-list types. Comparing case-insensitively (mirroring containsDragType) and using the real type names removes both risks:

Suggested change
// Data transfer types that signal a file-oriented drag rather than an in-webview
// text selection drag. "Files" covers drags from the OS file manager; the
// remaining types are set by VS Code when dragging resources from the Explorer
// or editor tabs, in local and remote workspaces alike (see fillEditorsDragData
// in VS Code's workbench drag-and-drop code).
const FILE_DRAG_DATA_TRANSFER_TYPES = [
"Files",
"text/uri-list",
"ResourceURIs",
"application/vnd.code.files",
"application/vnd.code.uri-list",
]
const isFileDrag = (dataTransfer: DataTransfer): boolean =>
FILE_DRAG_DATA_TRANSFER_TYPES.some((type) => dataTransfer.types.includes(type))
// Data transfer types that signal a file-oriented drag rather than an in-webview
// text selection drag. Chromium exposes custom drag types in DataTransfer.types
// lowercased (VS Code works around the same behavior in its containsDragType
// helper), so every entry here is lowercase. "files" covers drags from the OS
// file manager; the remaining types are set by VS Code when dragging resources
// from the Explorer or editor tabs, in local and remote workspaces alike (see
// fillEditorsDragData in VS Code's workbench drag-and-drop code).
const FILE_DRAG_DATA_TRANSFER_TYPES = [
"files",
"text/uri-list",
"resourceurls",
"codeeditors",
"codefiles",
"application/vnd.code.uri-list",
]
const isFileDrag = (dataTransfer: DataTransfer): boolean => {
const dragTypes = dataTransfer.types.map((type) => type.toLowerCase())
return FILE_DRAG_DATA_TRANSFER_TYPES.some((type) => dragTypes.includes(type))
}


interface ChatTextAreaProps {
inputValue: string
setInputValue: (value: string) => void
Expand Down Expand Up @@ -967,8 +983,11 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
className={cn("chat-text-area", !isEditMode && "relative", "flex", "flex-col", "outline-none")}
onDrop={handleDrop}
onDragOver={(e) => {
// Only allowed to drop images/files on shift key pressed.
if (!e.shiftKey) {
// Accept file drags from the OS file manager, the VS Code
// Explorer, and editor tabs without requiring Shift. In-webview
// text drags, such as moving a selection inside the input, keep
// the browser's native drop behavior unless Shift is held.
if (!e.shiftKey && !isFileDrag(e.dataTransfer)) {
setIsDraggingOver(false)
return
}
Expand Down
58 changes: 58 additions & 0 deletions webview-ui/src/components/chat/__tests__/ChatTextArea.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,64 @@ describe("ChatTextArea", () => {
expect(setInputValue).not.toHaveBeenCalled()
})

describe("dragover acceptance", () => {
// jsdom has no DragEvent constructor, so build a plain cancelable
// Event and attach the drag payload the React handler expects.
const fireDragOver = (element: Element, types: string[], shiftKey = false) => {
const event = new Event("dragover", { bubbles: true, cancelable: true })
Object.defineProperty(event, "dataTransfer", {
value: { types, files: [], dropEffect: "none" },
})
Object.defineProperty(event, "shiftKey", { value: shiftKey })
fireEvent(element, event)
return event
}

it("accepts VS Code Explorer file drags without holding Shift", () => {
const { container } = render(<ChatTextArea {...defaultProps} />)

const event = fireDragOver(
container.querySelector(".chat-text-area")!,
["text/plain", "ResourceURIs", "application/vnd.code.uri-list", "text/uri-list"],
false,
)

expect(event.defaultPrevented).toBe(true)
})

it("accepts OS file manager drags without holding Shift", () => {
const { container } = render(<ChatTextArea {...defaultProps} />)

const event = fireDragOver(container.querySelector(".chat-text-area")!, ["Files"], false)

expect(event.defaultPrevented).toBe(true)
})

it.each(["text/uri-list", "application/vnd.code.files"])("accepts %s drags without Shift", (type) => {
const { container } = render(<ChatTextArea {...defaultProps} />)

const event = fireDragOver(container.querySelector(".chat-text-area")!, [type], false)

expect(event.defaultPrevented).toBe(true)
})

it("keeps the browser's native behavior for plain text drags without Shift", () => {
const { container } = render(<ChatTextArea {...defaultProps} />)

const event = fireDragOver(container.querySelector(".chat-text-area")!, ["text/plain"], false)

expect(event.defaultPrevented).toBe(false)
})

it("still accepts plain text drags when Shift is held", () => {
const { container } = render(<ChatTextArea {...defaultProps} />)

const event = fireDragOver(container.querySelector(".chat-text-area")!, ["text/plain"], true)

expect(event.defaultPrevented).toBe(true)
})
})

describe("prompt history navigation", () => {
const mockClineMessages = [
{ type: "say", say: "user_feedback", text: "First prompt", ts: 1000 },
Expand Down
Loading