Bug 1356543 - Allow images to be copied via dataTransfer.setData draft
authorRob Wu <rob@robwu.nl>
Wed, 05 Jul 2017 14:26:09 +0200
changeset 604149 d0359806a78e3a144d10bf040ecf5a35784e6ee6
parent 604148 4a9a4aea04579ead8133774ef6c18575fb053f74
child 636105 8ed344c6f0e34589ef664c2d8bdf9e6593226a15
push id66977
push userbmo:rob@robwu.nl
push dateWed, 05 Jul 2017 12:39:50 +0000
bugs1356543
milestone56.0a1
Bug 1356543 - Allow images to be copied via dataTransfer.setData A previous patch added support for copying images via Blob/File objects: dataTransfer.mozSetData("application/x-moz-nativeimage", blob, 0); This patch adds support for the following: dataTransfer.setData("application/x-moz-nativeimage", string); In the case with Blob/File objects, the MIME type is derived from the Blob/File's type. With strings, the type must be a PNG image. Why do we have both? - Strings must fully fit in memory, while Blob/File instances can be backed by the filesystem and have lesser memory requirements. - mozSetData might be gone in the future (https://bugzi.la/1356543#c17). The alternative (dataTransfer.setData) only supports string values. MozReview-Commit-ID: 7i2gKKT9eeh
dom/events/DataTransfer.cpp
dom/events/test/test_bug1356543.html
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -1255,38 +1255,61 @@ DataTransfer::ConvertFromVariant(nsIVari
   return true;
 }
 
 bool
 DataTransfer::TryTransferAsImage(const nsAString& aFormat,
                                  nsIVariant* aVariant,
                                  nsITransferable* aTransferable) const
 {
-  nsCOMPtr<nsISupports> data;
-  nsresult rv = aVariant->GetAsISupports(getter_AddRefs(data));
+  uint16_t dataType;
+  nsresult rv = aVariant->GetDataType(&dataType);
   NS_ENSURE_SUCCESS(rv, false);
 
-  nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(data);
-  if (!blobImpl) {
-    nsCOMPtr<nsIDOMBlob> domBlob = do_QueryInterface(data);
-    NS_ENSURE_TRUE(domBlob, false);
-    blobImpl = static_cast<Blob*>(domBlob.get())->Impl();
-  }
+  nsCOMPtr<nsIInputStream> imageStream;
+  nsAutoCString mimeTypeUTF8;
+  if (dataType == nsIDataType::VTYPE_INTERFACE ||
+      dataType == nsIDataType::VTYPE_INTERFACE_IS) {
+    nsCOMPtr<nsISupports> data;
+    nsresult rv = aVariant->GetAsISupports(getter_AddRefs(data));
+    NS_ENSURE_SUCCESS(rv, false);
+
+    nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(data);
+    if (!blobImpl) {
+      nsCOMPtr<nsIDOMBlob> domBlob = do_QueryInterface(data);
+      NS_ENSURE_TRUE(domBlob, false);
+      blobImpl = static_cast<Blob*>(domBlob.get())->Impl();
+    }
 
-  nsCOMPtr<nsIInputStream> imageStream;
-  IgnoredErrorResult error;
-  blobImpl->GetInternalStream(getter_AddRefs(imageStream), error);
-  if (NS_WARN_IF(error.Failed())) {
-    return false;
+    IgnoredErrorResult error;
+    blobImpl->GetInternalStream(getter_AddRefs(imageStream), error);
+    if (NS_WARN_IF(error.Failed())) {
+      return false;
+    }
+    nsAutoString mimeTypeUTF16;
+    blobImpl->GetType(mimeTypeUTF16);
+    mimeTypeUTF8 = NS_ConvertUTF16toUTF8(mimeTypeUTF16);
+  } else {
+    // mozSetData might be deprecated in the future (bug 1345192), and the
+    // standard setData method only supports strings. So let's also support
+    // copying images that are stored in binary strings.
+    char* chrs;
+    uint32_t len = 0;
+    rv = aVariant->GetAsStringWithSize(&len, &chrs);
+    NS_ENSURE_SUCCESS(rv, false);
+
+    nsAutoCString str;
+    str.Adopt(chrs, len);
+    NS_NewCStringInputStream(getter_AddRefs(imageStream), str);
+    // When the input is a string, there is no way to specify an explicit MIME
+    // type. Since the stored image is represented as a PNG anyway, require the
+    // input to be in PNG format too.
+    mimeTypeUTF8 = "image/png";
   }
 
-  nsAutoString mimeTypeUTF16;
-  blobImpl->GetType(mimeTypeUTF16);
-  NS_ConvertUTF16toUTF8 mimeTypeUTF8(mimeTypeUTF16);
-
   nsCOMPtr<imgITools> imgtool = do_GetService(NS_IMGTOOLS_CID);
   NS_ENSURE_TRUE(imgtool, false);
 
   nsCOMPtr<imgIContainer> imgContainer;
   rv = imgtool->DecodeImage(imageStream, mimeTypeUTF8, getter_AddRefs(imgContainer));
   NS_ENSURE_SUCCESS(rv, false);
 
   nsCOMPtr<nsISupportsInterfacePointer> imgPtr =
--- a/dom/events/test/test_bug1356543.html
+++ b/dom/events/test/test_bug1356543.html
@@ -8,17 +8,19 @@
 
 <body>
 <div contentEditable id="editable"></div>
 <script class="testbody">
 
 const kNativeImageMime = "application/x-moz-nativeimage";
 const DUMMY_TEXT = "dummy text for clipboard";
 
-async function doCopyAsImage(data) {
+// Copies |data| to the clipboard using mozSetDataAt.
+// If |useDataTransferSetData| is true, setData is used instead.
+async function doCopyAsImage(data, useDataTransferSetData = false) {
   // Clear the clipboard by unconditionally writing dummy data.
   await new Promise(resolve => {
     document.addEventListener("copy", function(event) {
       event.preventDefault();
       event.clipboardData.clearData();
       event.clipboardData.setData('Text', DUMMY_TEXT);
       resolve();
     }, {once: true});
@@ -26,17 +28,21 @@ async function doCopyAsImage(data) {
     synthesizeKey("c", { accelKey: true });
   });
 
   // Pastes the actual data.
   await new Promise(resolve => {
     document.addEventListener("copy", function(event) {
       event.preventDefault();
       event.clipboardData.clearData();
-      event.clipboardData.mozSetDataAt(kNativeImageMime, data, 0);
+      if (useDataTransferSetData) {
+        event.clipboardData.setData(kNativeImageMime, data);
+      } else {
+        event.clipboardData.mozSetDataAt(kNativeImageMime, data, 0);
+      }
       resolve();
     }, {once: true});
     
     synthesizeKey("c", { accelKey: true });
   });
 }
 
 async function doPaste(onPaste) {
@@ -123,25 +129,27 @@ async function loadBlobAsImage(blob) {
 
 function createBlob(type, b64data) {
   var data = Uint8Array.from(atob(b64data), c => c.charCodeAt(0));
   return new Blob([data], {type});
 }
 
 const JPEG_DATA64 = "/9j/4AAQSkZJRgABAQEAYABgAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBkZWZhdWx0IHF1YWxpdHkK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAAQABAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A4uiiivmT9xP/2Q==";
 
+const PNG_DATA64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAABlBMVEX/AAD///9BHTQRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAACklEQVQImWNgAAAAAgAB9HFkpgAAAABJRU5ErkJggg==";
+
 async function doTest() {
   SimpleTest.waitForExplicitFinish();
 
   // Red 1x1 images.
   var testImages = [
     createBlob("image/gif","R0lGODdhAQABAIAAAP8AAP///ywAAAAAAQABAAACAkQBADs="),
     createBlob("image/jpeg", JPEG_DATA64),
     createBlob("image/jpg", JPEG_DATA64),
-    createBlob("image/png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAABlBMVEX/AAD///9BHTQRAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAACklEQVQImWNgAAAAAgAB9HFkpgAAAABJRU5ErkJggg=="),
+    createBlob("image/png", PNG_DATA64),
   ];
   var height = 1;
   var width = 1;
   for (var blob of testImages) {
     info("Testing copy and paste of image of type " + blob.type);
 
     // Sanity check: Verify that the input image is already valid.
     verifyImage(await loadBlobAsImage(blob), width, height);
@@ -157,19 +165,25 @@ async function doTest() {
   await doCopyAsImage(new Blob([testImages[0]]));
   await pasteAndExpectEmptyClipboard();
 
   info("blos with invalid image data cannot be exported as an image");
   await doCopyAsImage(new Blob([], { type: "image/png" }));
   await pasteAndExpectEmptyClipboard();
 
   info("non-Blob values cannot be exported as an image");
-  await doCopyAsImage("this is not a blob");
+  await doCopyAsImage(null);
   await pasteAndExpectEmptyClipboard();
-  await doCopyAsImage(null);
+
+  info("Testing copy+paste of PNG image with setData instead of mozSetDataAt");
+  await doCopyAsImage(atob(PNG_DATA64), true);
+  await pasteAndVerify("image/png", width, height);
+
+  info("Strings with JPEG data cannot be exported as a PNG image");
+  await doCopyAsImage(atob(JPEG_DATA64), true);
   await pasteAndExpectEmptyClipboard();
 
   SimpleTest.finish();
 }
 
 </script>
 <body onload="doTest();"></body>
 </html>