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
--- 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>