Skip to content

Commit

Permalink
Bug 1437126 - Support imgIContainer as a file type in DataTransfer. r…
Browse files Browse the repository at this point in the history
…=nika

When using images with drag & drop (or the clipboard) we use two or more different types:
- When dragging an image we put it into the DataTransfer object using the
  type kNativeImageMime and the data as an imgIContainer object.
- When dropping an image we want to have some kind of image/ type,
  but the data is already supposed to be a File or nsIInputStream when it comes from the OS.

This weird split usually works fine, because in our GTK, Windows and macOS code
we first convert imgIContainer to whatever native image format is required when
dragging something and then convert back to a file (or stream) when dropping.

What however doesn't work is when we never actually round-trip through the OS.
In that case we have the imgIContainer that we can't drop in a meaningful way.

(I actually already ran into this issue in bug 1396587 with the clipboard,
but we kind of resolved this by always doing the roundtrip through the OS's clipboard)

Differential Revision: https://phabricator.services.mozilla.com/D162493
  • Loading branch information
evilpie committed Nov 28, 2022
1 parent 05dc271 commit 07f08ea
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 3 deletions.
37 changes: 34 additions & 3 deletions dom/events/DataTransferItem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#include "mozilla/dom/FileSystem.h"
#include "mozilla/dom/FileSystemDirectoryEntry.h"
#include "mozilla/dom/FileSystemFileEntry.h"
#include "imgIContainer.h"
#include "imgITools.h"
#include "nsComponentManagerUtils.h"
#include "nsIClipboard.h"
#include "nsIFile.h"
Expand Down Expand Up @@ -86,6 +88,8 @@ void DataTransferItem::SetData(nsIVariant* aData) {
// These are provided by the system, and have guaranteed properties about
// their kind based on their type.
MOZ_ASSERT(!mType.IsEmpty());
// This type should not be provided by the OS.
MOZ_ASSERT(!mType.EqualsASCII(kNativeImageMime));

mKind = KIND_STRING;
for (uint32_t i = 0; i < ArrayLength(kFileMimeNameMap); ++i) {
Expand Down Expand Up @@ -114,6 +118,12 @@ void DataTransferItem::SetData(nsIVariant* aData) {
nsCOMPtr<nsIFile>(do_QueryInterface(supports))) {
return KIND_FILE;
}

// Firefox internally uses imgIContainer to represent images being
// copied/dragged. These need to be encoded to PNG files.
if (nsCOMPtr<imgIContainer>(do_QueryInterface(supports))) {
return KIND_FILE;
}
}

nsAutoString string;
Expand Down Expand Up @@ -201,7 +211,6 @@ void DataTransferItem::FillInExternalData() {
}
data = do_QueryObject(file);
}

variant->SetAsISupports(data);
} else {
// We have an external piece of string data. Extract it and store it in the
Expand Down Expand Up @@ -308,6 +317,22 @@ already_AddRefed<File> DataTransferItem::GetAsFile(
if (NS_WARN_IF(!mCachedFile)) {
return nullptr;
}
} else if (nsCOMPtr<imgIContainer> img = do_QueryInterface(supports)) {
nsCOMPtr<imgITools> imgTools =
do_CreateInstance("@mozilla.org/image/tools;1");

nsCOMPtr<nsIInputStream> inputStream;
nsresult rv = imgTools->EncodeImage(img, "image/png"_ns, u""_ns,
getter_AddRefs(inputStream));
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}

mCachedFile = CreateFileFromInputStream(
inputStream, "GenericImageNamePNG", u"image/png"_ns);
if (NS_WARN_IF(!mCachedFile)) {
return nullptr;
}
} else {
MOZ_ASSERT(false, "One of the above code paths should be taken");
return nullptr;
Expand Down Expand Up @@ -382,9 +407,15 @@ already_AddRefed<File> DataTransferItem::CreateFileFromInputStream(
key = "GenericFileName";
}

return CreateFileFromInputStream(aStream, key, mType);
}

already_AddRefed<File> DataTransferItem::CreateFileFromInputStream(
nsIInputStream* aStream, const char* aFileNameKey,
const nsAString& aContentType) {
nsAutoString fileName;
nsresult rv = nsContentUtils::GetLocalizedString(
nsContentUtils::eDOM_PROPERTIES, key, fileName);
nsContentUtils::eDOM_PROPERTIES, aFileNameKey, fileName);
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
Expand All @@ -402,7 +433,7 @@ already_AddRefed<File> DataTransferItem::CreateFileFromInputStream(
}

return File::CreateMemoryFileWithLastModifiedNow(global, data, available,
fileName, mType);
fileName, aContentType);
}

void DataTransferItem::GetAsString(FunctionStringCallback* aCallback,
Expand Down
3 changes: 3 additions & 0 deletions dom/events/DataTransferItem.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ class DataTransferItem final : public nsISupports, public nsWrapperCache {
private:
~DataTransferItem() = default;
already_AddRefed<File> CreateFileFromInputStream(nsIInputStream* aStream);
already_AddRefed<File> CreateFileFromInputStream(
nsIInputStream* aStream, const char* aFileNameKey,
const nsAString& aContentType);

already_AddRefed<nsIGlobalObject> GetGlobalFromDataTransfer();

Expand Down
4 changes: 4 additions & 0 deletions dom/events/test/mochitest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,10 @@ skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
skip-if = toolkit == 'android' # not supported
[test_drag_coords.html]
skip-if = toolkit == 'android' # not supported
[test_drag_image_file.html]
skip-if = toolkit == 'android'
support-files =
green.png
[test_error_events.html]
skip-if = toolkit == 'android' #TIMED_OUT
[test_event_handler_cc.html]
Expand Down
46 changes: 46 additions & 0 deletions dom/events/test/test_drag_image_file.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test that dragging an image produces a File</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>

<img id="green-png" src="green.png">

<script>
async function runTest() {
let dt = await synthesizePlainDragAndCancel({
srcElement: document.getElementById('green-png'),
finalY: 20,
}, null);

ok(dt.types.includes("Files"), "types should contains 'Files'");
is(dt.files.length, 1, "files contains one File");

let fileItem = null;
for (let item of dt.items) {
if (item.kind === "file") {
fileItem = item;
break;
}
}

is(fileItem.kind, "file", "Is a file");
is(fileItem.type, "image/png", "Is a PNG file");

let file = fileItem.getAsFile();
is(file.name, "image.png", "Has generic image name")
ok(file.size > 100, "Is not empty");

SimpleTest.finish();
}

SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(runTest);
</script>
</body>
</html>

0 comments on commit 07f08ea

Please sign in to comment.