This is an archived snapshot of W3C's public bugzilla bug tracker, decommissioned in April 2019. Please see the home page for more details.

Bug 23486 - Drag Data Store "protected mode" prevents data-based drop effects
Summary: Drag Data Store "protected mode" prevents data-based drop effects
Status: RESOLVED WONTFIX
Alias: None
Product: WHATWG
Classification: Unclassified
Component: HTML (show other bugs)
Version: unspecified
Hardware: All All
: P3 normal
Target Milestone: Needs Impl Interest
Assignee: Ian 'Hixie' Hickson
QA Contact: contributor
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2013-10-12 11:52 UTC by Robert Kieffer
Modified: 2019-03-29 20:31 UTC (History)
5 users (show)

See Also:


Attachments

Description Robert Kieffer 2013-10-12 11:52:19 UTC
Expected behavior: When calling event.dataTransfer.getData(type) from within dragenter, dragleave, or dragover event handlers, callers should be able to get the contents of the dataTransfer object for that type.

Actual behavior: getData() always returns an empty string from within these event handlers.

I get that protected mode is designed to prevent 3rd party pages from setting up listener divs to inappropriately access dataTransfer content. However this is interferes with some very common use cases.  It's also forcing developers to hack around the problem by putting content in the types _keys_ , as described here - http://stackoverflow.com/a/11089592/109538 - thus circumventing any security benefit this mode might have.

Note, too, that this doesn't prevent 3rd parties from listening on drop events. I.e. the protected mode only mitigates the security problem. It doesn't solve it completely.

Could this be solved by having the data store be in read-only mode for these events for pages with the same origin?  I'm guessing that would address 90% of the problems this restriction is currently causing for developers.
Comment 1 Ian 'Hixie' Hickson 2013-10-13 05:18:49 UTC
I'm assuming you're suggesting that an origin A should be able to read the data from a drag from origin A, while it should not be able to read the data from a drag from an origin B, before the drop.

This would make the API different in same-origin drags as different-origin drags, which is something we've been trying to avoid, as it discourages authors from designing their applications to support cross-origin drag-and-drop as well as same-origin drag-and-drop.
Comment 2 Ian 'Hixie' Hickson 2013-10-13 05:27:37 UTC
That StackOverflow page is interesting. It actually already led to feedback on the spec, though it didn't go anywhere:

   http://lists.w3.org/Archives/Public/public-whatwg-archive/2012Jun/0221.html
   http://lists.w3.org/Archives/Public/public-whatwg-archive/2012Aug/0291.html
   http://lists.w3.org/Archives/Public/public-whatwg-archive/2012Aug/0303.html
   http://lists.w3.org/Archives/Public/public-whatwg-archive/2012Aug/0299.html
   http://lists.w3.org/Archives/Public/public-whatwg-archive/2012Dec/0085.html

Looks like that last e-mail from me asking for use cases never got a reply.

What is the content that is needed here, that isn't appropriate as a type key?

(Note that blocking third parties from listening to drop events is intentional: if the user dropped content on that third-party site, presumably the user wanted to give that site the information.)
Comment 3 Robert Kieffer 2013-10-13 05:52:38 UTC
The use case I have is a rocket simulation app.  The rocket is built up by dragging "parts" around (nosecone, fins, motor, etc.)  To build a rocket you start by dragging template parts into the build area, where the only distinction between a template and a part is the presence of a flag indicating the former is read-only.  Where the drag UI is concerned if a template is dragged over the build area, it should have a "copy" effect, while a part should have a "move" effect.

In both cases the content of the drag data has identical schemas, represented in identical formats.  Could one be a "text/x-rocket-part" and one be a "text/x-rocket-template"?  Yeah, I suppose, but that leads to a rather nasty proliferation of types that seems grossly inappropriate. E.g. imagine an HR app with a "text/x-employee" type that had state for title, department, and location, any one of which might impact what drop effects were applied depending on drop zone and drag data type.

Basically anywhere drag operations are dependent on state in the source element this is going to be an issue.  While some of this might be wedged into type keys it doesn't really seem appropriate.  "types" should represent different data formats (e.g. "text" .vs. "image" .vs. "json"), not minor variations in state for objects with identical schemas.
Comment 4 Ian 'Hixie' Hickson 2013-10-13 21:00:57 UTC
Hm, that's an interesting use case.

What would you want to have happen if someone dragged a part from another app entirely? You'd presumably need to know if it's a template or a part in that case also, right?
Comment 5 Robert Kieffer 2013-10-14 01:33:16 UTC
(In reply to Ian 'Hixie' Hickson from comment #4)
> What would you want to have happen if someone dragged a part from another
> app entirely? You'd presumably need to know if it's a template or a part in
> that case also, right?

Ideally you could probably do some fancy postMessage()'ing to message back to the originating window that the part should be removed as part of moving to the current window.  Failing that I'd just default to the copy operation for parts from other windows.  But... can you even tell if a drag comes from another window currently?  Seems like "is this external?" is just a variant of the same problem.
Comment 6 Ian 'Hixie' Hickson 2013-10-14 18:55:08 UTC
Wouldn't the original window know whether to remove their bit or not based on the dropEffect information in the 'dragend' message?
Comment 7 Robert Kieffer 2013-10-14 20:17:38 UTC
Yeah, that'd probably work.  That's kind of getting away from the original problem though.  I'm not sure I'd be comfortable relying on the dropEffect field to wholly contain the information the source window needs to complete it's half of the drop action though.  It's not hard to contrive scenarios where there might be different flavors of move actions. E.g. "move to group" .vs. "move to trash" .vs. "move to folder named <some name>", etc.  Relying on a field whose state is limited to "copy" or "move" for this would be limiting.

Regardless, I think this is a little off topic since the issue is with the events that restrict access to the datastore prior to dragend.
Comment 8 Ian 'Hixie' Hickson 2013-11-13 23:16:54 UTC
Seems like those three moves would all be moves, and thus would have the same effect on where the nodes come from, assuming the move is actually done by where you drop the data. I seem to recall there was a suggestion to provide a MessagePort so that the drop target could communicate with the source, but I can't see where that suggestion went. If you want that, file a bug for it. It would basically resolve all the issues around dealing with drops, I think.

Anyway, my original point stands: it seems like you'd want this to be available cross-origin as well as same-origin, right? So that, for instance, you could get a rocket component from an unaffiliated rocket template store.

Suppose a user dragged financial information from their bank to mint.com, and mint.com wanted to know something about the state before the user dropped the information, to set whether or not to accept the drop (e.g. only accept the drop as a new savings account if it is a bank account with the "savings" bit set). That's all well and good, but what if the user dragged over an unreputable porn site in between dragging from the bank and dropping on mint.com? We wouldn't want the porn site to be able to get any information out of the drag data store.

So how do we determine who can get the data and who can't?

I'm at a loss as to what to do here. The obvious solution is to have a bit on each piece of information saying whether it's protected or not (default true), but that doesn't really work. The model currently is that the user grants the drop site access by dropping the data there, but I don't see what the natural gesture would be before the actual drop.
Comment 9 Ian 'Hixie' Hickson 2013-12-11 21:47:33 UTC
I found the MessagePort suggestion in a comment in the spec:

     * We should let drop targets communicate back to drag sources if
       they want to communicate. (e.g. expose Window, and thus
       postMessage(), on the dataTransfer object on drop.) Or maybe
       just use a MessagePort!

       We should let drag sources provide a set of options via a
       context menu when the drop happens. (So that, e.g., the source
       can know whether a capabilities URI that it is passing along is
       supposed to be read-write access or read-only access to the
       object being dragged.)

       We should let potential drop targets see the types (but not the
       contents!) of dragged data so they can establish if they care
       or not. (dataTransfer.hasType())

       Ack: Ben Laurie (@g)
Comment 10 Robert Kieffer 2013-12-12 15:37:40 UTC
(In reply to Ian 'Hixie' Hickson from comment #8)
> Seems like those three moves would all be moves, and thus would have the
> same effect on where the nodes come from, assuming the move is actually done
> by where you drop the data.

[Restating the issue for my own benefit here]  The core issue is the extent to which the business logic in a drop target can rely on state and/or interaction with the drag source.  If interaction isn't allowed, that logic can only run during the drop event.  This is problematic because it forces apps to retroactively inform users about unexpected behavior - for example, alert dialogs reading, "The dragged-thing can't be dropped here" - rather than using drag action indicators to more accurately reflect what's going to happen.

I agree the drag-bank-info-across-porn-site case is a concern.  However I don't believe limiting the same-origin case because of objections to cross-origin security issues is the right solution.  I would instead look to the XHR API for inspiration.  In the same-origin case the XHR API behaves pretty much the way a naive developer expects.  The cross-origin limitations don't manifest until you actually try to make cross-origin requests, at which time you [the developer] have to educate yourself on the security issues and how to work around them with CORS.  This just-in-time learning curve is, I believe, how APIs should be designed.

> Anyway, my original point stands: it seems like you'd want this to be
> available cross-origin as well as same-origin, right? So that, for instance,
> you could get a rocket component from an unaffiliated rocket template store.

Cross-origin operation is (imho) not as important having intuitive same-origin operation.

In my case, I won't care about cross-origin interaction until version 2 or 3 of this app (assuming I ever get to that point).  Right now, I'm just trying to build a basic one-page web app, where all drag behavior is in-page.

> So how do we determine who can get the data and who can't?

The MessagePort suggestion is interesting but results in a pretty odd separation of public .vs. protected drag state.  Specifically, dataTransfer.getData() is going to be much easier to use but is only available after drop.  Thus, developers are going to end up putting "protected" state there.  Then, when they discover they need additional state during drag, or in cross-origin cases, they'll end up contriving some ad-hoc messaging system for sharing that state via a MessagePort.

For example with my rocket app, my drag event code ends up doing something like  `messagePort.postMessage('isTemplatePart(<objectId>)'). But my drop event code will almost certainly be doing `dataTransfer.getData(type).isTemplate`.

I dunno... I find myself gravitating to a CORS-inspired solution of some sort, where there's a handshake between drag source and drop target to establish acceptability of the origin.

By way of proposing an alternate solution, what if dataTransfer supported an `allowOrigin()` callback that behaved akin to the Origin/Allow-Origin headers in CORS?  For example

    e.dataTransfer.setData(dataType, someData);

    // Called when dataTransfer.getData() is invoked cross-origin.
    // Return true/false to allow the specified origin to access drag data
    e.dataTransfer.allowOrigin = function(origin) {
      return origin === window.location.origin;
    }

Thoughts?
Comment 11 Ian 'Hixie' Hickson 2014-01-02 20:58:43 UTC
What I don't like about making same-origin and cross-origin cases work differently is that it makes it impossible for third-parties to provide tools that interface with second-party software that wasn't designed explicitly with interaction with the third-party software in mind. The great thing about the drag-and-drop model in theory is that anyone can be a drag source and anyone a drag target. It's supposed to Just Work, whether it's same-origin or cross-origin.

Not that I have a better proposal, unfortunately.
Comment 12 Steven Wood 2014-08-22 09:39:47 UTC
No great insight here, but wanted to comment.  I have run into the same frustration and also ended up using the types field and agree it does not feel very pleasant.

"Right now, I'm just trying to build a basic one-page web app, where all drag behavior is in-page."

For single page apps it is easy to give all drop targets visibility of exactly what is being dragged and everything about it that you want to share, i don't see how the spec as it stands currently stops you from getting this working nicely in a single page.  

IMO by the time you get to the level of having to define cross origin access to protected bits of the drag, it's too complicated for straightforward use anyway. Also I'm no usability expert but I think it's reasonable to expect that there are situations that are too complex to resolve during the drag - the dragover must quickly be able to decide whether or not the drop is acceptable.  

Of course, if all the browsers actually implemented the existing spec it would be a lot better...
Comment 13 Ian 'Hixie' Hickson 2014-09-17 22:26:25 UTC
One way we could do this is that you have to drag over to the target frame, then hold it there, at which point the browser could show some sort of UI similar to when you drag over to an icon in the dock on OS X, and if you still stayed there, it would mean you were granting that page access to the data in the data store.
Comment 14 Robert Kieffer 2014-09-18 00:57:30 UTC
Re Comment 12 ...
> ... it is easy to give all drop targets visibility of exactly what is being dragged and everything about it that you want to share...

I assume you're referring to some sort of shared state the dev sets up between drag source and target?  Such solutions tend to be rather ad-hoc, and involve globals and other undesirable dependencies.

Re Comment 13 ...
> the browser could show some sort of UI similar to when you drag over to an icon in the dock on OS X

Hmm... that's changing the expected interaction on the part of the user.  E.g. when I drag text between windows, I don't wait for something to bounce up and down to indicate it's okay to drop.  Why would I (as a user) expect that for other content?
Comment 15 Ian 'Hixie' Hickson 2014-09-18 16:48:22 UTC
I don't know. It's just the best I've come up with so far. Better ideas welcome!
Comment 16 Domenic Denicola 2019-03-29 20:31:28 UTC
W3C Bugzilla is closing down, and as such we're closing all feature request bugs against HTML as "WONTFIX", at least wontfix-in-this-bugtracker.

If you still think this feature is valuable, please feel free to open a new issue against https://github.com/whatwg/html/issues ; the community has gotten much more active and involved since the Bugzilla days, and you might get a more useful dialogue there.