Bug 1249522, when a file is present, only specify file type, r=smaug
authorNeil Deakin <neil@mozilla.com>
Wed, 11 May 2016 10:04:19 -0400 (2016-05-11)
changeset 297004 d4e621e02edccfff3eb4cb6b2a47655eca845280
parent 297003 ccf7dc442e1fea2b7ae1c1f28a6c75cbd54a3102
child 297005 bb36d2769fe3689817a8b33f9a6c0eb6d63ad1d2
push id30251
push usercbook@mozilla.com
push dateThu, 12 May 2016 09:54:48 +0000 (2016-05-12)
treeherdermozilla-central@c3f5e6079284 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1249522
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1249522, when a file is present, only specify file type, r=smaug
browser/base/content/test/general/browser.ini
browser/base/content/test/general/browser_clipboard_pastefile.js
browser/base/content/test/general/clipboard_pastefile.html
dom/events/DataTransfer.cpp
dom/events/DataTransfer.h
testing/mochitest/chrome/test_sanityChromeUtils.xul
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -21,16 +21,17 @@ support-files =
   browser_web_channel.html
   browser_web_channel_iframe.html
   bug1262648_string_with_newlines.dtd
   bug592338.html
   bug792517-2.html
   bug792517.html
   bug792517.sjs
   bug839103.css
+  clipboard_pastefile.html
   contextmenu_common.js
   ctxmenu-image.png
   discovery.html
   domplate_test.js
   download_page.html
   dummy_page.html
   feed_tab.html
   file_generic_favicon.ico
@@ -272,16 +273,17 @@ tags = mcb
 [browser_mixedContentFramesOnHttp.js]
 tags = mcb
 [browser_bug970746.js]
 [browser_bug1015721.js]
 skip-if = os == 'win'
 [browser_bug1064280_changeUrlInPinnedTab.js]
 [browser_accesskeys.js]
 [browser_clipboard.js]
+[browser_clipboard_pastefile.js]
 [browser_contentAreaClick.js]
 [browser_contextmenu.js]
 tags = fullscreen
 skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558
 [browser_contextmenu_input.js]
 skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558
 [browser_ctrlTab.js]
 [browser_datachoices_notification.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_clipboard_pastefile.js
@@ -0,0 +1,59 @@
+// This test is used to check that pasting files removes all non-file data from
+// event.clipboardData.
+
+add_task(function*() {
+  var searchbar = document.getElementById("searchbar");
+
+  searchbar.focus();
+  searchbar.value = "Text";
+  searchbar.select();
+
+  yield new Promise((resolve, reject) => {
+    searchbar.addEventListener("copy", function copyEvent(event) {
+      searchbar.removeEventListener("copy", copyEvent, true);
+      event.clipboardData.setData("text/plain", "Alternate");
+      // For this test, it doesn't matter that the file isn't actually a file.
+      event.clipboardData.setData("application/x-moz-file", "Sample");
+      event.preventDefault();
+      resolve();
+    }, true)
+
+    EventUtils.synthesizeKey("c", { accelKey: true });
+  });
+
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+              "https://example.com/browser/browser/base/content/test/general/clipboard_pastefile.html");
+  let browser = tab.linkedBrowser;
+
+  yield ContentTask.spawn(browser, { }, function* (arg) {
+    content.document.getElementById("input").focus();
+  });
+
+  yield BrowserTestUtils.synthesizeKey("v", { accelKey: true }, browser);
+
+  let output = yield ContentTask.spawn(browser, { }, function* (arg) {
+    return content.document.getElementById("output").textContent;
+  });
+  is (output, "Passed", "Paste file");
+
+  searchbar.focus();
+
+  yield new Promise((resolve, reject) => {
+    searchbar.addEventListener("paste", function copyEvent(event) {
+      searchbar.removeEventListener("paste", copyEvent, true);
+
+      let dt = event.clipboardData;
+      is(dt.types.length, 3, "number of types");
+      ok(dt.types.contains("text/plain"), "text/plain exists in types");
+      ok(dt.mozTypesAt(0).contains("text/plain"), "text/plain exists in mozTypesAt");
+      is(dt.getData("text/plain"), "Alternate", "text/plain returned in getData");
+      is(dt.mozGetDataAt("text/plain", 0), "Alternate", "text/plain returned in mozGetDataAt");
+
+      resolve();
+    }, true);
+
+    EventUtils.synthesizeKey("v", { accelKey: true });
+  });
+
+  yield BrowserTestUtils.removeTab(tab);
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/clipboard_pastefile.html
@@ -0,0 +1,37 @@
+<html><body>
+<script>
+function checkPaste(event)
+{
+  let output = document.getElementById("output");
+  output.textContent = checkPasteHelper(event);
+}
+
+function checkPasteHelper(event)
+{
+  let dt = event.clipboardData;
+  if (dt.types.length != 2)
+    return "Wrong number of types; got " + dt.types.length;
+
+  for (let type of dt.types) {
+    if (type != "Files" && type != "application/x-moz-file")
+      return "Invalid type for types; got" + type;
+  }
+
+  for (let type of dt.mozTypesAt(0)) {
+    if (type != "Files" && type != "application/x-moz-file")
+      return "Invalid type for mozTypesAt; got" + type;
+  }
+
+  if (dt.getData("text/plain"))
+    return "text/plain found with getData";
+  if (dt.mozGetDataAt("text/plain", 0))
+    return "text/plain found with mozGetDataAt";
+
+  return "Passed";
+}
+</script>
+
+<input id="input" onpaste="checkPaste(event)">
+<div id="output"></div>
+
+</body></html>
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -373,35 +373,18 @@ DataTransfer::GetFiles(nsIDOMFileList** 
   NS_IF_ADDREF(*aFileList =
     GetFileListInternal(rv, nsContentUtils::GetSystemPrincipal()));
   return rv.StealNSResult();
 }
 
 already_AddRefed<DOMStringList>
 DataTransfer::Types() const
 {
-  RefPtr<DOMStringList> types = new DOMStringList();
-  if (mItems.Length()) {
-    bool addFile = false;
-    const nsTArray<TransferItem>& item = mItems[0];
-    for (uint32_t i = 0; i < item.Length(); i++) {
-      const nsString& format = item[i].mFormat;
-      types->Add(format);
-      if (!addFile) {
-        addFile = format.EqualsASCII(kFileMime) ||
-                  format.EqualsASCII("application/x-moz-file-promise");
-      }
-    }
-
-    if (addFile) {
-      types->Add(NS_LITERAL_STRING("Files"));
-    }
-  }
-
-  return types.forget();
+  ErrorResult rv;
+  return MozTypesAt(0, rv);
 }
 
 NS_IMETHODIMP
 DataTransfer::GetTypes(nsISupports** aTypes)
 {
   RefPtr<DOMStringList> types = Types();
   types.forget(aTypes);
 
@@ -565,32 +548,49 @@ DataTransfer::GetMozSourceNode(nsIDOMNod
     *aSourceNode = nullptr;
     return NS_OK;
   }
 
   return CallQueryInterface(sourceNode, aSourceNode);
 }
 
 already_AddRefed<DOMStringList>
-DataTransfer::MozTypesAt(uint32_t aIndex, ErrorResult& aRv)
+DataTransfer::MozTypesAt(uint32_t aIndex, ErrorResult& aRv) const
 {
   // Only the first item is valid for clipboard events
   if (aIndex > 0 &&
       (mEventMessage == eCut || mEventMessage == eCopy ||
        mEventMessage == ePaste)) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return nullptr;
   }
 
   RefPtr<DOMStringList> types = new DOMStringList();
   if (aIndex < mItems.Length()) {
+    bool addFile = false;
     // note that you can retrieve the types regardless of their principal
-    nsTArray<TransferItem>& item = mItems[aIndex];
+    const nsTArray<TransferItem>& item = mItems[aIndex];
     for (uint32_t i = 0; i < item.Length(); i++) {
-      types->Add(item[i].mFormat);
+      const nsString& format = item[i].mFormat;
+      types->Add(format);
+      if (!addFile) {
+        addFile = format.EqualsASCII(kFileMime);
+      }
+    }
+
+    if (addFile) {
+      // If this is a content caller, and a file is in the data transfer, remove
+      // the non-file types. This prevents alternate text forms of the file
+      // from being returned.
+      if (!nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+        types->Clear();
+        types->Add(NS_LITERAL_STRING(kFileMime));
+      }
+
+      types->Add(NS_LITERAL_STRING("Files"));
     }
   }
 
   return types.forget();
 }
 
 NS_IMETHODIMP
 DataTransfer::MozTypesAt(uint32_t aIndex, nsISupports** aTypes)
@@ -627,22 +627,33 @@ DataTransfer::GetDataAtInternal(const ns
 
   // Only the first item is valid for clipboard events
   if (aIndex > 0 &&
       (mEventMessage == eCut || mEventMessage == eCopy ||
        mEventMessage == ePaste)) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
 
-
   nsAutoString format;
   GetRealFormat(aFormat, format);
 
   nsTArray<TransferItem>& item = mItems[aIndex];
 
+  // If this is a content caller, and a file is in the data transfer, only
+  // return the file type.
+  if (!format.EqualsLiteral(kFileMime) &&
+      !nsContentUtils::IsSystemPrincipal(aSubjectPrincipal)) {
+    uint32_t count = item.Length();
+    for (uint32_t i = 0; i < count; i++) {
+      if (item[i].mFormat.EqualsLiteral(kFileMime)) {
+        return NS_OK;
+      }
+    }
+  }
+
   // Check if the caller is allowed to access the drag data. Callers with
   // chrome privileges can always read the data. During the
   // drop event, allow retrieving the data except in the case where the
   // source of the drag is in a child frame of the caller. In that case,
   // we only allow access to data of the same principal. During other events,
   // only allow access to the data with the same principal.
   bool checkFormatItemPrincipal = mIsCrossDomainSubFrameDrop ||
       (mEventMessage != eDrop && mEventMessage != eLegacyDragDrop &&
@@ -741,18 +752,18 @@ DataTransfer::SetDataAtInternal(const ns
   // Don't allow the custom type to be assigned.
   if (aFormat.EqualsLiteral(kCustomTypesMime)) {
     return NS_ERROR_TYPE_ERR;
   }
 
   // Don't allow non-chrome to add non-string or file data. We'll block file
   // promises as well which are used internally for drags to the desktop.
   if (!nsContentUtils::IsSystemPrincipal(aSubjectPrincipal)) {
-    if (aFormat.EqualsLiteral("application/x-moz-file-promise") ||
-        aFormat.EqualsLiteral("application/x-moz-file")) {
+    if (aFormat.EqualsLiteral(kFilePromiseMime) ||
+        aFormat.EqualsLiteral(kFileMime)) {
       return NS_ERROR_DOM_SECURITY_ERR;
     }
 
     uint16_t type;
     aData->GetDataType(&type);
     if (type == nsIDataType::VTYPE_INTERFACE ||
         type == nsIDataType::VTYPE_INTERFACE_IS) {
       return NS_ERROR_DOM_SECURITY_ERR;
--- a/dom/events/DataTransfer.h
+++ b/dom/events/DataTransfer.h
@@ -175,17 +175,17 @@ public:
     if (mCursorState) {
       aCursor.AssignLiteral("default");
     } else {
       aCursor.AssignLiteral("auto");
     }
   }
 
   already_AddRefed<DOMStringList> MozTypesAt(uint32_t aIndex,
-                                             mozilla::ErrorResult& aRv);
+                                             mozilla::ErrorResult& aRv) const;
 
   void MozClearDataAt(const nsAString& aFormat, uint32_t aIndex,
                       mozilla::ErrorResult& aRv);
 
   void MozSetDataAt(JSContext* aCx, const nsAString& aFormat,
                     JS::Handle<JS::Value> aData, uint32_t aIndex,
                     mozilla::ErrorResult& aRv);
 
--- a/testing/mochitest/chrome/test_sanityChromeUtils.xul
+++ b/testing/mochitest/chrome/test_sanityChromeUtils.xul
@@ -60,17 +60,19 @@
       { type  : "text/uri-list",
         data  : "http://www.mozilla.org/" },
       { type  : "text/plain",
         data  : "this is text/plain" }
     ]];
     var dragfile = [[
       { type    : "application/x-moz-file",
         data    : testFile,
-        eqTest  : function(actualData, expectedData) {return expectedData.equals(actualData);} }
+        eqTest  : function(actualData, expectedData) {return expectedData.equals(actualData);} },
+      { type    : "Files",
+        data    : null }
     ]];
     
     function doOnDrop(aEvent) {
       gData = aEvent.dataTransfer.getData(dragDrop[0][0].type);
       aEvent.preventDefault(); // cancels event and keeps dropEffect
                                // as was before event.
     }