Bug 1352852 - Add dummy data to the DataTransfer when dragging a draggable object, r=enndeakin
☠☠ backed out by 53cdf23f7d18 ☠ ☠
authorMichael Layzell <michael@thelayzells.com>
Fri, 21 Apr 2017 16:38:12 -0400
changeset 591194 d20646799abb8ab802873017464461bd1a526804
parent 591193 65c3ca9e63c0c85be91d5df2c06a806e1ad7436d
child 591195 0d4fe81a5f816b5bf67bca874431a2857b52f247
push id62992
push userbsmedberg@mozilla.com
push dateThu, 08 Jun 2017 19:57:41 +0000
reviewersenndeakin
bugs1352852
milestone55.0a1
Bug 1352852 - Add dummy data to the DataTransfer when dragging a draggable object, r=enndeakin When dragging a `draggable=true` HTML DOM node, if no data is added to the DataTransfer during the DragStart event, we currently cancel the drag. This is inconsistent with Chrome's behaviour. This patch adds a chrome-only (hidden from content) item to the DataTransfer: application/x-moz-dummy-data. This data is added only when no other data has been added to the DataTransfer, and the target of the dragstart event was a draggable=true HTML DOM node. This hidden node allows for the drag event to complete successfully, while appearing the same as Chrome's behavior to content scripts. MozReview-Commit-ID: HVqEr7aR6DD
dom/events/DataTransfer.cpp
dom/events/DataTransfer.h
dom/events/EventStateManager.cpp
dom/events/test/mochitest.ini
dom/events/test/test_drag_empty.html
widget/nsITransferable.idl
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -33,16 +33,17 @@
 #include "mozilla/dom/DataTransferBinding.h"
 #include "mozilla/dom/DataTransferItemList.h"
 #include "mozilla/dom/Directory.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/FileList.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/OSFileSystem.h"
 #include "mozilla/dom/Promise.h"
+#include "mozilla/storage/Variant.h"
 #include "nsNetUtil.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(DataTransfer)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DataTransfer)
@@ -1568,10 +1569,33 @@ DataTransfer::FillInExternalCustomTypes(
       rv = variant->SetAsAString(data);
       NS_ENSURE_SUCCESS_VOID(rv);
 
       SetDataWithPrincipal(format, variant, aIndex, aPrincipal);
     }
   } while (type != eCustomClipboardTypeId_None);
 }
 
+void
+DataTransfer::AddDummyHiddenData()
+{
+  RefPtr<nsVariantCC> variant = new nsVariantCC();
+  variant->SetAsAString(EmptyString());
+
+  ErrorResult rv;
+  RefPtr<DataTransferItem> item =
+    mItems->SetDataWithPrincipal(NS_LITERAL_STRING(kDummyTypeMime),
+                                 variant,
+                                 /* aIndex = */ 0,
+                                 nsContentUtils::GetSystemPrincipal(),
+                                 /* aInsertOnly = */ true,
+                                 /* aHidden = */ true,
+                                 rv);
+  MOZ_ASSERT(item->ChromeOnly());
+
+  // We might have gotten a NS_ERROR_DOM_NOT_SUPPORTED_ERROR because we've
+  // already added an application/x-moz-dummy-data. We want to ignore that
+  // error.
+  rv.SuppressException();
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/events/DataTransfer.h
+++ b/dom/events/DataTransfer.h
@@ -265,16 +265,20 @@ public:
   // Variation of SetDataWithPrincipal with handles extracting
   // kCustomTypesMime data into separate types.
   void SetDataWithPrincipalFromOtherProcess(const nsAString& aFormat,
                                             nsIVariant* aData,
                                             uint32_t aIndex,
                                             nsIPrincipal* aPrincipal,
                                             bool aHidden);
 
+  // Adds a dummy type to the DataTransfer which is hidden from the user to
+  // allow the drag to run. This type is not visible to content code.
+  void AddDummyHiddenData();
+
   // returns a weak reference to the drag image
   Element* GetDragImage(int32_t* aX, int32_t* aY) const
   {
     *aX = mDragImageX;
     *aY = mDragImageY;
     return mDragImage;
   }
 
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -1780,16 +1780,33 @@ EventStateManager::GenerateDragGesture(n
 
       // Dispatch the dragstart event to the DOM.
       nsEventStatus status = nsEventStatus_eIgnore;
       EventDispatcher::Dispatch(targetContent, aPresContext, &startEvent,
                                 nullptr, &status);
 
       WidgetDragEvent* event = &startEvent;
 
+      // If we didn't get any data in the dataTransfer, yet the targetContent is
+      // a draggable HTML element draggable, we need to fire the drag event
+      // anyway. We'll add a custom dummy data type to the DataTransfer to make
+      // this possible. This data type will be marked as hidden so that content
+      // can't see it. This does not apply to non-HTML elements.
+      uint32_t count = 0;
+      dataTransfer->GetMozItemCount(&count);
+      if (count == 0) {
+        nsCOMPtr<nsIDOMHTMLElement> htmlDragTarget = do_QueryInterface(targetContent);
+        bool draggable = false;
+        if (htmlDragTarget &&
+            NS_SUCCEEDED(htmlDragTarget->GetDraggable(&draggable)) &&
+            draggable) {
+          dataTransfer->AddDummyHiddenData();
+        }
+      }
+
       nsCOMPtr<nsIObserverService> observerService =
         mozilla::services::GetObserverService();
       // Emit observer event to allow addons to modify the DataTransfer object.
       if (observerService) {
         observerService->NotifyObservers(dataTransfer,
                                          "on-datatransfer-available",
                                          nullptr);
       }
--- a/dom/events/test/mochitest.ini
+++ b/dom/events/test/mochitest.ini
@@ -149,16 +149,17 @@ skip-if = toolkit == 'android' #CRASH_DU
 skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
 [test_dom_mouse_event.html]
 skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
 [test_dom_storage_event.html]
 [test_dom_wheel_event.html]
 [test_draggableprop.html]
 skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
 [test_dragstart.html]
+skip-if = (!e10s && os == "win") # non-e10s windows builds hang when running this test due to the OS expecting non-synthesized input events (bug 1352852 comment 16)
 [test_error_events.html]
 skip-if = toolkit == 'android' #TIMED_OUT
 [test_eventctors.html]
 skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
 [test_eventhandler_scoping.html]
 [test_eventTimeStamp.html]
 [test_focus_disabled.html]
 [test_legacy_event.html]
@@ -172,8 +173,10 @@ skip-if = toolkit == 'android' #CRASH_DU
 [test_wheel_default_action.html]
 [test_bug687787.html]
 [test_bug1305458.html]
 [test_bug1298970.html]
 [test_bug1304044.html]
 [test_bug1332699.html]
 [test_bug1339758.html]
 [test_dnd_with_modifiers.html]
+[test_drag_empty.html]
+skip-if = (!e10s && os == "win") # non-e10s windows builds hang when running this test due to the OS expecting non-synthesized input events (bug 1352852 comment 16)
new file mode 100644
--- /dev/null
+++ b/dom/events/test/test_drag_empty.html
@@ -0,0 +1,78 @@
+<html>
+<head>
+  <title>Tests for the dragstart event</title>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<body>
+<!--
+ This test checks the behaviour of the dragstart event when no data is added to the DataTransfer.
+  -->
+  <style>
+    #droptarget { width: 100px; height: 100px; background-color: #550000; }
+  </style>
+
+<div id="draggable" draggable="true" ondragstart="onDragStart(event)">This is a bit of text.</div>
+
+<div id="droptarget" ondragover="onDragOver(event)" ondrop="onDrop(event)">THIS IS A DROP TARGET?!?!?</div>
+
+<script>
+  let dragStartFired = false;
+  function onDragStart(event) {
+    dragStartFired = true;
+    info("dragstart fired");
+  }
+
+  let dragOverFired = false;
+  function onDragOver(event) {
+    dragOverFired = true;
+    event.preventDefault();
+    info("dragover fired");
+  }
+
+  let dropFired = false;
+  function onDrop(event) {
+    dropFired = true;
+    is(event.dataTransfer.types.length, 0,
+       "There shouldn't be any data on the DataTransfer");
+    info("drop fired");
+  }
+
+  let loaded = new Promise(resolve => document.body.onload = resolve);
+
+  add_task(function*() {
+    var ds = SpecialPowers.Cc["@mozilla.org/widget/dragservice;1"].
+             getService(SpecialPowers.Ci.nsIDragService);
+    ok(!ds.getCurrentSession(), "There should be no drag session in progress");
+
+    // XXX: Make sure that we've finished loading the document before we start
+    // trying to do work with drag events.
+    yield loaded;
+
+    var draggable = $("draggable");
+    var droptarget = $("droptarget");
+    // Fire the dragstart event - this should start a drag session
+    synthesizeMouse(draggable, 6, 6, { type: "mousedown" });
+    synthesizeMouse(draggable, 14, 14, { type: "mousemove" });
+    // drags are asynchronous on Linux, so this extra event is needed to make
+    // sure the drag gets processed
+    synthesizeMouse(draggable, 15, 15, { type: "mousemove" });
+
+    ok(dragStartFired, "dragstart should have fired");
+    ok(ds.getCurrentSession(), "The drag session should have started");
+
+    // Synthesize the dragover and drop events on the target node.
+    var [result, dataTransfer] = synthesizeDragOver(draggable, droptarget);
+    synthesizeDropAfterDragOver(result, dataTransfer, droptarget);
+    ds.endDragSession(true);
+
+    ok(dropFired, "drop should have been fired");
+    ok(dragOverFired, "dragover should have been fired");
+    ok(!ds.getCurrentSession(), "The drag session should have ended");
+  });
+</script>
+</body>
+</html>
--- a/widget/nsITransferable.idl
+++ b/widget/nsITransferable.idl
@@ -45,18 +45,25 @@ interface nsIPrincipal;
 #define kFilePromiseURLMime         "application/x-moz-file-promise-url"
 // the destination filename for a file promise
 #define kFilePromiseDestFilename    "application/x-moz-file-promise-dest-filename"
 // a dataless flavor used to interact with the OS during file drags
 #define kFilePromiseMime            "application/x-moz-file-promise"
 // a synthetic flavor, put into the transferable once we know the destination directory of a file drag
 #define kFilePromiseDirectoryMime   "application/x-moz-file-promise-dir"
 
+// A custom data type used by DataTransfer for securely transferring arbitrary
+// data through the drag or clipboard object without allowing websites to set
+// arbitrary data types.
 #define kCustomTypesMime "application/x-moz-custom-clipdata"
 
+// A custom data type used during drag events to allow them to occur even when
+// no data has been attached to the drag event's Transferrable.
+#define kDummyTypeMime "application/x-moz-dummy-data"
+
 %}
 
 
 /**
   * nsIFlavorDataProvider allows a flavor to 'promise' data later,
   * supplying the data lazily.
   * 
   * To use it, call setTransferData, passing the flavor string,