Bug 1453153 - Initial removal of moz* APIs in DataTransfer, r?enndeakin+6102,r?nika draft
authorAnny Gakhokidze <agakhokidze@mozilla.com>
Mon, 18 Jun 2018 23:43:31 -0400
changeset 816203 243922fcd05ba133a8445189f6a44364b6765790
parent 816202 4c55484e1a803a3dac7cc3ef9010ce601d9ad194
child 816204 04069710f3933834e439786df68770858d1e9fff
push id115774
push userbmo:agakhokidze@mozilla.com
push dateTue, 10 Jul 2018 18:09:23 +0000
reviewersenndeakin, nika
bugs1453153
milestone63.0a1
Bug 1453153 - Initial removal of moz* APIs in DataTransfer, r?enndeakin+6102,r?nika In DataTransfer, change mozItemCount, mozTypesAt, mozClearDataAt, mozSetDataAt, mozGetDataAt APIs to be ChromeOnly. MozReview-Commit-ID: 9uJ9ncrcBL2
dom/base/test/test_bug574596.html
dom/events/DataTransfer.cpp
dom/events/DataTransfer.h
dom/events/test/test_DataTransferItemList.html
dom/events/test/test_bug1264380.html
dom/events/test/test_dragstart.html
dom/tests/mochitest/general/test_clipboard_disallowed.html
dom/tests/mochitest/general/test_clipboard_events.html
dom/webidl/DataTransfer.webidl
modules/libpref/init/all.js
testing/mochitest/tests/SimpleTest/EventUtils.js
--- a/dom/base/test/test_bug574596.html
+++ b/dom/base/test/test_bug574596.html
@@ -37,21 +37,21 @@ var dragLinkText = [[
   { type:"text/_moz_htmlcontext",   data:"", eqTest:ignoreFunc },
   { type:"text/_moz_htmlinfo",      data:"", eqTest:ignoreFunc },
   { type:"text/html",               data:'<div id="link1"><a href="http://www.mozilla.org/">link1</a></div>' },
   { type:"text/plain",              data:"http://www.mozilla.org/" }
 ]];
 
 
 function dumpTransfer(dataTransfer,expect) {
-  dtData = dataTransfer.mozItemCount + "items:\n";
-  for (var i = 0; i < dataTransfer.mozItemCount; i++) {
-    var dtTypes = dataTransfer.mozTypesAt(i);
+  dtData = SpecialPowers.wrap(dataTransfer).mozItemCount + "items:\n";
+  for (var i = 0; i < SpecialPowers.wrap(dataTransfer).mozItemCount; i++) {
+    var dtTypes = SpecialPowers.wrap(dataTransfer).mozTypesAt(i);
     for (var j = 0; j < dtTypes.length; j++) {
-      var actualData = dataTransfer.mozGetDataAt(dtTypes[j],i)
+      var actualData = SpecialPowers.wrap(dataTransfer).mozGetDataAt(dtTypes[j],i)
       if (expect && expect[i] && expect[i][j]) {
         if (expect[i][j].eqTest)
           dtData += expect[i][j].eqTest(actualData,expect[i][j].data) ? "ok" : "fail";
         else
           dtData += (actualData == expect[i][j].data) ? "ok" : "fail";
       }
       dtData += "["+i+"]" + "["+j+"]: " + '"' + dtTypes[j] + '"  "' + actualData + '"\n';
     }
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -35,16 +35,18 @@
 #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 "nsNetUtil.h"
 
+#define MOZ_CALLS_ENABLED_PREF "dom.datatransfer.moz"
+
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(DataTransfer)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DataTransfer)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mItems)
@@ -1518,10 +1520,27 @@ DataTransfer::SetMode(DataTransfer::Mode
 {
   if (!PrefProtected() && aMode == Mode::Protected) {
     mMode = Mode::ReadOnly;
   } else {
     mMode = aMode;
   }
 }
 
+/* static */
+bool
+DataTransfer::MozCallsEnabled(JSContext* aCx, JSObject* aObj /*unused*/)
+{
+  // Read the pref
+  static bool sPrefCached = false;
+  static bool sPrefCacheValue = false;
+
+  if (!sPrefCached) {
+    sPrefCached = true;
+    Preferences::AddBoolVarCache(&sPrefCacheValue, MOZ_CALLS_ENABLED_PREF);
+  }
+
+  // We can expose moz* APIs if we are chrome code or if pref is enabled
+  return nsContentUtils::IsSystemCaller(aCx) || sPrefCacheValue;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/events/DataTransfer.h
+++ b/dom/events/DataTransfer.h
@@ -423,16 +423,21 @@ public:
   //
   // If kFileMime is supported, then it will be placed either at
   // index 0 or at index 1 in aResult
   static void
   GetExternalClipboardFormats(const int32_t& aWhichClipboard,
                               const bool& aPlainTextOnly,
                               nsTArray<nsCString>* aResult);
 
+  // Returns true if moz* APIs should be exposed (true for chrome code or if
+  // dom.datatransfer.moz pref is enabled).
+  // The affected moz* APIs are mozItemCount, mozTypesAt, mozClearDataAt, mozSetDataAt, mozGetDataAt
+  static bool MozCallsEnabled(JSContext* cx, JSObject* obj);
+
 protected:
 
   // caches text and uri-list data formats that exist in the drag service or
   // clipboard for retrieval later.
   nsresult CacheExternalData(const char* aFormat, uint32_t aIndex,
                              nsIPrincipal* aPrincipal, bool aHidden);
 
   // caches the formats that exist in the drag service that were added by an
--- a/dom/events/test/test_DataTransferItemList.html
+++ b/dom/events/test/test_DataTransferItemList.html
@@ -1,21 +1,21 @@
 <html>
 <head>
-  <title>Tests for the DatTransferItemList object</title>
+  <title>Tests for the DataTransferItemList object</title>
   <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
   <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/AddTask.js"></script>
   <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body style="height: 300px; overflow: auto;">
 <p id="display"> </p>
 <img id="image" draggable="true" src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%18%00%00%00%18%02%03%00%00%00%9D%19%D5k%00%00%00%04gAMA%00%00%B1%8F%0B%FCa%05%00%00%00%0CPLTE%FF%FF%FF%FF%FF%FF%F7%DC%13%00%00%00%03%80%01X%00%00%00%01tRNS%08N%3DPT%00%00%00%01bKGD%00%88%05%1DH%00%00%00%09pHYs%00%00%0B%11%00%00%0B%11%01%7Fd_%91%00%00%00%07tIME%07%D2%05%0C%14%0C%0D%D8%3F%1FQ%00%00%00%5CIDATx%9C%7D%8E%CB%09%C0%20%10D%07r%B7%20%2F%E9wV0%15h%EA%D9%12D4%BB%C1x%CC%5C%1E%0C%CC%07%C0%9C0%9Dd7()%C0A%D3%8D%E0%B8%10%1DiCHM%D0%AC%D2d%C3M%F1%B4%E7%FF%10%0BY%AC%25%93%CD%CBF%B5%B2%C0%3Alh%CD%AE%13%DF%A5%F7%E0%03byW%09A%B4%F3%E2%00%00%00%00IEND%AEB%60%82">
-<div id="over" "style="width: 100px; height: 100px; border: 2px black dashed;">
+<div id="over" style="width: 100px; height: 100px; border: 2px black dashed;">
   drag over here
 </div>
 
 <script>
   function spin() {
     // Defer to the event loop twice to wait for any events to be flushed out.
     return new Promise(function(a) {
       SimpleTest.executeSoon(function() {
--- a/dom/events/test/test_bug1264380.html
+++ b/dom/events/test/test_bug1264380.html
@@ -25,17 +25,17 @@ function runTests()
   target.href = "http://www.mozilla.org/";
   shadow.appendChild(target);
 
   let dataTransfer;
   let trapDrag = function(event) {
     ok(true, "Got dragstart event");
     dataTransfer = event.dataTransfer;
     ok(dataTransfer, "DataTransfer object is available.");
-    is(dataTransfer.mozItemCount, 1, "initial link item count");
+    is(SpecialPowers.wrap(dataTransfer).mozItemCount, 1, "initial link item count");
     is(dataTransfer.getData("text/uri-list"), "http://www.mozilla.org/", "link text/uri-list");
     is(dataTransfer.getData("text/plain"), "http://www.mozilla.org/", "link text/plain");
   }
 
   ok(!dragService.getCurrentSession(), "There shouldn't be a drag session!");
   iframeWin.addEventListener("dragstart", trapDrag, true);
   synthesizeMouse(target, 2, 2, { type: "mousedown" }, iframeWin);
   synthesizeMouse(target, 11, 11, { type: "mousemove" }, iframeWin);
--- a/dom/events/test/test_dragstart.html
+++ b/dom/events/test/test_dragstart.html
@@ -36,19 +36,19 @@ function afterDragTests()
   // be read only.
   ok(gDataTransfer instanceof DataTransfer, "DataTransfer after dragstart event");
   checkTypes(gDataTransfer, [], 0, "after dragstart event");
 
   expectError(() => gDataTransfer.setData("text/plain", "Some Text"),
               "NoModificationAllowedError", "setData when read only");
   expectError(() => gDataTransfer.clearData("text/plain"),
               "NoModificationAllowedError", "clearData when read only");
-  expectError(() => gDataTransfer.mozSetDataAt("text/plain", "Some Text", 0),
+  expectError(() => SpecialPowers.wrap(gDataTransfer).mozSetDataAt("text/plain", "Some Text", 0),
               "NoModificationAllowedError", "setDataAt when read only");
-  expectError(() => gDataTransfer.mozClearDataAt("text/plain", 0),
+  expectError(() => SpecialPowers.wrap(gDataTransfer).mozClearDataAt("text/plain", 0),
               "NoModificationAllowedError", "clearDataAt when read only");
   expectError(() => gDataTransfer.addElement(draggable),
               "NoModificationAllowedError", "addElement when read only");
 
   var evt = document.createEvent("dragevent");
   ok(evt instanceof DragEvent, "synthetic dragevent class")
   ok(evt instanceof MouseEvent, "synthetic event inherits from MouseEvent")
   evt.initDragEvent("dragstart", true, true, window, 1, 40, 35, 20, 15,
@@ -125,33 +125,36 @@ function doDragStartSelection(event)
 
   // text/unicode and Text are available for compatibility. They retrieve the
   // text/plain data
   is(dt.getData("text/unicode"), "This is a draggable bit of text.", "initial selection text/unicode");
   is(dt.getData("Text"), "This is a draggable bit of text.", "initial selection Text");
   is(dt.getData("TEXT"), "This is a draggable bit of text.", "initial selection TEXT");
   is(dt.getData("text/UNICODE"), "This is a draggable bit of text.", "initial selection text/UNICODE"); 
 
-  is(dt.mozItemCount, 1, "initial selection item count");
+  is(SpecialPowers.wrap(dt).mozItemCount, 1, "initial selection item count");
 
   dt.clearData("text/plain");
   dt.clearData("text/html");
   dt.clearData("text/_moz_htmlinfo");
   dt.clearData("text/_moz_htmlcontext");
 
   test_DataTransfer(dt);
   setTimeout(afterDragTests, 0);
 }
 
 function test_DataTransfer(dt)
 {
-  is(dt.mozItemCount, 0, "empty itemCount");
+  is(SpecialPowers.wrap(dt).mozItemCount, 0, "empty itemCount");
 
   var types = dt.types;
   ok(Array.isArray(types), "empty types is an Array");
+  // The above test fails if we have SpecialPowers.wrap(dt).types instead of dt.types
+  // So wrap with special powers after the test
+  dt = SpecialPowers.wrap(dt);
   checkTypes(dt, [], 0, "empty");
   is(dt.getData("text/plain"), "", "empty data is empty");
 
   // calling setDataAt requires an index that is 0 <= index <= dt.itemCount
   expectError(() => dt.mozSetDataAt("text/plain", "Some Text", 1),
               "IndexSizeError", "setDataAt index too high");
 
   is(dt.mozUserCancelled, false, "userCancelled");
@@ -311,30 +314,28 @@ function test_DataTransfer(dt)
   checkOneDataItem(dt, ["text/plain", "text/html"],
                    ["Changed Second Item", "<em>Second Item</em>"], 1, "changed with setData item at index 1");
 
   dt.mozSetDataAt("application/-moz-node", "draggable", 2);
   is(dt.mozItemCount, 3, "setDataAt node itemCount");
   checkOneDataItem(dt, ["application/-moz-node"], ["draggable"], 2, "setDataAt node item at index 2");
 
   // Try to add and then remove a non-string type to the DataTransfer and ensure
-  // that the type appears in DataTransfer.types. These calls need to be called
-  // with SpecialPowers.wrap(), as adding and removing non-string/file types to
-  // DataTransfer is chrome-only.
+  // that the type appears in DataTransfer.types.
   {
-    SpecialPowers.wrap(dt).mozSetDataAt("application/-x-body", document.body, 0);
+    dt.mozSetDataAt("application/-x-body", document.body, 0);
     let found = false;
     for (let i = 0; i < dt.types.length; ++i) {
       if (dt.types[i] == "application/-x-body") {
         found = true;
         break;
       }
     }
     ok(found, "Data should appear in datatransfer.types despite having a non-string type");
-    SpecialPowers.wrap(dt).mozClearDataAt("application/-x-body", 0);
+    dt.mozClearDataAt("application/-x-body", 0);
   }
 
   dt.mozClearDataAt("text/html", 1);
   is(dt.mozItemCount, 3, "clearDataAt itemCount");
   checkOneDataItem(dt, ["text/plain", "text/html"],
                    ["First Item", "Changed with setData"], 0, "clearDataAt item at index 0");
   checkOneDataItem(dt, ["text/plain"], ["Changed Second Item"], 1, "clearDataAt item at index 1");
 
@@ -414,48 +415,48 @@ function test_DataTransfer(dt)
 }
 
 function doDragStartLink(event)
 {
   var dt = event.dataTransfer;
   checkTypes(dt, ["text/x-moz-url", "text/x-moz-url-data", "text/x-moz-url-desc", "text/uri-list",
                   "text/_moz_htmlcontext", "text/_moz_htmlinfo", "text/html", "text/plain"], 0, "initial link");
 
-  is(dt.mozItemCount, 1, "initial link item count");
+  is(SpecialPowers.wrap(dt).mozItemCount, 1, "initial link item count");
   is(dt.getData("text/uri-list"), "http://www.mozilla.org/", "link text/uri-list");
   is(dt.getData("text/plain"), "http://www.mozilla.org/", "link text/plain");
 
   event.preventDefault();
 
   gExtraDragTests++;
 }
 
 function doDragStartImage(event)
 {
   var dataurl = $("image").src;
 
   var dt = event.dataTransfer;
   checkTypes(dt, ["text/x-moz-url", "text/x-moz-url-data", "text/x-moz-url-desc", "text/uri-list",
                   "text/_moz_htmlcontext", "text/_moz_htmlinfo", "text/html", "text/plain"], 0, "initial image");
 
-  is(dt.mozItemCount, 1, "initial image item count");
+  is(SpecialPowers.wrap(dt).mozItemCount, 1, "initial image item count");
   is(dt.getData("text/uri-list"), dataurl, "image text/uri-list");
   is(dt.getData("text/plain"), dataurl, "image text/plain");
 
   event.preventDefault();
 
   gExtraDragTests++;
 }
 
 function doDragStartInput(event)
 {
   var dt = event.dataTransfer;
   checkTypes(dt, ["text/plain"], 0, "initial input");
 
-  is(dt.mozItemCount, 1, "initial input item count");
+  is(SpecialPowers.wrap(dt).mozItemCount, 1, "initial input item count");
 //  is(dt.getData("text/plain"), "Text", "input text/plain");
 
 //  event.preventDefault();
 }
 
 function doDragStartSynthetic(event)
 {
   is(event.type, "dragstart", "synthetic dragstart event type");
@@ -506,21 +507,22 @@ function doDragOverSynthetic(event)
 //  Uncomment next two lines once the todo instanceof above is fixed.
 //  dt.setData("text/plain", "Text");
 //  is(dt.getData("text/plain"), "Text", "synthetic dragover data is set after adding");
 }
 
 function onDragStartDraggable(event)
 {
   var dt = event.dataTransfer;
-  ok(dt.mozItemCount == 0 && dt.types.length == 0 && event.originalTarget == gDragInfo.target, gDragInfo.testid);
+  ok(SpecialPowers.wrap(dt).mozItemCount == 0 && dt.types.length == 0 && event.originalTarget == gDragInfo.target, gDragInfo.testid);
 
   gExtraDragTests++;
 }
 
+// Expects dt wrapped in SpecialPowers
 function checkOneDataItem(dt, expectedtypes, expecteddata, index, testid)
 {
   checkTypes(dt, expectedtypes, index, testid);
   for (var f = 0; f < expectedtypes.length; f++) {
     if (index == 0)
       is(dt.getData(expectedtypes[f]), expecteddata[f], testid + " getData " + expectedtypes[f]);
     is(dt.mozGetDataAt(expectedtypes[f], index), expecteddata[f] ? expecteddata[f] : null,
        testid + " getDataAt " + expectedtypes[f]);
@@ -532,23 +534,24 @@ function checkTypes(dt, expectedtypes, i
   if (index == 0) {
     var types = dt.types;
     is(types.length, expectedtypes.length, testid + " types length");
     for (var f = 0; f < expectedtypes.length; f++) {
       is(types[f], expectedtypes[f], testid + " " + types[f] + " check");
     }
   }
 
-  types = dt.mozTypesAt(index);
+  types = SpecialPowers.wrap(dt).mozTypesAt(index);
   is(types.length, expectedtypes.length, testid + " typesAt length");
   for (var f = 0; f < expectedtypes.length; f++) {
     is(types[f], expectedtypes[f], testid + " " + types[f] + " at " + index + " check");
   }
 }
 
+// Expects dt wrapped in SpecialPowers
 function checkURL(dt, url, fullurllist, index, testid)
 {
   is(dt.getData("text/uri-list"), fullurllist, testid + " text/uri-list");
   is(dt.getData("URL"), url, testid + " URL");
   is(dt.mozGetDataAt("text/uri-list", 0), fullurllist, testid + " text/uri-list");
   is(dt.mozGetDataAt("URL", 0), fullurllist, testid + " URL");
 }
 
--- a/dom/tests/mochitest/general/test_clipboard_disallowed.html
+++ b/dom/tests/mochitest/general/test_clipboard_disallowed.html
@@ -19,41 +19,33 @@ function doTest()
 }
 
 function checkAllowed(event)
 {
   let clipboardData = event.clipboardData;
 
   let exception;
   try {
-    clipboardData.mozSetDataAt("text/customdata", document.getElementById("input"), 0);
-  } catch(ex) {
-    exception = ex;
-  }
-  is(String(exception).indexOf("SecurityError"), 0, "Cannot set non-string");
-
-  exception = null;
-  try {
-    clipboardData.mozSetDataAt("application/x-moz-file", "Test", 0);
+    clipboardData.setData("application/x-moz-file", "Test");
   } catch(ex) {
     exception = ex;
   }
   is(String(exception).indexOf("SecurityError"), 0, "Cannot set file");
 
   exception = null;
   try {
-    clipboardData.mozSetDataAt("application/x-moz-file-promise", "Test", 0);
+    clipboardData.setData("application/x-moz-file-promise", "Test");
   } catch(ex) {
     exception = ex;
   }
   is(String(exception).indexOf("SecurityError"), 0, "Cannot set file promise");
 
   exception = null;
   try {
-    clipboardData.mozSetDataAt("application/something", "This is data", 0);
+    clipboardData.setData("application/something", "This is data");
   } catch(ex) {
     exception = ex;
   }
   is(exception, null, "Can set custom data to a string");
   SimpleTest.finish();
 }
 
 SimpleTest.waitForExplicitFinish();
--- a/dom/tests/mochitest/general/test_clipboard_events.html
+++ b/dom/tests/mochitest/general/test_clipboard_events.html
@@ -297,17 +297,17 @@ add_task(async function test_input_cut_d
 
   // Cut using event.dataTransfer. The event is not cancelled so the default
   // cut should occur
   selectContentInput();
   contentInput.oncut = function(event) {
     ok(event instanceof ClipboardEvent, "cut event is a ClipboardEvent");
     ok(event.clipboardData instanceof DataTransfer, "cut event dataTransfer is a DataTransfer");
     is(event.target, contentInput, "cut event target");
-    is(event.clipboardData.mozItemCount, 0, "cut event mozItemCount");
+    is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 0, "cut event mozItemCount");
     is(event.clipboardData.getData("text/plain"), "", "cut event getData");
     event.clipboardData.setData("text/plain", "This is some dataTransfer text");
     cachedCutData = event.clipboardData;
   };
   try {
     await putOnClipboard("INPUT TEXT", () => {
       synthesizeKey("x", {accelKey: 1});
     }, "cut using dataTransfer on plaintext editor set clipboard correctly");
@@ -344,17 +344,17 @@ add_task(async function test_input_copy_
   await reset();
 
   // Copy using event.dataTransfer
   selectContentInput();
   contentInput.oncopy = function(event) {
     ok(event instanceof ClipboardEvent, "copy event is a ClipboardEvent");
     ok(event.clipboardData instanceof DataTransfer, "copy event dataTransfer is a DataTransfer");
     is(event.target, contentInput, "copy event target");
-    is(event.clipboardData.mozItemCount, 0, "copy event mozItemCount");
+    is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 0, "copy event mozItemCount");
     is(event.clipboardData.getData("text/plain"), "", "copy event getData");
     event.clipboardData.setData("text/plain", "Copied dataTransfer text");
     cachedCopyData = event.clipboardData;
   };
   try {
     await putOnClipboard("INPUT TEXT", () => {
       synthesizeKey("c", {accelKey: 1});
     }, "copy using dataTransfer on plaintext editor set clipboard correctly");
@@ -389,17 +389,17 @@ add_task(async function test_input_paste
   await reset();
 
   // Paste using event.dataTransfer
   selectContentInput();
   contentInput.onpaste = function(event) {
     ok(event instanceof ClipboardEvent, "paste event is an ClipboardEvent");
     ok(event.clipboardData instanceof DataTransfer, "paste event dataTransfer is a DataTransfer");
     is(event.target, contentInput, "paste event target");
-    is(event.clipboardData.mozItemCount, 1, "paste event mozItemCount");
+    is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 1, "paste event mozItemCount");
     is(event.clipboardData.getData("text/plain"), clipboardInitialValue, "paste event getData");
     cachedPasteData = event.clipboardData;
   };
   try {
     synthesizeKey("v", {accelKey: 1});
     is(getClipboardText(), clipboardInitialValue,
       "paste using dataTransfer on plaintext editor did not modify clipboard contents");
     is(contentInput.value, clipboardInitialValue,
@@ -435,31 +435,31 @@ add_task(async function test_input_copyp
 
   // Cut several types of data and paste it again
   contentInput.value = "This is a line of text";
   contentInput.oncopy = function(event) {
     var cd = event.clipboardData;
     cd.setData("text/plain", "would be a phrase");
 
     var exh = false;
-    try { cd.mozSetDataAt("text/plain", "Text", 1); } catch (ex) { exh = true; }
+    try { SpecialPowers.wrap(cd).mozSetDataAt("text/plain", "Text", 1); } catch (ex) { exh = true; }
     ok(exh, "exception occured mozSetDataAt 1");
     exh = false;
-    try { cd.mozTypesAt(1); } catch (ex) { exh = true; }
+    try { SpecialPowers.wrap(cd).mozTypesAt(1); } catch (ex) { exh = true; }
     ok(exh, "exception occured mozTypesAt 1");
     exh = false;
-    try { cd.mozGetDataAt("text/plain", 1); } catch (ex) { exh = true; }
+    try { SpecialPowers.wrap(cd).mozGetDataAt("text/plain", 1); } catch (ex) { exh = true; }
     ok(exh, "exception occured mozGetDataAt 1");
     exh = false;
     try { cd.mozClearDataAt("text/plain", 1); } catch (ex) { exh = true; }
     ok(exh, "exception occured mozClearDataAt 1");
 
     cd.setData("text/x-moz-url", "http://www.mozilla.org");
-    cd.mozSetDataAt("text/x-custom", "Custom Text with \u0000 null", 0);
-    is(cd.mozItemCount, 1, "mozItemCount after set multiple types");
+    SpecialPowers.wrap(cd).mozSetDataAt("text/x-custom", "Custom Text with \u0000 null", 0);
+    is(SpecialPowers.wrap(cd).mozItemCount, 1, "mozItemCount after set multiple types");
     return false;
   };
 
   try {
     selectContentInput();
 
     await putOnClipboard("would be a phrase", () => {
       synthesizeKey("c", {accelKey: 1});
@@ -468,17 +468,17 @@ add_task(async function test_input_copyp
   finally {
     contentInput.oncopy = null;
   }
 
   contentInput.setSelectionRange(5, 14);
 
   contentInput.onpaste = function(event) {
     var cd = event.clipboardData;
-    is(cd.mozItemCount, 1, "paste after copy multiple types mozItemCount");
+    is(SpecialPowers.wrap(cd).mozItemCount, 1, "paste after copy multiple types mozItemCount");
     is(cd.getData("text/plain"), "would be a phrase", "paste text/plain multiple types");
 
     // Firefox for Android's clipboard code doesn't handle x-moz-url. Therefore
     // disabling the following test. Enable this once bug #840101 is fixed.
     if (!navigator.appVersion.includes("Android")) {
       is(cd.getData("text/x-moz-url"), "http://www.mozilla.org", "paste text/x-moz-url multiple types");
       is(cd.getData("text/x-custom"), "Custom Text with \u0000 null", "paste text/custom multiple types");
     } else {
@@ -635,20 +635,20 @@ function compareSynthetic(event, eventty
   if (step == "during") {
     is(eventtype, expectedData.type, "synthetic " + eventtype + " event fired");
   }
 
   ok(event.clipboardData instanceof DataTransfer, "clipboardData is assigned");
 
   is(event.type, expectedData.type, "synthetic " + eventtype + " event type");
   if (expectedData.data === null) {
-    is(event.clipboardData.mozItemCount, 0, "synthetic " + eventtype + " empty data");
+    is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 0, "synthetic " + eventtype + " empty data");
   }
   else {
-    is(event.clipboardData.mozItemCount, 1, "synthetic " + eventtype + " item count");
+    is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 1, "synthetic " + eventtype + " item count");
     is(event.clipboardData.types.length, 1, "synthetic " + eventtype + " types length");
     is(event.clipboardData.getData(expectedData.dataType), expectedData.data,
        "synthetic " + eventtype + " data");
   }
 
   is(getClipboardText(), "empty", "event does not change the clipboard " + step + " dispatch");
 
   if (step == "during") {
@@ -661,17 +661,17 @@ async function checkCachedDataTransfer(c
 
   await putOnClipboard("Some Clipboard Text", () => { setClipboardText("Some Clipboard Text") },
                        "change clipboard outside of event");
 
   var oldtext = cd.getData("text/plain");
   ok(!oldtext, "clipboard get using " + testprefix);
 
   try {
-    cd.mozSetDataAt("text/plain", "Test Cache Data", 0);
+    SpecialPowers.wrap(cd).mozSetDataAt("text/plain", "Test Cache Data", 0);
   } catch (ex) {}
   ok(!cd.getData("text/plain"), "clipboard set using " + testprefix);
 
   is(getClipboardText(), "Some Clipboard Text", "clipboard not changed using " + testprefix);
 
   try {
     cd.mozClearDataAt("text/plain", 0);
   } catch (ex) {}
--- a/dom/webidl/DataTransfer.webidl
+++ b/dom/webidl/DataTransfer.webidl
@@ -49,17 +49,17 @@ partial interface DataTransfer {
    * @throws NO_MODIFICATION_ALLOWED_ERR if the item cannot be modified
    */
   [Throws, UseCounter]
   void addElement(Element element);
 
   /**
    * The number of items being dragged.
    */
-  [UseCounter]
+  [Func="DataTransfer::MozCallsEnabled"]
   readonly attribute unsigned long mozItemCount;
 
   /**
    * Sets the drag cursor state. Primarily used to control the cursor during
    * tab drags, but could be expanded to other uses. XXX Currently implemented
    * on Win32 only.
    *
    * Possible values:
@@ -72,34 +72,34 @@ partial interface DataTransfer {
   [UseCounter]
   attribute DOMString mozCursor;
 
   /**
    * Holds a list of the format types of the data that is stored for an item
    * at the specified index. If the index is not in the range from 0 to
    * itemCount - 1, an empty string list is returned.
    */
-  [Throws, NeedsCallerType, UseCounter]
+  [Throws, NeedsCallerType, Func="DataTransfer::MozCallsEnabled"]
   DOMStringList mozTypesAt(unsigned long index);
 
   /**
    * Remove the data associated with the given format for an item at the
    * specified index. The index is in the range from zero to itemCount - 1.
    *
    * If the last format for the item is removed, the entire item is removed,
    * reducing the itemCount by one.
    *
    * If format is empty, then the data associated with all formats is removed.
    * If the format is not found, then this method has no effect.
    *
    * @param format the format to remove
    * @throws NS_ERROR_DOM_INDEX_SIZE_ERR if index is greater or equal than itemCount
    * @throws NO_MODIFICATION_ALLOWED_ERR if the item cannot be modified
    */
-  [Throws, NeedsSubjectPrincipal, UseCounter]
+  [Throws, NeedsSubjectPrincipal, Func="DataTransfer::MozCallsEnabled"]
   void mozClearDataAt(DOMString format, unsigned long index);
 
   /*
    * A data transfer may store multiple items, each at a given zero-based
    * index. setDataAt may only be called with an index argument less than
    * itemCount in which case an existing item is modified, or equal to
    * itemCount in which case a new item is added, and the itemCount is
    * incremented by one.
@@ -113,29 +113,29 @@ partial interface DataTransfer {
    * (which will be converted into a string) or an nsISupports.
    *
    * @param format the format to add
    * @param data the data to add
    * @throws NS_ERROR_NULL_POINTER if the data is null
    * @throws NS_ERROR_DOM_INDEX_SIZE_ERR if index is greater than itemCount
    * @throws NO_MODIFICATION_ALLOWED_ERR if the item cannot be modified
    */
-  [Throws, NeedsSubjectPrincipal, UseCounter]
+  [Throws, NeedsSubjectPrincipal, Func="DataTransfer::MozCallsEnabled"]
   void mozSetDataAt(DOMString format, any data, unsigned long index);
 
   /**
    * Retrieve the data associated with the given format for an item at the
    * specified index, or null if it does not exist. The index should be in the
    * range from zero to itemCount - 1.
    *
    * @param format the format of the data to look up
    * @returns the data of the given format, or null if it doesn't exist.
    * @throws NS_ERROR_DOM_INDEX_SIZE_ERR if index is greater or equal than itemCount
    */
-  [Throws, NeedsSubjectPrincipal, UseCounter]
+  [Throws, NeedsSubjectPrincipal, Func="DataTransfer::MozCallsEnabled"]
   any mozGetDataAt(DOMString format, unsigned long index);
 
   /**
    * Update the drag image. Arguments are the same as setDragImage. This is only
    * valid within the parent chrome process.
    */
   [ChromeOnly]
   void updateDragImage(Element image, long x, long y);
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5839,9 +5839,12 @@ pref("general.document_open_conversion_d
 // If true, touchstart and touchmove listeners on window, document,
 // documentElement and document.body are passive by default.
 pref("dom.event.default_to_passive_touch_listeners", true);
 
 // Disables clipboard reads and writes by default.
 pref("dom.events.asyncClipboard", false);
 pref("dom.events.asyncClipboard.dataTransfer", false);
 // Should only be enabled in tests
-pref("dom.events.testing.asyncClipboard", false);
\ No newline at end of file
+pref("dom.events.testing.asyncClipboard", false);
+
+// Expose moz* APIs in DataTransfer
+pref("dom.datatransfer.moz", true);
\ No newline at end of file
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -2583,17 +2583,17 @@ function synthesizeDragOver(aSrcElement,
 
   // This method runs before other callbacks, and acts as a way to inject the
   // initial drag data into the DataTransfer.
   function fillDrag(event) {
     if (aDragData) {
       for (var i = 0; i < aDragData.length; i++) {
         var item = aDragData[i];
         for (var j = 0; j < item.length; j++) {
-          event.dataTransfer.mozSetDataAt(item[j].type, item[j].data, i);
+          SpecialPowers.wrap(event.dataTransfer).mozSetDataAt(item[j].type, item[j].data, i);
         }
       }
     }
     event.dataTransfer.dropEffect = aDropEffect || "move";
     event.preventDefault();
   }
 
   function trapDrag(subject, topic) {