Bug 66469 - drop event isn't fired unless dragover event is canceled
Summary: drop event isn't fired unless dragover event is canceled
Status: UNCONFIRMED
Alias: None
Product: WebKit
Classification: Unclassified
Component: UI Events (show other bugs)
Version: 528+ (Nightly build)
Hardware: Mac (Intel) OS X 10.7
: P2 Normal
Assignee: Nobody
URL: http://www.deadmarshes.com/webkit/sel...
Keywords:
Depends on:
Blocks:
 
Reported: 2011-08-18 08:10 PDT by Richard York
Modified: 2012-10-18 23:08 PDT (History)
3 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Richard York 2011-08-18 08:10:05 PDT
If you visit the attached URL, you'll see a reduced test for what I'm describing here.

I've been working on a WYSIWYG editor.  In this editor, I have a component that reveals a photo library and provides an interface to drag and drop items from that library into the WYSIWYG document.  These components are designed for one another and presently exist within the same browser window.

This works well up to a point.  Where this falls apart is where I want to expand and abstract the functionality to allow drag and drop from a separate, unrelated components.  One of those being a file managing component, for example.  This file managing component places markup on the clipboard containing all of the relevant data I need to know about, the path to a document, the title of a document, and so on.  But it also includes data that I don't need in a WYSIWYG, things like the MIME type and file size.  These are things that are also worthless to the user.

What I'd like to do is to be able to allow the file management side to have its own drag and drop component as it now exists, with all of that extra meta data, but in this separate WYSIWYG application, find a way to intercept the drop and manipulate the markup on the clipboard before the drop occurs, transforming what presently exists there into a simple link upon drop, i.e., <a href='/path/to/document'>Title</a>.

What I find in my attempt to achieve this goal is that a drop event cannot be intercepted unless you cancel the default action of the drag and drop.  Well, in this case, I mostly want the default action.  That is to say, when you drag an item onto the WYSIWYG, as you drag that item, the blinking text cursor follows the mouse pointer and when you drop, the contents of the clipboard are inserted at the point the blinking text cursor presently resides.  What I don't want about the default scenario is the contents of the clipboard as-is, I want to be able to examine and transform the content of the clipboard before that content is inserted.  However, if I attempt to intercept drop, I become responsible for the whole thing, and I find that a drop cannot be detected unless the default action is canceled.

Since that's the case, I attempted to sidestep the system clipboard as an experiment.  In this scenario, what I've attempted to do, is to set the clipboard content provided to the system to null.  Then allow the default drag and drop to take place, but when a drop occurs, insert null at the blinking cursor.  But as the drag portion takes place, I attempt to track the movement of the blinking cursor in the document.  I'm able to do this in other scenarios via window.getSelection(), and with the text cursor position specifically, "getRangeAt(0)".  This works for me in every case, clicking, selecting something.   Where it does not work is during a drag and drop, making even a sideways hack  to achieve my goal improbable, since it is not possible to track the movement of the text cursor.

Furthermore, I find there is an additional inconsistency with regards to the contenteditable region, which won't provide a selection via windows.getSelection() until it has been focused.  The "won't provide a selection via window.getSelection()" I found already documented in a different bug, but I did not see a bug reported that talks about attempting to access window.getSelection() as a drag is taking place over an editable element, which is not possible regardless of whether or not the editable node has been focused.

Finally, I'd love to see the original problem that led me down this path addressed in some way; that is to say, I'd love to see an API emerge that allows you to intercept the content of a drop without having to cancel the default action.

It's also worth noting that the approach laid out by my test case did not work in Chrome.  In that browser, when I set the clipboard to null in "e.dataTransfer.setData('text/html', '');", the "dragover" event did not subsequently fire, so that browser essentially ignored the entire drag and drop action when that occurred.  I did no further cross-browser testing, since my desired goal could not be achieved in Webkit.
Comment 1 Alexey Proskuryakov 2011-08-18 10:40:30 PDT
Could you please post steps to reproduce the problem (click here, drag there, release the mouse, expected results, actual results...)? I have a lot of difficulty figuring out what exactly we need to fix here.
Comment 2 Richard York 2011-08-18 13:15:17 PDT
OK, well, what I wrote explains this pretty thoroughly.  Please read that.

To quickly test what's happening, drag the image on the page over the contenteditable div.  As you drag the image over the div the dragover event is continuously firing, and as that fires, it is calling code to see what's going on with window.getSelection().

And you can see in the console, the rangeCount and the baseOffset properties do not update during the drag over the contenteditable div, even though you can see the blinking text cursor moving around in the contenteditable div element.

What I expect to happen, I expect to be able to query the getSelection() API and be able to see where the text cursor is in real time via the rangeCount and baseOffset properties as a drag over the contenteditable  div is taking place.
Comment 3 Alexey Proskuryakov 2011-08-18 15:25:35 PDT
There is no need to be snarky. Multiple people are likely to look at the bug, and having it in a common format with easily identifiable steps to reproduce really helps to move it forward. Please see <http://www.webkit.org/quality/bugwriting.html> for more detail.

So, steps to reproduce are:
1. Open http://www.deadmarshes.com/webkit/selection.html
2. Open Web Inspector.
3. Click on the test page to activate it again (if Web Inspector is in a separate window).
4. Click on editable text to focus it (you didn't mention that, but otherwise, there is no logging while dragging).
5. Start dragging the image over the text.
6. Check logs in Web Inspector console.

The selection doesn't change during dragging, so none of the selection properties change. Why do you expect otherwise? There is a vertical line drawn underneath mouse cursor during dragging, and it's similar to selection caret, but it's not the caret.

The bug title says that rangeCount in particular isn't getting updated. WebKit does not support multiple ranges per selection, so rangeCount can only ever be 0 or 1. What values of rangeCount do you expect to observe while dragging?
Comment 4 Richard York 2011-08-18 16:17:30 PDT
My apologies, I didn't intend to be snarky.

I didn't realize that the rangeCount property is boolean.   So what I really want is for baseOffset to update with the position of the floating caret, since it's my understanding that baseOffset provides the actual offset position of the caret within the content.   And, since you say the floating caret in a drag isn't the real caret, I believe that the floating caret that moves with the selection should be considered the 'real' caret, since that's the illusion conveyed to the user with the UI, otherwise, why draw a caret?  Well, regardless of what that line is presently considered, I think there should be some way to know where within a text node a drop has occurred, in terms of that floating caret, so that the drop operation can be manipulated and controlled with more precision and flexibility.

The objective is to be able to utilize the drag and drop API in a WYSIWYG editor, so that the user can drag and drop an item to a precision position within a body of text content.

Presently, I can think of three possible ways to approach this, but all three are unworkable:

1. You cannot detect a drop without canceling the default dragover event, therefore, you cannot manipulate a drop on the fly.  If you cancel the default dragover, you lose the floating caret, losing precision placement of a dropped element.  This could work if you could detect a drop and access the clipboard upon drop without canceling the default behavior.

2. Without being able to detect a drop, an alternative approach is to roll a custom clipboard (a hodgepodge, but theoretically possible), but since it is also impossible to detect the position of the floating caret, this approach cannot work.

3. Yet another possible approach is detecting a dragstart event, but since this event only fires on the element on which dragging began, this approach is also not workable, and that's because drag and drop can occur between multiple windows (with no DOM relationship between each window, and it even works between completely different browsers).  If a drag began in another window (or browser), you have no way to tell the originating window how the content should be formatted for the target window.

With this capability, it is possible to drag and drop items between multiple browser windows.  So, let's say in one window you're simply enabling dragging of a link, http://www.example.com.  If I can detect, either the drop, the position of the caret as it moves, or the dragstart (in the same window as the drop), I can manipulate the link on-the-fly and transform it into HTML, <a href='http://www.example.com'>www.example.com</a>, for example, and insert that HTML into the precise position of that floating caret.

Hope that explains the problem better.
Comment 5 Richard York 2011-08-18 17:06:27 PDT
Then, I guess, a forth approach could be to transform the dropped item after the drop occurs, because again, the goal is to allow the user to drop an item to the position of the floating caret.  And this is what I am presently doing, but it is ugly and not as efficient or fluid as I'd like it to be.

Ideally, you'd be able to call an event when a drop occurs, but I can't attach a callback to the drop event, because I want the default action, the floating caret.  And since that's not possible, I tried the mouseup event, but that event, of course, doesn't fire when a drag and drop occurs.

What I'm stuck with is doing an event after the drop has occurred, such as a subsequent click on the contenteditable region.  This is not desirable, because you don't see the correctly formatted markup until you do something else with the editable element, so it appears to be buggy to the user.

Then, it's also not very efficient, because you have to continuously re-examine the editable content for things that have been dropped but need to be fixed.

So, with regards to webkit itself, I guess there turn out to be two bugs, at least in my opinion, though in pursuit of the functionality I describe, there are several things I think that could be improved in general.  1. The position of the floating caret cannot be detected as a drag is taking place, because the floating caret isn't considered to be the real selection caret.  2. There is no event (that I'm aware of, perhaps I'm wrong) that can be fired to detect that a drop has occurred while at the same time allowing the default behavior to commence, perhaps fixing that is as easy as letting the drop event fire without having canceled the default behavior.
Comment 6 Richard York 2011-08-19 07:11:46 PDT
Please excuse my grammar, a fourth approach.
Comment 7 Alexey Proskuryakov 2011-08-20 21:54:51 PDT
> 1. The position of the floating caret cannot be detected as a drag is taking place, because the floating caret isn't considered to be the real selection caret.

You can determine it using document.caretRangeFromPoint(event.x, event.y).

> 2. There is no event (that I'm aware of, perhaps I'm wrong) that can be fired to detect that a drop has occurred while at the same time allowing the default behavior to commence, perhaps fixing that is as easy as letting the drop event fire without having canceled the default behavior.

I don't know enough about how these events work to comment. Do you have evidence that WebKit doesn't properly implement HTML5 in this area (see <http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dnd>)? There is also a nice description of how drag and drop is supposed to work in <http://www.html5rocks.com/en/tutorials/dnd/basics/>.
Comment 8 Richard York 2011-08-22 08:50:18 PDT
Thanks for the information about document.caretRangeFromPoint(), that's extremely useful and I had no idea that method existed.  I think that makes most of my previous concerns completely moot.

Regarding the 'drop' event and the WHATWG specification, I can find no information in the specification that says that the 'drop' event should only be fired if the 'dragover' event is canceled.  The behavior seems out of line with the specification, but I only took a cursory look at the document.  Perhaps I'm wrong about that.

In that same vein, I would be very surprised if someone else hasn't already filed a bug about webkit's implementation of the drop event.

Further investigation seems to indicate that this behavior is isolated to Webkit browsers.  A test on Firefox indicates that Firefox fires the drop event whether or not the default action of the dragover event is canceled.

But with the aforementioned caretRangeFromPoint method, with regards to my goals, perhaps this is also rendered moot.

the caretRangeFromPoint from my initial testing seems to not only allow content to be inserted at the position of the mouse cursor, but also seems to make possible a completely custom floating caret, which would certainly be necessary if I cancel the default behavior.

Thanks!
Comment 9 Alexey Proskuryakov 2011-08-23 11:04:55 PDT
I don't know the answer offhand. Re-titling to make it clear that this bug is now only tracking the single issue with drop vs. dragover.

Please file separate bugs if you discover other discrepancies.
Comment 10 Daniel Cheng 2012-10-18 23:04:48 PDT
(In reply to comment #8)
> Regarding the 'drop' event and the WHATWG specification, I can find no information in the specification that says that the 'drop' event should only be fired if the 'dragover' event is canceled.  The behavior seems out of line with the specification, but I only took a cursory look at the document.  Perhaps I'm wrong about that.
> 

From http://whatwg.org/html5 about dragover:
If the dragover event is not canceled, run the appropriate step from the following list:

If the current target element is a text field (e.g. textarea, or an input element whose type attribute is in the Text state) or an editing host or editable element, and the drag data store item list has an item with the drag data item type string "text/plain" and the drag data item kind Plain Unicode string
-> Set the current drag operation to either "copy" or "move", as appropriate given the platform conventions.

If the current target element is an element with a dropzone attribute that matches the drag data store and specifies an operation
-> Set the current drag operation to the operation specified by the dropzone attribute of the current target element.

If the current target element is an element with a dropzone attribute that matches the drag data store and does not specify an operation
-> Set the current drag operation to "copy".

Otherwise
-> Reset the current drag operation to "none".

From later in the same document about the end of the drag and drop operation:
If the current drag operation is "none" (no drag operation), or, if the user ended the drag-and-drop operation by canceling it (e.g. by hitting the Escape key), or if the current target element is null, then the drag operation failed.

It seems pretty clear to me that no drop event should be fired in that case. Note that we're currently incorrectly firing it but I'm planning on fixing that behavior...
Comment 11 Daniel Cheng 2012-10-18 23:07:21 PDT

*** This bug has been marked as a duplicate of bug 99791 ***
Comment 12 Daniel Cheng 2012-10-18 23:08:09 PDT
Oops, I resolved the wrong bug as a dupe. Sorry.