gwtproject / gwt

GWT Open Source Project
http://www.gwtproject.org
1.52k stars 375 forks source link

HTML 5 Drag and Drop: Impossible to disallow a drop operation in GWT #9989

Open jnehlmeier opened 3 months ago

jnehlmeier commented 3 months ago

GWT version: HEAD Browser (with version): any Operating System: any


Description

To tell the browser that a future drop operation is allowed on an element/dropzone you have to call DragOverEvent.preventDefault(). If you DO NOT call it a drop operation on that element/dropzone results in a canceled drop operation. In that case the dragged element receives a dragend event with dropEffect = none

See: https://jsfiddle.net/gdtja9y7/2/

In GWT all HTML 5 Drag and Drop events are registered using addBitlessDomHandler. In DOMImplStandard.getBitlessEventDispatchers() GWT adds special handling for dragenter and dragover events. For both events GWT itself already calls event.preventDefault() before calling back to user code. So it is currently impossible in GWT to disallow a drop operation.

Known workarounds

Use a helper class to manage event handlers using elemental2

niloc132 commented 3 months ago

According to the comment, it looks like this was specifically (at the time, 11 years ago) to prevent a bug where while dragging something, text would be selected. I'm assuming that is no longer true, so it should be trivial to remove that line and comment (and test to confirm).

Without that line, this is just standard dispatchEvent(), so getBitlessEventDispatchers() can be simplified?

jnehlmeier commented 3 months ago

I don't really understand that comment. The HTML5 DnD spec says that the browser should disable text selection if an element is marked with draggable so you actually drag the element and not text. This generally works except on iOS/iPadOs (not tested on Android). However this whole text selection thing is only relevant for the dragstart event. So you could check wether or not event.target is a text node and then disallow dragging. I could imagine that browsers in the past did allow text selection in a draggable element and that is what GWT tried to solve.

However, GWT does event.preventDefault in dragenter and dragover which are events you define on the drop zone. I don't think any browser in the past would select text after you have dragged something. Also GWT only prevents the default action once you add one or both event handlers to an element, but I would imagine a browser would have selected text on any element then and not just in elements having these listeners attached.

Anyways, yes the code should be deleted in GWT because event.preventDefault() is a crucial part of the HTML5 DnD API and standard dispatchEvent() should be used.

niloc132 commented 3 months ago

I can't speak confidently here without an 11 year old browser up and running, but did the spec say that 11+ years ago? Did all browsers consistently implement it that way? I'm guessing not.

If as you say (I have done no testing here) this is entirely unusable for modern browsers, there should be no harm in fixing it, even destructively. Anyone interrupted by this seem to have a simple workaround - put a preventDefault in their event handlers.

jnehlmeier commented 3 months ago

This funny blog article of 2009 indicates that the spec was the same back in the days. You had to prevent the default action if you want to do a drop. If you don't no drop event is triggered and the drag operation is canceled.

mP1 commented 3 months ago

What happens if you disable selection itself (not by preventing the selection event), that way the selection event never happen, and maybe only the drag event happens giving you a chance to prevent it.?

https://www.w3schools.com/howto/howto_css_disable_text_selection.asp

.prevent-select {
  -webkit-user-select: none; /* Safari */
  -ms-user-select: none; /* IE 10 and IE 11 */
  user-select: none; /* Standard syntax */
}
jnehlmeier commented 3 months ago

@mP1 I think you mixed something. You can devide all DnD events into two groups

Source element: dragstart and dragend Target element: dragenter, dragover, dragleave, drop

Without any DnD if you click and hold the mouse button and then move the mouse you select text on the page. That is why the spec says that draggable elements should automatically disallow text selection. Otherwise you select text instead of triggering dragstart. Or you might trigger dragstart but you are actually dragging text. This works for all browsers except mobile, so you would add your mentioned CSS for mobile to make the dragging experience better.

The issue here is that GWT calls preventDefault on the dragover event with a comment that says that it should avoid text selection. However dragover happens after dragstart and only reaches GWT code if you actually add an event listener for it on a target element. So I don't see the logic of it. Because it is native HTML5 DnD the page might not have any drop zone. If we treat the comment as correct, it would mean that you select text all over the page while dragging your data around. If the page has a drop zone but I never hover it then the code can't prevent text selection as well, assuming the comment is correct.

I think HTML5 DnD was badly implemented in browsers and complex to understand 10 years ago. So maybe that code and comment were added by accident. Even 10 years ago the spec states that in order to control wether or not dropping on an element is allowed you have to either prevent or not prevent the default action of dropover. So GWT should never ever call it behind the curtain. Even back then.