Skip to content

[lexical-rich-text][lexical-plain-text] Bug Fix: Fix Firefox drag-and-drop by calling event.preventDefault() in dragover#8663

Open
sahiee-dev wants to merge 2 commits into
facebook:mainfrom
sahiee-dev:fix/firefox-dragover-prevent-default
Open

[lexical-rich-text][lexical-plain-text] Bug Fix: Fix Firefox drag-and-drop by calling event.preventDefault() in dragover#8663
sahiee-dev wants to merge 2 commits into
facebook:mainfrom
sahiee-dev:fix/firefox-dragover-prevent-default

Conversation

@sahiee-dev

Copy link
Copy Markdown
Contributor

Description

Per the HTML5 DnD spec, an element must call event.preventDefault() in its dragover handler to signal that it accepts drops, without it, the browser never fires the drop event. The native auto accept exception applies only to <textarea> and <input type="text">, not to contenteditable divs. Chrome papers over this with internal contenteditable handling; Firefox enforces the spec strictly.

Current behavior:

  • registerRichText's DRAGOVER_COMMAND handler only called event.preventDefault() when the drag target was a DecoratorNode. For plain text drags it did nothing, so Firefox silently refused the drop and $handleRichTextDrop never ran.
  • registerPlainText had no DRAGOVER_COMMAND handler at all, producing the same failure.
  • Both editors appeared to work in the existing e2e tests because TextDragDrop.spec.mjs dispatches .........
    dragstartdrop directly, bypassing dragover entirely, so the browser gate never came into play.

Changes:

  • registerRichText: add unconditional event.preventDefault() after the existing file transfer guard in DRAGOVER_COMMAND. The decorator specific call that was already there is now a harmless no op.
  • registerPlainText: add a new DRAGOVER_COMMAND handler with the same file transfer guard followed by event.preventDefault().
  • TextDragDrop.spec.mjs: add a test that dispatches a synthetic dragover with text/plain data and asserts event.defaultPrevented === true, covering the gap the existing tests could not catch.

Closes #8014

Test plan

Before

Drag and drop of selected text in a Lexical editor on Firefox: text stays in place, is not moved to the drop location. The drop event never fires because dragover never called preventDefault().

Automated: TextDragDrop.spec.mjs, new test dragover calls event.preventDefault() for text drags fails (returns false).

After

Drag-and-drop of selected text works in Firefox: text is cut from the source position and inserted at the drop location, matching Chrome behavior.

Automated: all five tests in TextDragDrop.spec.mjs pass across Chromium, Firefox, and WebKit.

…fault() in dragover to fix Firefox drag-and-drop (facebook#8014)

Per the HTML5 DnD spec, a contenteditable div must call event.preventDefault()
in its dragover handler to become a valid drop target — without it, the browser
never fires the drop event. Chrome ignores this gap; Firefox enforces it strictly.

registerRichText's DRAGOVER_COMMAND handler only called preventDefault() for
DecoratorNode targets. For plain text drags it did nothing, so Firefox silently
swallowed the drop and $handleRichTextDrop never ran.

registerPlainText had no DRAGOVER handler at all, producing the same failure.

Fix: add an unconditional event.preventDefault() in the rich-text handler (after
the existing file-transfer guard) and add a new DRAGOVER handler to plain-text
with the same guard. Also adds an e2e test that directly asserts dragover sets
event.defaultPrevented, covering the gap left by the existing tests which bypass
dragover by dispatching drop directly.
@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lexical Ready Ready Preview, Comment Jun 10, 2026 7:49am
lexical-playground Ready Ready Preview, Comment Jun 10, 2026 7:49am

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 10, 2026

@mayrang mayrang 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.

The decorator branch right after the new preventDefault is dead: line 1248 already calls preventDefault unconditionally, so the caretFromPoint / $isDecoratorNode block below it only re-calls it as a no-op. The // Won't work for DecoratorNode nor it's relevant comment no longer describes what runs either. Dropping the block and its comment would leave just the new line.

Separately, the new test asserts event.defaultPrevented === true on a synthetic dragover, but the existing dragSelectionToOffset helper (lines 49-71) only dispatches dragstart / drop / dragend — never dragover. If a future change re-breaks the gate, every existing drag test still passes; only the new one fails. Dispatching dragover inside that helper would cover the existing suite.

- Remove dead decorator-node block from rich-text DRAGOVER handler
- Embed defaultPrevented assertion in dragSelectionToOffset helper so
  all four drag-drop tests fail if the Firefox gate regresses
- Remove now-redundant standalone dragover preventDefault test
- Add comment on plain-text Files check explaining the dependency
  direction constraint (cannot import eventFiles from rich-text)
@sahiee-dev

Copy link
Copy Markdown
Contributor Author

Addressed: removed the dead decorator block and its stale comment. Also wired the dragover dispatch into dragSelectionToOffset with a defaultPrevented guard, so all four existing drag tests now cover the gate rather than just the new one.

@mayrang

mayrang commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

On the Before behavior — I instrumented the current playground.lexical.dev in Firefox (no fix from this PR applied) and ran a text-selection drag-and-drop. Bubble-phase capture (lexical handlers had already fired):

[dragover bubble] defaultPrevented: false, types: [...'application/x-lexical-editor', 'application/x-lexical-drag']
[drop bubble] defaultPrevented: true

So drop fires even with dragover defaultPrevented=false, and the text moves correctly. Firefox accepts the drop on contenteditable here without the spec-required dragover preventDefault; the lexical DROP_COMMAND handler then preventDefaults at drop time. The PR's user-visible Before doesn't reproduce on the current build.

Could you share the Firefox version + setup where the original report reproduces?

@mayrang

mayrang commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Following up on the Before check above, on the test side: the new dragover dispatch + throw could catch a hypothetical future regression (if someone removes the preventDefault call), but it doesn't function as a regression test for the user-visible issue #8014 actually reports (Firefox users unable to drag text). The existing tests already pass without this PR on the current Firefox build, so what the throw guards is the spec-compliance line itself — not the user-facing failure #8014 describes.

@potatowagon potatowagon 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.

Reviewed by Navi (Tater Thoughts Bobblehead) on behalf of @potatowagon.

LGTM — Firefox drag-and-drop fix.

What I verified:

  • Two commits: (1) adds event.preventDefault() in DRAGOVER_COMMAND for both plain-text and rich-text editors, (2) refines the e2e test with an explicit dragover dispatch + defaultPrevented assertion as a regression guard.
  • Rich-text handler simplified from conditional (DecoratorNode-only) preventDefault to unconditional, which matches the HTML5 DnD spec — dragover must be cancelled for drop to fire.
  • Plain-text handler inlines the file-drop guard correctly.
  • CI all green (core unit 22.x + 24.x, browser, integrity, e2e canary chromium, CLA, Vercel).

No concerns. Safe to land.

@sahiee-dev

Copy link
Copy Markdown
Contributor Author

@mayrang Good catch on the version gap. The original report was against v0.38.2, it's worth checking whether #8373 already fixed the user visible symptom as a side effect, which would explain why the issue stayed open without being explicitly closed.
If that's the case, happy to reposition this as spec-compliance hardening or close if the maintainers consider it covered.

@sahiee-dev

Copy link
Copy Markdown
Contributor Author

Checked git history: #8373 already fixed the user-visible symptom. Before that PR the DROP_COMMAND handler returned true without actually processing the drop, so text never moved regardless of browser. That was the real bug, not the dragover gate.

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

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Drag and drop of text is broken in Firefox

3 participants