-
Notifications
You must be signed in to change notification settings - Fork 264
Add explainer for Preserving dropEffect Values from dragover to drop Events #1195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
roraja
wants to merge
3
commits into
MicrosoftEdge:main
Choose a base branch
from
roraja:roraja/preserveDropEffect
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 2 commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,280 @@ | ||
| # Explainer: Preserving `dropEffect` Values from `dragover` to `drop` Events | ||
|
|
||
| ## Authors: | ||
| - Rohan Raja ([email protected]) | ||
|
|
||
| ## Participate | ||
| - Chromium Bug: [Issue 40068941](https://issues.chromium.org/issues/40068941) | ||
| - Chromium Review: [CL 6818116](https://chromium-review.googlesource.com/c/chromium/src/+/6818116) | ||
| - Spec: [HTML5 Drag and Drop Specification](https://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#dndevents) | ||
| - Open new issue: [Open New Issue](https://github.com/MicrosoftEdge/MSEdgeExplainers/issues/new?title=%5BPreserveDropEffect%5D+%3CTITLE+HERE%3E) | ||
|
||
|
|
||
| <!-- START doctoc generated TOC please keep comment here to allow auto update --> | ||
| <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> | ||
| ## Table of Contents | ||
|
|
||
| - [Introduction](#introduction) | ||
| - [User-Facing Problem](#user-facing-problem) | ||
| - [Goals](#goals) | ||
| - [Non-goals](#non-goals) | ||
| - [Motivation](#motivation) | ||
| - [Code Example](#code-example) | ||
| - [Before the Fix](#before-the-fix) | ||
| - [After the Fix](#after-the-fix) | ||
| - [Real-World Use Case: File Manager](#real-world-use-case-file-manager) | ||
| - [Considered Alternatives](#considered-alternatives) | ||
| - [Alternative 1: Keeping the Current Behavior](#alternative-1-keeping-the-current-behavior) | ||
| - [Alternative 2: Exposing Both Values](#alternative-2-exposing-both-values) | ||
| - [Alternative 3: Making dropEffect Read-Only During Drop](#alternative-3-making-dropeffect-read-only-during-drop) | ||
| - [Security and Privacy](#security-and-privacy) | ||
| - [Performance Impact](#performance-impact) | ||
| - [Interoperability](#interoperability) | ||
| - [References and Acknowledgements](#references-and-acknowledgements) | ||
|
|
||
| <!-- END doctoc generated TOC please keep comment here to allow auto update --> | ||
|
|
||
| ## Introduction | ||
|
|
||
| The HTML5 Drag and Drop API allows web applications to handle drag-and-drop operations through a series of events: `dragstart`, `dragenter`, `dragover`, `dragleave`, `drop`, and `dragend`. During these events, the `dataTransfer.dropEffect` property indicates which operation (copy, move, link, or none) should be performed. According to the [HTML5 specification](https://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#dndevents), the `dropEffect` value set by web applications during the last `dragover` event should be preserved and available in the subsequent `drop` event. | ||
roraja marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| However, prior to this fix, Chromium based browsers were overwriting the web application's `dropEffect` value with the browser's own negotiated operation before the `drop` event fired, breaking specification compliance and limiting developer control over drag-and-drop behavior. | ||
roraja marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ## User-Facing Problem | ||
|
|
||
| Web developers building applications with drag-and-drop functionality need to: | ||
| - Determine which operation was requested by the user during `dragover` (copy, move, or link) | ||
| - Use this information in the `drop` event handler to perform the appropriate action | ||
| - Provide consistent user feedback throughout the drag operation | ||
|
|
||
| Before this fix, the `dropEffect` property was being reset by the browser before the `drop` event, making it impossible for developers to reliably determine what operation should be performed. This led to: | ||
roraja marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ```javascript | ||
| const dropZone = document.getElementById('dropZone'); | ||
|
|
||
| dropZone.addEventListener('dragover', (e) => { | ||
| e.preventDefault(); | ||
| e.dataTransfer.dropEffect = 'copy'; // Developer sets "copy" operation | ||
| console.log('dragover dropEffect:', e.dataTransfer.dropEffect); // Logs: "copy" | ||
| }); | ||
|
|
||
| dropZone.addEventListener('drop', (e) => { | ||
| e.preventDefault(); | ||
| console.log('drop dropEffect:', e.dataTransfer.dropEffect); // Before fix: "none" or unpredictable | ||
|
|
||
| // Developer cannot reliably determine which operation to perform | ||
| if (e.dataTransfer.dropEffect === 'copy') { | ||
| // This code path may not execute as expected | ||
| performCopyOperation(e.dataTransfer); | ||
| } | ||
| }); | ||
| ``` | ||
|
|
||
| This inconsistency meant: | ||
| - Developers couldn't implement spec-compliant drag-and-drop behavior | ||
| - Drop handlers couldn't determine the intended operation reliably | ||
| - User expectations (based on cursor feedback during drag) might not match the actual behavior during drop | ||
| - Custom drag-and-drop implementations were unreliable across different browsers | ||
|
|
||
| ## Goals | ||
|
|
||
| The goal of this feature is to ensure that the `dropEffect` value set by web applications during `dragover` event handlers is preserved and correctly reflected in the `drop` event, in compliance with the HTML5 specification. Specifically: | ||
|
|
||
| 1. **Preserve Developer Intent**: Maintain the `dropEffect` value set by the web application during the last `dragover` or `dragenter` event through to the `drop` event | ||
| 2. **Spec Compliance**: Align browser behavior with the HTML5 specification requirement that states: _"dropEffect will be set to the action that was desired, which will be the value dropEffect had after the last dragenter or dragover event"_ | ||
| 3. **Enable Consistent Behavior**: Allow developers to build reliable drag-and-drop interactions where the operation indicated during hover matches the operation available during drop | ||
|
|
||
| What developers can do with preserved `dropEffect`: | ||
|
|
||
| 1. **Implement Operation-Specific Drop Logic** | ||
| - Differentiate between copy, move, and link operations in the drop handler | ||
| - Execute different code paths based on the user's intended operation | ||
| - Provide appropriate feedback and confirmations based on the operation | ||
|
|
||
| 2. **Build Consistent User Experiences** | ||
| - Ensure the cursor feedback during drag matches the actual operation performed | ||
| - Implement visual indicators that accurately reflect what will happen on drop | ||
| - Build file managers, code editors, and other applications with reliable drag-and-drop | ||
|
|
||
| 3. **Comply with Platform Conventions** | ||
| - Respect modifier keys (Ctrl/Cmd for copy, etc.) properly | ||
| - Match native application behavior for drag-and-drop operations | ||
| - Provide familiar interactions for users | ||
|
|
||
| ## Non-goals | ||
|
|
||
| This feature does not: | ||
| - Change the way `effectAllowed` is negotiated or processed | ||
roraja marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| - Modify the cursor feedback during drag operations | ||
| - Alter the security model or permissions for drag-and-drop | ||
| - Add new drag-and-drop events or properties | ||
| - Change behavior for dragging between different windows or applications | ||
|
|
||
| ## Motivation | ||
|
|
||
| The HTML5 specification clearly states that `dropEffect` should reflect the value set during the last `dragover` or `dragenter` event when the `drop` event fires. However, Chromium's implementation was overwriting this value with the browser's internal negotiated operation, breaking the specification and making it impossible for developers to build reliable drag-and-drop interactions. | ||
|
|
||
| This feature addresses: | ||
| - **Spec Compliance**: Aligns with the HTML5 Drag and Drop specification | ||
| - **Developer Control**: Returns control of the `dropEffect` property to web applications | ||
| - **Predictable Behavior**: Ensures the operation indicated during drag matches the operation available during drop | ||
| - **Cross-Browser Consistency**: Provides consistent behavior that developers can rely on | ||
|
|
||
| ### Code Example | ||
|
|
||
| #### Before the Fix | ||
|
|
||
| ```javascript | ||
| const dropZone = document.getElementById('dropZone'); | ||
|
|
||
| dropZone.addEventListener('dragover', (e) => { | ||
| e.preventDefault(); | ||
| e.dataTransfer.dropEffect = 'copy'; // Developer wants copy operation | ||
| console.log('dragover dropEffect:', e.dataTransfer.dropEffect); // Logs: "copy" | ||
| }); | ||
|
|
||
| dropZone.addEventListener('drop', (e) => { | ||
| e.preventDefault(); | ||
| console.log('drop dropEffect:', e.dataTransfer.dropEffect); // Logs: "none" (WRONG!) | ||
|
|
||
| // Cannot determine the intended operation | ||
| if (e.dataTransfer.dropEffect === 'copy') { | ||
| performCopyOperation(e.dataTransfer); // This doesn't execute! | ||
| } else if (e.dataTransfer.dropEffect === 'move') { | ||
| performMoveOperation(e.dataTransfer); | ||
| } | ||
| }); | ||
| ``` | ||
|
|
||
| #### After the Fix | ||
|
|
||
| ```javascript | ||
| const dropZone = document.getElementById('dropZone'); | ||
|
|
||
| dropZone.addEventListener('dragover', (e) => { | ||
| e.preventDefault(); | ||
| e.dataTransfer.dropEffect = 'copy'; // Developer wants copy operation | ||
| console.log('dragover dropEffect:', e.dataTransfer.dropEffect); // Logs: "copy" | ||
| }); | ||
|
|
||
| dropZone.addEventListener('drop', (e) => { | ||
| e.preventDefault(); | ||
| console.log('drop dropEffect:', e.dataTransfer.dropEffect); // Logs: "copy" (CORRECT!) | ||
|
|
||
| // Can now reliably determine the intended operation | ||
| if (e.dataTransfer.dropEffect === 'copy') { | ||
| performCopyOperation(e.dataTransfer); // This executes as expected! | ||
| } else if (e.dataTransfer.dropEffect === 'move') { | ||
| performMoveOperation(e.dataTransfer); | ||
| } | ||
| }); | ||
| ``` | ||
|
|
||
| ### Real-World Use Case: File Manager | ||
|
|
||
| ```javascript | ||
| // Example: Building a web-based file manager | ||
| const fileManager = document.getElementById('fileManager'); | ||
|
|
||
| fileManager.addEventListener('dragover', (e) => { | ||
| e.preventDefault(); | ||
|
|
||
| // Check for modifier keys and set appropriate operation | ||
| if (e.ctrlKey || e.metaKey) { | ||
| e.dataTransfer.dropEffect = 'copy'; | ||
| } else if (e.shiftKey) { | ||
| e.dataTransfer.dropEffect = 'move'; | ||
| } else if (e.altKey) { | ||
| e.dataTransfer.dropEffect = 'link'; | ||
| } else { | ||
| e.dataTransfer.dropEffect = 'move'; // Default operation | ||
| } | ||
|
|
||
| // Update UI to show which operation will be performed | ||
| updateDropIndicator(e.dataTransfer.dropEffect); | ||
| }); | ||
|
|
||
| fileManager.addEventListener('drop', (e) => { | ||
| e.preventDefault(); | ||
|
|
||
| const files = Array.from(e.dataTransfer.files); | ||
|
|
||
| // NOW we can reliably use dropEffect to determine what to do | ||
| switch (e.dataTransfer.dropEffect) { | ||
| case 'copy': | ||
| copyFiles(files, e.target); | ||
| showNotification('Files copied'); | ||
| break; | ||
| case 'move': | ||
| moveFiles(files, e.target); | ||
| showNotification('Files moved'); | ||
| break; | ||
| case 'link': | ||
| createShortcuts(files, e.target); | ||
| showNotification('Shortcuts created'); | ||
| break; | ||
| } | ||
| }); | ||
| ``` | ||
|
|
||
| ## Considered Alternatives | ||
|
|
||
| ### Alternative 1: Keeping the Current Behavior | ||
| This would continue to violate the HTML5 specification and limit developer capabilities. Developers would need to implement workarounds, such as: | ||
| - Storing the `dropEffect` value in a global variable during `dragover` | ||
| - Using custom data attributes to track the intended operation | ||
| - Abandoning the native `dropEffect` property entirely | ||
|
|
||
| **Rejected because**: This forces developers to work around the platform rather than use the standard API as designed. | ||
|
|
||
| ### Alternative 2: Exposing Both Values | ||
| We could expose both the developer-set value and the browser-negotiated value through separate properties (e.g., `dataTransfer.requestedDropEffect` and `dataTransfer.actualDropEffect`). | ||
|
|
||
| **Rejected because**: This adds complexity without clear benefit. The specification already defines the expected behavior, and adding new properties would create additional interoperability challenges. | ||
|
|
||
| ### Alternative 3: Making dropEffect Read-Only During Drop | ||
| We could make `dropEffect` read-only during the `drop` event to prevent confusion. | ||
|
|
||
| **Rejected because**: The specification allows reading `dropEffect` during drop, and making it read-only would break existing code that might check this value. | ||
|
|
||
| ## Security and Privacy | ||
|
|
||
| This proposal has no known impact on security or privacy: | ||
|
|
||
| - **No New Data Exposure**: The `dropEffect` property already exists; this change only ensures its value is preserved correctly | ||
| - **No Permission Changes**: The drag-and-drop security model remains unchanged | ||
| - **Scoped to Web Content**: This only affects how web applications receive the `dropEffect` value; it does not change cross-origin or cross-application drag-and-drop behavior | ||
| - **User Control Maintained**: Users still control the drag-and-drop operation through modifier keys and drop location | ||
|
|
||
| The feature simply ensures that the value web applications set is the value they receive, without introducing new security surfaces. | ||
|
|
||
| ## Performance Impact | ||
|
|
||
| This feature introduces minimal overhead: | ||
|
|
||
| - **No Additional Computation**: The browser already tracks the `dropEffect` value; this change only preserves it instead of overwriting it | ||
| - **Single Property Transfer**: Only one additional property value needs to be passed from the `dragover` handler to the `drop` event | ||
| - **No New Allocations**: No additional objects or data structures are created | ||
|
|
||
| ## Interoperability | ||
|
|
||
| This feature improves interoperability by: | ||
|
|
||
| - **Aligning with the HTML5 Specification**: Implements the behavior defined in the [HTML5 Drag and Drop specification](https://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#dndevents) | ||
| - **Reducing Browser Inconsistencies**: Provides consistent behavior that developers can rely on across browsers | ||
| - **Following Web Standards**: Respects the intent of the specification that web applications should control the `dropEffect` property | ||
|
|
||
| Web developers can now write drag-and-drop code that works consistently according to the specification, rather than working around browser-specific quirks. | ||
|
|
||
| ## References and Acknowledgements | ||
|
|
||
| - [HTML5 Drag and Drop Specification](https://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#dndevents) | ||
| - [Chromium Bug 40068941](https://issues.chromium.org/issues/40068941) | ||
| - [Chromium Code Review 6818116](https://chromium-review.googlesource.com/c/chromium/src/+/6818116) | ||
| - [MDN: DataTransfer.dropEffect](https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/dropEffect) | ||
|
|
||
| Many thanks for valuable feedback and code reviews from: | ||
| - [Avi Drissman](https://github.com/avi) | ||
| - [Kent Tamura](https://github.com/tkent-google) | ||
| - [Pranav Modi](https://github.com/pranavmodi) | ||
|
|
||
| --- | ||
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
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.
Uh oh!
There was an error while loading. Please reload this page.