From a030ec3ce64c859bc679449e9cd7900f48b82a22 Mon Sep 17 00:00:00 2001 From: Neil Deakin Date: Thu, 21 Apr 2016 14:11:14 -0400 Subject: [PATCH] Bug 860857, support custom datatransfer types using a special type, r=smaug,jmathies,mstange --- dom/events/DataTransfer.cpp | 372 ++++++++++++++---- dom/events/DataTransfer.h | 10 + dom/ipc/ContentChild.cpp | 9 +- dom/ipc/TabParent.cpp | 29 +- .../general/test_clipboard_events.html | 13 +- widget/cocoa/nsClipboard.mm | 47 ++- widget/cocoa/nsDragService.h | 1 + widget/cocoa/nsDragService.mm | 32 +- widget/gtk/nsDragService.cpp | 14 +- widget/nsClipboardProxy.cpp | 3 +- widget/nsITransferable.idl | 2 + widget/nsPrimitiveHelpers.cpp | 4 +- widget/windows/nsClipboard.cpp | 27 +- widget/windows/nsClipboard.h | 1 + widget/windows/nsDataObj.cpp | 2 +- 15 files changed, 453 insertions(+), 113 deletions(-) diff --git a/dom/events/DataTransfer.cpp b/dom/events/DataTransfer.cpp index b8e63e73dd4c..55c826071975 100644 --- a/dom/events/DataTransfer.cpp +++ b/dom/events/DataTransfer.cpp @@ -18,6 +18,10 @@ #include "nsIClipboard.h" #include "nsContentUtils.h" #include "nsIContent.h" +#include "nsIBinaryInputStream.h" +#include "nsIBinaryOutputStream.h" +#include "nsIStorageStream.h" +#include "nsStringStream.h" #include "nsCRT.h" #include "nsIScriptObjectPrincipal.h" #include "nsIScriptContext.h" @@ -80,6 +84,12 @@ const char DataTransfer::sEffects[8][9] = { "none", "copy", "move", "copyMove", "link", "copyLink", "linkMove", "all" }; +// Used for custom clipboard types. +enum CustomClipboardTypeId { + eCustomClipboardTypeId_None, + eCustomClipboardTypeId_String +}; + DataTransfer::DataTransfer(nsISupports* aParent, EventMessage aEventMessage, bool aIsExternal, int32_t aClipboardType) : mParent(aParent) @@ -710,6 +720,11 @@ DataTransfer::SetDataAtInternal(const nsAString& aFormat, nsIVariant* aData, return NS_ERROR_DOM_INDEX_SIZE_ERR; } + // 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)) { @@ -984,44 +999,166 @@ DataTransfer::GetTransferable(uint32_t aIndex, nsILoadContext* aLoadContext) } transferable->Init(aLoadContext); + nsCOMPtr storageStream; + nsCOMPtr stream; + bool added = false; - for (uint32_t f = 0; f < count; f++) { - const TransferItem& formatitem = item[f]; - if (!formatitem.mData) { // skip empty items - continue; + bool handlingCustomFormats = true; + uint32_t totalCustomLength = 0; + + const char* knownFormats[] = { kTextMime, kHTMLMime, kNativeHTMLMime, kRTFMime, + kURLMime, kURLDataMime, kURLDescriptionMime, kURLPrivateMime, + kPNGImageMime, kJPEGImageMime, kGIFImageMime, kNativeImageMime, + kFileMime, kFilePromiseMime, kFilePromiseDirectoryMime, + kMozTextInternal, kHTMLContext, kHTMLInfo }; + + /* + * Two passes are made here to iterate over all of the types. First, look for + * any types that are not in the list of known types. For this pass, handlingCustomFormats + * will be true. Data that corresponds to unknown types will be pulled out and + * inserted into a single type (kCustomTypesMime) by writing the data into a stream. + * + * The second pass will iterate over the formats looking for known types. These are + * added as is. The unknown types are all then inserted as a single type (kCustomTypesMime) + * in the same position of the first custom type. This model is used to maintain the + * format order as best as possible. + * + * The format of the kCustomTypesMime type is one or more of the following stored sequentially: + * <32-bit> type (only none or string is supported) + * <32-bit> length of format + * format + * <32-bit> length of data + * data + * A type of eCustomClipboardTypeId_None ends the list, without any following data. + */ + do { + for (uint32_t f = 0; f < count; f++) { + const TransferItem& formatitem = item[f]; + if (!formatitem.mData) { // skip empty items + continue; + } + + // If the data is of one of the well-known formats, use it directly. + bool isCustomFormat = true; + for (uint32_t f = 0; f < ArrayLength(knownFormats); f++) { + if (formatitem.mFormat.EqualsASCII(knownFormats[f])) { + isCustomFormat = false; + break; + } + } + + uint32_t lengthInBytes; + nsCOMPtr convertedData; + + if (handlingCustomFormats) { + if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData), &lengthInBytes)) { + continue; + } + + // When handling custom types, add the data to the stream if this is a + // custom type. + if (isCustomFormat) { + // If it isn't a string, just ignore it. The dataTransfer is cached in the + // drag sesion during drag-and-drop, so non-strings will be available when + // dragging locally. + nsCOMPtr str(do_QueryInterface(convertedData)); + if (str) { + nsAutoString data; + str->GetData(data); + + if (!stream) { + // Create a storage stream to write to. + NS_NewStorageStream(1024, UINT32_MAX, getter_AddRefs(storageStream)); + + nsCOMPtr outputStream; + storageStream->GetOutputStream(0, getter_AddRefs(outputStream)); + + stream = do_CreateInstance("@mozilla.org/binaryoutputstream;1"); + stream->SetOutputStream(outputStream); + } + + int32_t formatLength = formatitem.mFormat.Length() * sizeof(nsString::char_type); + + stream->Write32(eCustomClipboardTypeId_String); + stream->Write32(formatLength); + stream->WriteBytes((const char *)formatitem.mFormat.get(), formatLength); + stream->Write32(lengthInBytes); + stream->WriteBytes((const char *)data.get(), lengthInBytes); + + // The total size of the stream is the format length, the data length, + // two integers to hold the lengths and one integer for the string flag. + totalCustomLength += formatLength + lengthInBytes + (sizeof(uint32_t) * 3); + } + } + } else if (isCustomFormat && stream) { + // This is the second pass of the loop (handlingCustomFormats is false). + // When encountering the first custom format, append all of the stream + // at this position. + + // Write out a terminator. + totalCustomLength += sizeof(uint32_t); + stream->Write32(eCustomClipboardTypeId_None); + + nsCOMPtr inputStream; + storageStream->NewInputStream(0, getter_AddRefs(inputStream)); + + RefPtr stringBuffer = nsStringBuffer::Alloc(totalCustomLength + 1); + + // Read the data from the string and add a null-terminator as ToString needs it. + uint32_t amountRead; + inputStream->Read(static_cast(stringBuffer->Data()), totalCustomLength, &amountRead); + static_cast(stringBuffer->Data())[amountRead] = 0; + + nsCString str; + stringBuffer->ToString(totalCustomLength, str); + nsCOMPtr strSupports(do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID)); + strSupports->SetData(str); + + nsresult rv = transferable->SetTransferData(kCustomTypesMime, strSupports, totalCustomLength); + if (NS_FAILED(rv)) { + return nullptr; + } + + added = true; + + // Clear the stream so it doesn't get used again. + stream = nullptr; + } else { + // This is the second pass of the loop and a known type is encountered. + // Add it as is. + if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData), &lengthInBytes)) { + continue; + } + + // The underlying drag code uses text/unicode, so use that instead of text/plain + const char* format; + NS_ConvertUTF16toUTF8 utf8format(formatitem.mFormat); + if (utf8format.EqualsLiteral(kTextMime)) { + format = kUnicodeMime; + } else { + format = utf8format.get(); + } + + // If a converter is set for a format, set the converter for the + // transferable and don't add the item + nsCOMPtr converter = do_QueryInterface(convertedData); + if (converter) { + transferable->AddDataFlavor(format); + transferable->SetConverter(converter); + continue; + } + + nsresult rv = transferable->SetTransferData(format, convertedData, lengthInBytes); + if (NS_FAILED(rv)) { + return nullptr; + } + + added = true; + } } - uint32_t length; - nsCOMPtr convertedData; - if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData), &length)) { - continue; - } - - // the underlying drag code uses text/unicode, so use that instead of text/plain - const char* format; - NS_ConvertUTF16toUTF8 utf8format(formatitem.mFormat); - if (utf8format.EqualsLiteral("text/plain")) { - format = kUnicodeMime; - } else { - format = utf8format.get(); - } - - // if a converter is set for a format, set the converter for the - // transferable and don't add the item - nsCOMPtr converter = do_QueryInterface(convertedData); - if (converter) { - transferable->AddDataFlavor(format); - transferable->SetConverter(converter); - continue; - } - - nsresult rv = transferable->SetTransferData(format, convertedData, length); - if (NS_FAILED(rv)) { - return nullptr; - } - - added = true; - } + handlingCustomFormats = !handlingCustomFormats; + } while (!handlingCustomFormats); // only return the transferable if data was successfully added to it if (added) { @@ -1152,6 +1289,19 @@ DataTransfer::SetDataWithPrincipal(const nsAString& aFormat, return NS_OK; } +void +DataTransfer::SetDataWithPrincipalFromOtherProcess(const nsAString& aFormat, + nsIVariant* aData, + uint32_t aIndex, + nsIPrincipal* aPrincipal) +{ + if (aFormat.EqualsLiteral(kCustomTypesMime)) { + FillInExternalCustomTypes(aData, aIndex, aPrincipal); + } else { + SetDataWithPrincipal(aFormat, aData, aIndex, aPrincipal); + } +} + void DataTransfer::GetRealFormat(const nsAString& aInFormat, nsAString& aOutFormat) { @@ -1200,11 +1350,19 @@ DataTransfer::CacheExternalDragFormats() // there isn't a way to get a list of the formats that might be available on // all platforms, so just check for the types that can actually be imported // XXXndeakin there are some other formats but those are platform specific. - const char* formats[] = { kFileMime, kHTMLMime, kRTFMime, kURLMime, kURLDataMime, kUnicodeMime }; + const char* formats[] = { kFileMime, kHTMLMime, kRTFMime, + kURLMime, kURLDataMime, kUnicodeMime }; uint32_t count; dragSession->GetNumDropItems(&count); for (uint32_t c = 0; c < count; c++) { + // First, check for the special format that holds custom types. + bool supported; + dragSession->IsDataFlavorSupported(kCustomTypesMime, &supported); + if (supported) { + FillInExternalCustomTypes(c, sysPrincipal); + } + for (uint32_t f = 0; f < ArrayLength(formats); f++) { // IsDataFlavorSupported doesn't take an index as an argument and just // checks if any of the items support a particular flavor, even though @@ -1241,8 +1399,10 @@ DataTransfer::CacheExternalClipboardFormats() ssm->GetSystemPrincipal(getter_AddRefs(sysPrincipal)); // there isn't a way to get a list of the formats that might be available on - // all platforms, so just check for the types that can actually be imported - const char* formats[] = { kFileMime, kHTMLMime, kRTFMime, kURLMime, kURLDataMime, kUnicodeMime }; + // all platforms, so just check for the types that can actually be imported. + // Note that the loop below assumes that kCustomTypesMime will be first. + const char* formats[] = { kCustomTypesMime, kFileMime, kHTMLMime, kRTFMime, + kURLMime, kURLDataMime, kUnicodeMime }; for (uint32_t f = 0; f < mozilla::ArrayLength(formats); ++f) { // check each format one at a time @@ -1251,7 +1411,11 @@ DataTransfer::CacheExternalClipboardFormats() // if the format is supported, add an item to the array with null as // the data. When retrieved, GetRealData will read the data. if (supported) { - CacheExternalData(formats[f], 0, sysPrincipal); + if (f == 0) { + FillInExternalCustomTypes(0, sysPrincipal); + } else { + CacheExternalData(formats[f], 0, sysPrincipal); + } } } } @@ -1269,17 +1433,17 @@ DataTransfer::FillInExternalData(TransferItem& aItem, uint32_t aIndex) NS_ASSERTION(mEventMessage != eCut && mEventMessage != eCopy, "clipboard event with empty data"); - NS_ConvertUTF16toUTF8 utf8format(aItem.mFormat); - const char* format = utf8format.get(); - if (strcmp(format, "text/plain") == 0) - format = kUnicodeMime; - else if (strcmp(format, "text/uri-list") == 0) - format = kURLDataMime; + NS_ConvertUTF16toUTF8 utf8format(aItem.mFormat); + const char* format = utf8format.get(); + if (strcmp(format, "text/plain") == 0) + format = kUnicodeMime; + else if (strcmp(format, "text/uri-list") == 0) + format = kURLDataMime; - nsCOMPtr trans = - do_CreateInstance("@mozilla.org/widget/transferable;1"); - if (!trans) - return; + nsCOMPtr trans = + do_CreateInstance("@mozilla.org/widget/transferable;1"); + if (!trans) + return; trans->Init(nullptr); trans->AddDataFlavor(format); @@ -1309,33 +1473,33 @@ DataTransfer::FillInExternalData(TransferItem& aItem, uint32_t aIndex) dragSession->GetData(trans, aIndex); } - uint32_t length = 0; - nsCOMPtr data; - trans->GetTransferData(format, getter_AddRefs(data), &length); - if (!data) - return; + uint32_t length = 0; + nsCOMPtr data; + trans->GetTransferData(format, getter_AddRefs(data), &length); + if (!data) + return; - RefPtr variant = new nsVariantCC(); + RefPtr variant = new nsVariantCC(); - nsCOMPtr supportsstr = do_QueryInterface(data); - if (supportsstr) { - nsAutoString str; - supportsstr->GetData(str); - variant->SetAsAString(str); - } - else { - nsCOMPtr supportscstr = do_QueryInterface(data); - if (supportscstr) { - nsAutoCString str; - supportscstr->GetData(str); - variant->SetAsACString(str); - } else { - variant->SetAsISupports(data); - } - } - - aItem.mData = variant; + nsCOMPtr supportsstr = do_QueryInterface(data); + if (supportsstr) { + nsAutoString str; + supportsstr->GetData(str); + variant->SetAsAString(str); } + else { + nsCOMPtr supportscstr = do_QueryInterface(data); + if (supportscstr) { + nsAutoCString str; + supportscstr->GetData(str); + variant->SetAsACString(str); + } else { + variant->SetAsISupports(data); + } + } + + aItem.mData = variant; +} void DataTransfer::FillAllExternalData() @@ -1352,5 +1516,67 @@ DataTransfer::FillAllExternalData() } } +void +DataTransfer::FillInExternalCustomTypes(uint32_t aIndex, nsIPrincipal* aPrincipal) +{ + TransferItem item; + item.mFormat.AssignLiteral(kCustomTypesMime); + + FillInExternalData(item, aIndex); + if (!item.mData) { + return; + } + + FillInExternalCustomTypes(item.mData, aIndex, aPrincipal); +} + +void +DataTransfer::FillInExternalCustomTypes(nsIVariant* aData, uint32_t aIndex, nsIPrincipal* aPrincipal) +{ + char* chrs; + uint32_t len = 0; + nsresult rv = aData->GetAsStringWithSize(&len, &chrs); + if (NS_FAILED(rv)) { + return; + } + + nsAutoCString str; + str.Adopt(chrs, len); + + nsCOMPtr stringStream; + NS_NewCStringInputStream(getter_AddRefs(stringStream), str); + + nsCOMPtr stream = do_CreateInstance("@mozilla.org/binaryinputstream;1"); + stream->SetInputStream(stringStream); + if (!stream) { + return; + } + + uint32_t type; + do { + stream->Read32(&type); + if (type == eCustomClipboardTypeId_String) { + uint32_t formatLength; + stream->Read32(&formatLength); + char* formatBytes; + stream->ReadBytes(formatLength, &formatBytes); + nsAutoString format; + format.Adopt(reinterpret_cast(formatBytes), formatLength / sizeof(char16_t)); + + uint32_t dataLength; + stream->Read32(&dataLength); + char* dataBytes; + stream->ReadBytes(dataLength, &dataBytes); + nsAutoString data; + data.Adopt(reinterpret_cast(dataBytes), dataLength / sizeof(char16_t)); + + RefPtr variant = new nsVariantCC(); + variant->SetAsAString(data); + + SetDataWithPrincipal(format, variant, aIndex, aPrincipal); + } + } while (type != eCustomClipboardTypeId_None); +} + } // namespace dom } // namespace mozilla diff --git a/dom/events/DataTransfer.h b/dom/events/DataTransfer.h index f65a78d0c780..dee3d0de17bf 100644 --- a/dom/events/DataTransfer.h +++ b/dom/events/DataTransfer.h @@ -218,6 +218,13 @@ public: uint32_t aIndex, nsIPrincipal* aPrincipal); + // Variation of SetDataWithPrincipal with handles extracting + // kCustomTypesMime data into separate types. + void SetDataWithPrincipalFromOtherProcess(const nsAString& aFormat, + nsIVariant* aData, + uint32_t aIndex, + nsIPrincipal* aPrincipal); + // returns a weak reference to the drag image Element* GetDragImage(int32_t* aX, int32_t* aY) { @@ -261,6 +268,9 @@ protected: friend class ContentParent; void FillAllExternalData(); + void FillInExternalCustomTypes(uint32_t aIndex, nsIPrincipal* aPrincipal); + void FillInExternalCustomTypes(nsIVariant* aData, uint32_t aIndex, nsIPrincipal* aPrincipal); + void MozClearDataAtHelper(const nsAString& aFormat, uint32_t aIndex, mozilla::ErrorResult& aRv); diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index cbd6721551b2..d67e34b6112e 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -3222,6 +3222,9 @@ ContentChild::RecvInvokeDragSession(nsTArray&& aTransfers, if (item.data().type() == IPCDataTransferData::TnsString) { const nsString& data = item.data().get_nsString(); variant->SetAsAString(data); + } else if (item.data().type() == IPCDataTransferData::TnsCString) { + const nsCString& data = item.data().get_nsCString(); + variant->SetAsACString(data); } else if (item.data().type() == IPCDataTransferData::TPBlobChild) { BlobChild* blob = static_cast(item.data().get_PBlobChild()); RefPtr blobImpl = blob->GetBlobImpl(); @@ -3229,9 +3232,9 @@ ContentChild::RecvInvokeDragSession(nsTArray&& aTransfers, } else { continue; } - dataTransfer->SetDataWithPrincipal(NS_ConvertUTF8toUTF16(item.flavor()), - variant, i, - nsContentUtils::GetSystemPrincipal()); + dataTransfer->SetDataWithPrincipalFromOtherProcess( + NS_ConvertUTF8toUTF16(item.flavor()), variant, i, + nsContentUtils::GetSystemPrincipal()); } } session->SetDataTransfer(dataTransfer); diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp index 956c0b9cf704..5ffd402e5faa 100644 --- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -3142,24 +3142,27 @@ TabParent::AddInitialDnDDataTo(DataTransfer* aDataTransfer) auto* parent = static_cast(item.data().get_PBlobParent()); RefPtr impl = parent->GetBlobImpl(); variant->SetAsISupports(impl); - } else if (item.data().type() == IPCDataTransferData::TnsCString && - nsContentUtils::IsFlavorImage(item.flavor())) { - // An image! Get the imgIContainer for it and set it in the variant. - nsCOMPtr imageContainer; - nsresult rv = - nsContentUtils::DataTransferItemToImage(item, - getter_AddRefs(imageContainer)); - if (NS_FAILED(rv)) { - continue; + } else if (item.data().type() == IPCDataTransferData::TnsCString) { + if (nsContentUtils::IsFlavorImage(item.flavor())) { + // An image! Get the imgIContainer for it and set it in the variant. + nsCOMPtr imageContainer; + nsresult rv = + nsContentUtils::DataTransferItemToImage(item, + getter_AddRefs(imageContainer)); + if (NS_FAILED(rv)) { + continue; + } + variant->SetAsISupports(imageContainer); + } else { + variant->SetAsACString(item.data().get_nsCString()); } - variant->SetAsISupports(imageContainer); } // Using system principal here, since once the data is on parent process // side, it can be handled as being from browser chrome or OS. - aDataTransfer->SetDataWithPrincipal(NS_ConvertUTF8toUTF16(item.flavor()), - variant, i, - nsContentUtils::GetSystemPrincipal()); + aDataTransfer->SetDataWithPrincipalFromOtherProcess(NS_ConvertUTF8toUTF16(item.flavor()), + variant, i, + nsContentUtils::GetSystemPrincipal()); } } mInitialDataTransferItems.Clear(); diff --git a/dom/tests/mochitest/general/test_clipboard_events.html b/dom/tests/mochitest/general/test_clipboard_events.html index 8bbe4781f315..f0a24fa5aa7e 100644 --- a/dom/tests/mochitest/general/test_clipboard_events.html +++ b/dom/tests/mochitest/general/test_clipboard_events.html @@ -453,7 +453,7 @@ function test_input_copypaste_dataTransfer_multiple() { ok(exh, "exception occured mozClearDataAt 1"); cd.setData("text/x-moz-url", "http://www.mozilla.org"); - cd.mozSetDataAt("text/x-custom", "Custom Text", 0); + cd.mozSetDataAt("text/x-custom", "Custom Text with \u0000 null", 0); is(cd.mozItemCount, 1, "mozItemCount after set multiple types"); return false; }; @@ -479,9 +479,16 @@ function test_input_copypaste_dataTransfer_multiple() { // disabling the following test. Enable this once bug #840101 is fixed. if (navigator.appVersion.indexOf("Android") == -1) { 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 { + is(cd.getData("text/x-custom"), "", "paste text/custom multiple types"); } - // this is empty because only the built-in types are supported at the moment - is(cd.getData("text/x-custom"), "", "paste text/custom multiple types"); + + is(cd.getData("application/x-moz-custom-clipdata"), "", "application/x-moz-custom-clipdata is not present"); + + exh = false; + try { cd.setData("application/x-moz-custom-clipdata", "Some Data"); } catch (ex) { exh = true; } + ok(exh, "exception occured setData with application/x-moz-custom-clipdata"); exh = false; try { cd.setData("text/plain", "Text on Paste"); } catch (ex) { exh = true; } diff --git a/widget/cocoa/nsClipboard.mm b/widget/cocoa/nsClipboard.mm index 870638ac7e1a..1c1aa71a4ad1 100644 --- a/widget/cocoa/nsClipboard.mm +++ b/widget/cocoa/nsClipboard.mm @@ -186,6 +186,31 @@ nsClipboard::TransferableFromPasteboard(nsITransferable *aTransferable, NSPasteb free(clipboardDataPtr); break; } + else if (flavorStr.EqualsLiteral(kCustomTypesMime)) { + NSString* type = [cocoaPasteboard availableTypeFromArray:[NSArray arrayWithObject:kCustomTypesPboardType]]; + if (!type) { + continue; + } + + NSData* pasteboardData = GetDataFromPasteboard(cocoaPasteboard, type); + if (!pasteboardData) { + continue; + } + + unsigned int dataLength = [pasteboardData length]; + void* clipboardDataPtr = malloc(dataLength); + if (!clipboardDataPtr) { + return NS_ERROR_OUT_OF_MEMORY; + } + [pasteboardData getBytes:clipboardDataPtr]; + + nsCOMPtr genericDataWrapper; + nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtr, dataLength, + getter_AddRefs(genericDataWrapper)); + + aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLength); + free(clipboardDataPtr); + } else if (flavorStr.EqualsLiteral(kJPEGImageMime) || flavorStr.EqualsLiteral(kJPGImageMime) || flavorStr.EqualsLiteral(kPNGImageMime) || @@ -330,7 +355,7 @@ nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength, return NS_OK; // first see if we have data for this in our cached transferable - if (mTransferable) { + if (mTransferable) { nsCOMPtr transferableFlavorList; nsresult rv = mTransferable->FlavorsTransferableCanImport(getter_AddRefs(transferableFlavorList)); if (NS_SUCCEEDED(rv)) { @@ -367,6 +392,12 @@ nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength, *outResult = true; break; } + } else if (!strcmp(aFlavorList[i], kCustomTypesMime)) { + NSString* availableType = [generalPBoard availableTypeFromArray:[NSArray arrayWithObject:kCustomTypesPboardType]]; + if (availableType) { + *outResult = true; + break; + } } else if (!strcmp(aFlavorList[i], kJPEGImageMime) || !strcmp(aFlavorList[i], kJPGImageMime) || !strcmp(aFlavorList[i], kPNGImageMime) || @@ -447,6 +478,20 @@ nsClipboard::PasteboardDictFromTransferable(nsITransferable* aTransferable) free(data); } + else if (flavorStr.EqualsLiteral(kCustomTypesMime)) { + void* data = nullptr; + uint32_t dataSize = 0; + nsCOMPtr genericDataWrapper; + rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(genericDataWrapper), &dataSize); + nsPrimitiveHelpers::CreateDataFromPrimitive(flavorStr, genericDataWrapper, &data, dataSize); + + if (data) { + NSData* nativeData = [NSData dataWithBytes:data length:dataSize]; + + [pasteboardOutputDict setObject:nativeData forKey:kCustomTypesPboardType]; + free(data); + } + } else if (flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kJPEGImageMime) || flavorStr.EqualsLiteral(kJPGImageMime) || flavorStr.EqualsLiteral(kGIFImageMime) || flavorStr.EqualsLiteral(kNativeImageMime)) { diff --git a/widget/cocoa/nsDragService.h b/widget/cocoa/nsDragService.h index 86fab8229d9c..dfad5ee41d01 100644 --- a/widget/cocoa/nsDragService.h +++ b/widget/cocoa/nsDragService.h @@ -14,6 +14,7 @@ extern NSString* const kWildcardPboardType; extern NSString* const kCorePboardType_url; extern NSString* const kCorePboardType_urld; extern NSString* const kCorePboardType_urln; +extern NSString* const kCustomTypesPboardType; class nsDragService : public nsBaseDragService { diff --git a/widget/cocoa/nsDragService.mm b/widget/cocoa/nsDragService.mm index 121fb131c8f6..664869326514 100644 --- a/widget/cocoa/nsDragService.mm +++ b/widget/cocoa/nsDragService.mm @@ -49,6 +49,7 @@ NSString* const kCorePboardType_url = @"CorePasteboardFlavorType 0x75726C20"; / NSString* const kCorePboardType_urld = @"CorePasteboardFlavorType 0x75726C64"; // 'urld' desc NSString* const kCorePboardType_urln = @"CorePasteboardFlavorType 0x75726C6E"; // 'urln' title NSString* const kUTTypeURLName = @"public.url-name"; +NSString* const kCustomTypesPboardType = @"org.mozilla.custom-clipdata"; nsDragService::nsDragService() { @@ -110,7 +111,8 @@ static nsresult SetUpDragClipboard(nsISupportsArray* aTransferableArray) [dragPBoard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue)) forType:currentKey]; } - else if (currentKey == NSTIFFPboardType) { + else if (currentKey == NSTIFFPboardType || + currentKey == kCustomTypesPboardType) { [dragPBoard setData:currentValue forType:currentKey]; } else if (currentKey == NSFilesPromisePboardType || @@ -474,6 +476,31 @@ nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex) break; } + else if (flavorStr.EqualsLiteral(kCustomTypesMime)) { + NSString* availableType = [item availableTypeFromArray:[NSArray arrayWithObject:kCustomTypesPboardType]]; + if (!availableType || !IsValidType(availableType, false)) { + continue; + } + NSData *pasteboardData = [item dataForType:availableType]; + if (!pasteboardData) { + continue; + } + + unsigned int dataLength = [pasteboardData length]; + void* clipboardDataPtr = malloc(dataLength); + if (!clipboardDataPtr) { + return NS_ERROR_OUT_OF_MEMORY; + } + [pasteboardData getBytes:clipboardDataPtr]; + + nsCOMPtr genericDataWrapper; + nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtr, dataLength, + getter_AddRefs(genericDataWrapper)); + + aTransferable->SetTransferData(flavorStr, genericDataWrapper, sizeof(nsIInputStream*)); + free(clipboardDataPtr); + break; + } NSString* pString = nil; if (flavorStr.EqualsLiteral(kUnicodeMime)) { @@ -610,7 +637,10 @@ nsDragService::IsDataFlavorSupported(const char *aDataFlavor, bool *_retval) type = (const NSString*)kUTTypeURLName; } else if (dataFlavor.EqualsLiteral(kRTFMime)) { type = (const NSString*)kUTTypeRTF; + } else if (dataFlavor.EqualsLiteral(kCustomTypesMime)) { + type = (const NSString*)kCustomTypesPboardType; } + NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObjects:(id)type, nil]]; if (availableType && IsValidType(availableType, allowFileURL)) { *_retval = true; diff --git a/widget/gtk/nsDragService.cpp b/widget/gtk/nsDragService.cpp index 43b52d2dda68..d95d45c22af1 100644 --- a/widget/gtk/nsDragService.cpp +++ b/widget/gtk/nsDragService.cpp @@ -970,12 +970,14 @@ nsDragService::GetData(nsITransferable * aTransferable, } // else we try one last ditch effort to find our data if (dataFound) { - // the DOM only wants LF, so convert from MacOS line endings - // to DOM line endings. - nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks( - flavorStr, - &mTargetDragData, - reinterpret_cast(&mTargetDragDataLen)); + if (strcmp(flavorStr, kCustomTypesMime) != 0) { + // the DOM only wants LF, so convert from MacOS line endings + // to DOM line endings. + nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks( + flavorStr, + &mTargetDragData, + reinterpret_cast(&mTargetDragDataLen)); + } // put it into the transferable. nsCOMPtr genericDataWrapper; diff --git a/widget/nsClipboardProxy.cpp b/widget/nsClipboardProxy.cpp index 0fdd188243f0..fedc9528ea81 100644 --- a/widget/nsClipboardProxy.cpp +++ b/widget/nsClipboardProxy.cpp @@ -93,7 +93,8 @@ nsClipboardProxy::GetData(nsITransferable *aTransferable, int32_t aWhichClipboar rv = aTransferable->SetTransferData(flavor.get(), stream, sizeof(nsISupports*)); NS_ENSURE_SUCCESS(rv, rv); } else if (flavor.EqualsLiteral(kNativeHTMLMime) || - flavor.EqualsLiteral(kRTFMime)) { + flavor.EqualsLiteral(kRTFMime) || + flavor.EqualsLiteral(kCustomTypesMime)) { nsCOMPtr dataWrapper = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); diff --git a/widget/nsITransferable.idl b/widget/nsITransferable.idl index ab35d69f9738..b7363fd45dbe 100644 --- a/widget/nsITransferable.idl +++ b/widget/nsITransferable.idl @@ -49,6 +49,8 @@ interface nsIDOMNode; // a synthetic flavor, put into the transferable once we know the destination directory of a file drag #define kFilePromiseDirectoryMime "application/x-moz-file-promise-dir" +#define kCustomTypesMime "application/x-moz-custom-clipdata" + %} diff --git a/widget/nsPrimitiveHelpers.cpp b/widget/nsPrimitiveHelpers.cpp index 6767504c227c..242db3d5095c 100644 --- a/widget/nsPrimitiveHelpers.cpp +++ b/widget/nsPrimitiveHelpers.cpp @@ -48,7 +48,7 @@ nsPrimitiveHelpers :: CreatePrimitiveForData ( const char* aFlavor, const void* return; if ( strcmp(aFlavor,kTextMime) == 0 || strcmp(aFlavor,kNativeHTMLMime) == 0 || - strcmp(aFlavor,kRTFMime) == 0) { + strcmp(aFlavor,kRTFMime) == 0 || strcmp(aFlavor,kCustomTypesMime) == 0) { nsCOMPtr primitive = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID); if ( primitive ) { @@ -133,7 +133,7 @@ nsPrimitiveHelpers :: CreateDataFromPrimitive ( const char* aFlavor, nsISupports *aDataBuff = nullptr; - if ( strcmp(aFlavor,kTextMime) == 0 ) { + if ( strcmp(aFlavor,kTextMime) == 0 || strcmp(aFlavor,kCustomTypesMime) == 0) { nsCOMPtr plainText ( do_QueryInterface(aPrimitive) ); if ( plainText ) { nsAutoCString data; diff --git a/widget/windows/nsClipboard.cpp b/widget/windows/nsClipboard.cpp index 56a652cbdb44..05b35571fce3 100644 --- a/widget/windows/nsClipboard.cpp +++ b/widget/windows/nsClipboard.cpp @@ -42,6 +42,7 @@ PRLogModuleInfo* gWin32ClipboardLog = nullptr; // oddly, this isn't in the MSVC headers anywhere. UINT nsClipboard::CF_HTML = ::RegisterClipboardFormatW(L"HTML Format"); +UINT nsClipboard::CF_CUSTOMTYPES = ::RegisterClipboardFormatW(L"application/x-moz-custom-clipdata"); //------------------------------------------------------------------------- @@ -108,6 +109,8 @@ UINT nsClipboard::GetFormat(const char* aMimeStr, bool aMapHTMLMime) else if (strcmp(aMimeStr, kNativeHTMLMime) == 0 || aMapHTMLMime && strcmp(aMimeStr, kHTMLMime) == 0) format = CF_HTML; + else if (strcmp(aMimeStr, kCustomTypesMime) == 0) + format = CF_CUSTOMTYPES; else format = ::RegisterClipboardFormatW(NS_ConvertASCIItoUTF16(aMimeStr).get()); @@ -534,6 +537,9 @@ nsresult nsClipboard::GetNativeDataOffClipboard(IDataObject * aDataObject, UINT // do that in FindPlatformHTML(). For now, return the allocLen. This // case is mostly to ensure we don't try to call strlen on the buffer. *aLen = allocLen; + } else if (fe.cfFormat == CF_CUSTOMTYPES) { + // Binary data + *aLen = allocLen; } else if (fe.cfFormat == preferredDropEffect) { // As per the MSDN doc entitled: "Shell Clipboard Formats" // CFSTR_PREFERREDDROPEFFECT should return a DWORD @@ -682,16 +688,19 @@ nsresult nsClipboard::GetDataFromDataObject(IDataObject * aDataObject, NS_IF_RELEASE(imageStream); } else { - // we probably have some form of text. The DOM only wants LF, so convert from Win32 line - // endings to DOM line endings. - int32_t signedLen = static_cast(dataLen); - nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks ( flavorStr, &data, &signedLen ); - dataLen = signedLen; + // Treat custom types as a string of bytes. + if (strcmp(flavorStr, kCustomTypesMime) != 0) { + // we probably have some form of text. The DOM only wants LF, so convert from Win32 line + // endings to DOM line endings. + int32_t signedLen = static_cast(dataLen); + nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks ( flavorStr, &data, &signedLen ); + dataLen = signedLen; - if (strcmp(flavorStr, kRTFMime) == 0) { - // RTF on Windows is known to sometimes deliver an extra null byte. - if (dataLen > 0 && static_cast(data)[dataLen - 1] == '\0') - dataLen--; + if (strcmp(flavorStr, kRTFMime) == 0) { + // RTF on Windows is known to sometimes deliver an extra null byte. + if (dataLen > 0 && static_cast(data)[dataLen - 1] == '\0') + dataLen--; + } } nsPrimitiveHelpers::CreatePrimitiveForData ( flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper) ); diff --git a/widget/windows/nsClipboard.h b/widget/windows/nsClipboard.h index 87a7ae9d4bf3..18308d6dbba9 100644 --- a/widget/windows/nsClipboard.h +++ b/widget/windows/nsClipboard.h @@ -60,6 +60,7 @@ public: static UINT GetFormat(const char* aMimeStr, bool aMapHTMLMime = true); static UINT CF_HTML; + static UINT CF_CUSTOMTYPES; protected: NS_IMETHOD SetNativeClipboardData ( int32_t aWhichClipboard ) override; diff --git a/widget/windows/nsDataObj.cpp b/widget/windows/nsDataObj.cpp index dfdc850d6148..8f9027b27497 100644 --- a/widget/windows/nsDataObj.cpp +++ b/widget/windows/nsDataObj.cpp @@ -1328,7 +1328,7 @@ HRESULT nsDataObj::GetText(const nsACString & aDataFlavor, FORMATETC& aFE, STGME return S_OK; } } - else { + else if ( aFE.cfFormat != nsClipboard::CF_CUSTOMTYPES ) { // we assume that any data that isn't caught above is unicode. This may // be an erroneous assumption, but is true so far. allocLen += sizeof(char16_t);