Bug 860857, support custom datatransfer types using a special type, r=smaug,jmathies,mstange
authorNeil Deakin <neil@mozilla.com>
Thu, 21 Apr 2016 14:11:14 -0400
changeset 355106 4cbc83f4c4b1d7cd3bf9758247dce1f54318fb62
parent 355105 eb1d405b1239f342187201225a6cac0ff21440c2
child 355107 2747cac2de2fcfd3233930a10e4fa560b3376b60
push id16212
push userbmo:mh+mozilla@glandium.org
push dateFri, 22 Apr 2016 00:06:15 +0000
reviewerssmaug, jmathies, mstange
bugs860857
milestone48.0a1
Bug 860857, support custom datatransfer types using a special type, r=smaug,jmathies,mstange
dom/events/DataTransfer.cpp
dom/events/DataTransfer.h
dom/ipc/ContentChild.cpp
dom/ipc/TabParent.cpp
dom/tests/mochitest/general/test_clipboard_events.html
widget/cocoa/nsClipboard.mm
widget/cocoa/nsDragService.h
widget/cocoa/nsDragService.mm
widget/gtk/nsDragService.cpp
widget/nsClipboardProxy.cpp
widget/nsITransferable.idl
widget/nsPrimitiveHelpers.cpp
widget/windows/nsClipboard.cpp
widget/windows/nsClipboard.h
widget/windows/nsDataObj.cpp
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -13,16 +13,20 @@
 #include "nsISupportsPrimitives.h"
 #include "nsIScriptSecurityManager.h"
 #include "mozilla/dom/DOMStringList.h"
 #include "nsError.h"
 #include "nsIDragService.h"
 #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"
 #include "nsIDocument.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsVariant.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/DataTransferBinding.h"
@@ -75,16 +79,22 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMDataTransfer)
 NS_INTERFACE_MAP_END
 
 // the size of the array
 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)
   , mDropEffect(nsIDragService::DRAGDROP_ACTION_NONE)
   , mEffectAllowed(nsIDragService::DRAGDROP_ACTION_UNINITIALIZED)
   , mEventMessage(aEventMessage)
   , mCursorState(false)
   , mReadOnly(true)
@@ -705,16 +715,21 @@ DataTransfer::SetDataAtInternal(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;
   }
 
+  // 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")) {
       return NS_ERROR_DOM_SECURITY_ERR;
     }
 
@@ -979,54 +994,176 @@ DataTransfer::GetTransferable(uint32_t a
 
   nsCOMPtr<nsITransferable> transferable =
     do_CreateInstance("@mozilla.org/widget/transferable;1");
   if (!transferable) {
     return nullptr;
   }
   transferable->Init(aLoadContext);
 
+  nsCOMPtr<nsIStorageStream> storageStream;
+  nsCOMPtr<nsIBinaryOutputStream> 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
+   *   <wide string> format
+   *   <32-bit> length of data
+   *   <wide string> 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<nsISupports> 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<nsISupportsString> 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<nsIOutputStream> 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);
 
-    uint32_t length;
-    nsCOMPtr<nsISupports> convertedData;
-    if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData), &length)) {
-      continue;
+            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<nsIInputStream> inputStream;
+        storageStream->NewInputStream(0, getter_AddRefs(inputStream));
+
+        RefPtr<nsStringBuffer> 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<char*>(stringBuffer->Data()), totalCustomLength, &amountRead);
+        static_cast<char*>(stringBuffer->Data())[amountRead] = 0;
+
+        nsCString str;
+        stringBuffer->ToString(totalCustomLength, str);
+        nsCOMPtr<nsISupportsCString> 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<nsIFormatConverter> 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;
+      }
     }
 
-    // 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<nsIFormatConverter> 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) {
     return transferable.forget();
   }
 
   return nullptr;
 }
@@ -1148,16 +1285,29 @@ DataTransfer::SetDataWithPrincipal(const
   formatitem->mFormat = format;
   formatitem->mPrincipal = aPrincipal;
   formatitem->mData = aData;
 
   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)
 {
   // treat text/unicode as equivalent to text/plain
   nsAutoString lowercaseFormat;
   nsContentUtils::ASCIIToLower(aInFormat, lowercaseFormat);
   if (lowercaseFormat.EqualsLiteral("text") || lowercaseFormat.EqualsLiteral("text/unicode"))
     aOutFormat.AssignLiteral("text/plain");
   else if (lowercaseFormat.EqualsLiteral("url"))
@@ -1195,21 +1345,29 @@ DataTransfer::CacheExternalDragFormats()
   // make sure that the system principal is used for external drags
   nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
   nsCOMPtr<nsIPrincipal> sysPrincipal;
   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
   // 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
       // the GetData method does take an index. Here, we just assume that
       // every item being dragged has the same set of flavors.
       bool supported;
       dragSession->IsDataFlavorSupported(formats[f], &supported);
       // if the format is supported, add an item to the array with null as
@@ -1236,27 +1394,33 @@ DataTransfer::CacheExternalClipboardForm
     return;
   }
 
   nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
   nsCOMPtr<nsIPrincipal> sysPrincipal;
   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
     bool supported;
     clipboard->HasDataMatchingFlavors(&(formats[f]), 1, mClipboardType, &supported);
     // 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);
+      }
     }
   }
 }
 
 void
 DataTransfer::FillInExternalData(TransferItem& aItem, uint32_t aIndex)
 {
   NS_PRECONDITION(mIsExternal, "Not an external data transfer");
@@ -1264,27 +1428,27 @@ DataTransfer::FillInExternalData(Transfe
   if (aItem.mData) {
     return;
   }
 
   // only drag and paste events should be calling FillInExternalData
   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<nsITransferable> trans =
-      do_CreateInstance("@mozilla.org/widget/transferable;1");
-    if (!trans)
-      return;
+  nsCOMPtr<nsITransferable> trans =
+    do_CreateInstance("@mozilla.org/widget/transferable;1");
+  if (!trans)
+    return;
 
   trans->Init(nullptr);
   trans->AddDataFlavor(format);
 
   if (mEventMessage == ePaste) {
     MOZ_ASSERT(aIndex == 0, "index in clipboard must be 0");
 
     nsCOMPtr<nsIClipboard> clipboard = do_GetService("@mozilla.org/widget/clipboard;1");
@@ -1304,53 +1468,115 @@ DataTransfer::FillInExternalData(Transfe
     nsCOMPtr<nsIDOMDocument> domDoc;
     dragSession->GetSourceDocument(getter_AddRefs(domDoc));
     MOZ_ASSERT(!domDoc);
 #endif
 
     dragSession->GetData(trans, aIndex);
   }
 
-    uint32_t length = 0;
-    nsCOMPtr<nsISupports> data;
-    trans->GetTransferData(format, getter_AddRefs(data), &length);
-    if (!data)
-      return;
+  uint32_t length = 0;
+  nsCOMPtr<nsISupports> data;
+  trans->GetTransferData(format, getter_AddRefs(data), &length);
+  if (!data)
+    return;
+
+  RefPtr<nsVariantCC> variant = new nsVariantCC();
 
-    RefPtr<nsVariantCC> variant = new nsVariantCC();
-
-    nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data);
-    if (supportsstr) {
-      nsAutoString str;
-      supportsstr->GetData(str);
-      variant->SetAsAString(str);
+  nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data);
+  if (supportsstr) {
+    nsAutoString str;
+    supportsstr->GetData(str);
+    variant->SetAsAString(str);
+  }
+  else {
+    nsCOMPtr<nsISupportsCString> supportscstr = do_QueryInterface(data);
+    if (supportscstr) {
+      nsAutoCString str;
+      supportscstr->GetData(str);
+      variant->SetAsACString(str);
+    } else {
+      variant->SetAsISupports(data);
     }
-    else {
-      nsCOMPtr<nsISupportsCString> supportscstr = do_QueryInterface(data);
-      if (supportscstr) {
-        nsAutoCString str;
-        supportscstr->GetData(str);
-        variant->SetAsACString(str);
-      } else {
-        variant->SetAsISupports(data);
-      }
-    }
+  }
 
-    aItem.mData = variant;
-  }
+  aItem.mData = variant;
+}
 
 void
 DataTransfer::FillAllExternalData()
 {
   if (mIsExternal) {
     for (uint32_t i = 0; i < mItems.Length(); ++i) {
       nsTArray<TransferItem>& itemArray = mItems[i];
       for (uint32_t j = 0; j < itemArray.Length(); ++j) {
         if (!itemArray[j].mData) {
           FillInExternalData(itemArray[j], i);
         }
       }
     }
   }
 }
 
+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<nsIInputStream> stringStream;
+  NS_NewCStringInputStream(getter_AddRefs(stringStream), str);
+
+  nsCOMPtr<nsIBinaryInputStream> 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<char16_t*>(formatBytes), formatLength / sizeof(char16_t));
+
+      uint32_t dataLength;
+      stream->Read32(&dataLength);
+      char* dataBytes;
+      stream->ReadBytes(dataLength, &dataBytes);
+      nsAutoString data;
+      data.Adopt(reinterpret_cast<char16_t*>(dataBytes), dataLength / sizeof(char16_t));
+
+      RefPtr<nsVariantCC> variant = new nsVariantCC();
+      variant->SetAsAString(data);
+
+      SetDataWithPrincipal(format, variant, aIndex, aPrincipal);
+    }
+  } while (type != eCustomClipboardTypeId_None);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/events/DataTransfer.h
+++ b/dom/events/DataTransfer.h
@@ -213,16 +213,23 @@ public:
   // Similar to SetData except also specifies the principal to store.
   // aData may be null when called from CacheExternalDragFormats or
   // CacheExternalClipboardFormats.
   nsresult SetDataWithPrincipal(const nsAString& aFormat,
                                 nsIVariant* aData,
                                 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)
   {
     *aX = mDragImageX;
     *aY = mDragImageY;
     return mDragImage;
   }
 
@@ -256,16 +263,19 @@ protected:
   nsresult GetDataAtInternal(const nsAString& aFormat, uint32_t aIndex,
                              nsIPrincipal* aSubjectPrincipal, nsIVariant** aData);
   nsresult SetDataAtInternal(const nsAString& aFormat, nsIVariant* aData, uint32_t aIndex,
                              nsIPrincipal* aSubjectPrincipal);
 
   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);
 
   nsCOMPtr<nsISupports> mParent;
 
 
   // the drop effect and effect allowed
   uint32_t mDropEffect;
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -3217,26 +3217,29 @@ ContentChild::RecvInvokeDragSession(nsTA
       for (uint32_t i = 0; i < aTransfers.Length(); ++i) {
         auto& items = aTransfers[i].items();
         for (uint32_t j = 0; j < items.Length(); ++j) {
           const IPCDataTransferItem& item = items[j];
           RefPtr<nsVariantCC> variant = new nsVariantCC();
           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<BlobChild*>(item.data().get_PBlobChild());
             RefPtr<BlobImpl> blobImpl = blob->GetBlobImpl();
             variant->SetAsISupports(blobImpl);
           } 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);
     }
   }
   return true;
 }
 
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -3137,34 +3137,37 @@ TabParent::AddInitialDnDDataTo(DataTrans
           new nsContentAreaDragDropDataProvider();
         variant->SetAsISupports(flavorDataProvider);
       } else if (item.data().type() == IPCDataTransferData::TnsString) {
         variant->SetAsAString(item.data().get_nsString());
       } else if (item.data().type() == IPCDataTransferData::TPBlobParent) {
         auto* parent = static_cast<BlobParent*>(item.data().get_PBlobParent());
         RefPtr<BlobImpl> 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<imgIContainer> 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<imgIContainer> 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();
 }
 
 void
 TabParent::TakeDragVisualization(RefPtr<mozilla::gfx::SourceSurface>& aSurface,
                                  int32_t& aDragAreaX, int32_t& aDragAreaY)
--- a/dom/tests/mochitest/general/test_clipboard_events.html
+++ b/dom/tests/mochitest/general/test_clipboard_events.html
@@ -448,17 +448,17 @@ function test_input_copypaste_dataTransf
     exh = false;
     try { cd.mozGetDataAt("text/plain", 1); } catch (ex) { exh = true; }
     ok(exh, "exception occured mozGetDataAt 1");
     exh = false;
     try { cd.mozClearDataAt("text/plain", 1); } catch (ex) { exh = true; }
     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;
   };
 
   try {
     selectContentInput();
     synthesizeKey("c", {accelKey: 1});
   }
@@ -474,19 +474,26 @@ function test_input_copypaste_dataTransf
     var cd = event.clipboardData;
     is(cd.mozItemCount, 1, "paste after copy multiple types mozItemCount");
     is(cd.getData("text/plain"), "would be a phrase", "paste text/plain multiple types");
 
     // Firefox for Android's clipboard code doesn't handle x-moz-url. Therefore
     // 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; }
     ok(exh, "exception occured setData on paste");
 
     is(cd.getData("text/plain"), "would be a phrase", "text/plain data unchanged");
   };
   try {
--- a/widget/cocoa/nsClipboard.mm
+++ b/widget/cocoa/nsClipboard.mm
@@ -181,16 +181,41 @@ nsClipboard::TransferableFromPasteboard(
 
       nsCOMPtr<nsISupports> genericDataWrapper;
       nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtrNoBOM, dataLength,
                                                  getter_AddRefs(genericDataWrapper));
       aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLength);
       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<nsISupports> 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) ||
              flavorStr.EqualsLiteral(kGIFImageMime)) {
       // Figure out if there's data on the pasteboard we can grab (sanity check)
       NSString *type = [cocoaPasteboard availableTypeFromArray:[NSArray arrayWithObjects:IMAGE_PASTEBOARD_TYPES]];
       if (!type)
         continue;
@@ -325,17 +350,17 @@ nsClipboard::HasDataMatchingFlavors(cons
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   *outResult = false;
 
   if ((aWhichClipboard != kGlobalClipboard) || !aFlavorList)
     return NS_OK;
 
   // first see if we have data for this in our cached transferable
-  if (mTransferable) {    
+  if (mTransferable) {
     nsCOMPtr<nsISupportsArray> transferableFlavorList;
     nsresult rv = mTransferable->FlavorsTransferableCanImport(getter_AddRefs(transferableFlavorList));
     if (NS_SUCCEEDED(rv)) {
       uint32_t transferableFlavorCount;
       transferableFlavorList->Count(&transferableFlavorCount);
       for (uint32_t j = 0; j < transferableFlavorCount; j++) {
         nsCOMPtr<nsISupports> transferableFlavorSupports;
         transferableFlavorList->GetElementAt(j, getter_AddRefs(transferableFlavorSupports));
@@ -362,16 +387,22 @@ nsClipboard::HasDataMatchingFlavors(cons
     NSString *pboardType = nil;
 
     if (nsClipboard::IsStringType(mimeType, &pboardType)) {
       NSString* availableType = [generalPBoard availableTypeFromArray:[NSArray arrayWithObject:pboardType]];
       if (availableType && [availableType isEqualToString:pboardType]) {
         *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) ||
                !strcmp(aFlavorList[i], kGIFImageMime)) {
       NSString* availableType = [generalPBoard availableTypeFromArray:
                                   [NSArray arrayWithObjects:IMAGE_PASTEBOARD_TYPES]];
       if (availableType) {
         *outResult = true;
@@ -442,16 +473,30 @@ nsClipboard::PasteboardDictFromTransfera
       
       // be nice to Carbon apps, normalize the receiver's contents using Form C.
       nativeString = [nativeString precomposedStringWithCanonicalMapping];
 
       [pasteboardOutputDict setObject:nativeString forKey:pboardType];
       
       free(data);
     }
+    else if (flavorStr.EqualsLiteral(kCustomTypesMime)) {
+      void* data = nullptr;
+      uint32_t dataSize = 0;
+      nsCOMPtr<nsISupports> 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)) {
       uint32_t dataSize = 0;
       nsCOMPtr<nsISupports> transferSupports;
       aTransferable->GetTransferData(flavorStr, getter_AddRefs(transferSupports), &dataSize);
       nsCOMPtr<nsISupportsInterfacePointer> ptrPrimitive(do_QueryInterface(transferSupports));
       if (!ptrPrimitive)
--- a/widget/cocoa/nsDragService.h
+++ b/widget/cocoa/nsDragService.h
@@ -9,16 +9,17 @@
 #include "nsBaseDragService.h"
 
 #include <Cocoa/Cocoa.h>
 
 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
 {
 public:
   nsDragService();
 
   // nsBaseDragService
   virtual nsresult InvokeDragSessionImpl(nsISupportsArray* anArrayTransferables,
--- a/widget/cocoa/nsDragService.mm
+++ b/widget/cocoa/nsDragService.mm
@@ -44,16 +44,17 @@ extern bool gUserCancelledDrag;
 // file destination callback.
 nsISupportsArray *gDraggedTransferables = nullptr;
 
 NSString* const kWildcardPboardType = @"MozillaWildcard";
 NSString* const kCorePboardType_url  = @"CorePasteboardFlavorType 0x75726C20"; // 'url '  url
 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()
 {
   mNativeDragView = nil;
   mNativeDragEvent = nil;
 
   EnsureLogInitialized();
 }
@@ -105,17 +106,18 @@ static nsresult SetUpDragClipboard(nsISu
           currentKey == kCorePboardType_urld ||
           currentKey == kCorePboardType_urln) {
         [dragPBoard setString:currentValue forType:currentKey];
       }
       else if (currentKey == NSHTMLPboardType) {
         [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 ||
                currentKey == NSFilenamesPboardType) {
         [dragPBoard setPropertyList:currentValue forType:currentKey];        
       }
     }
   }
@@ -469,16 +471,41 @@ nsDragService::GetData(nsITransferable* 
       free(clipboardDataPtr);
       if (NS_FAILED(rv))
         continue;
 
       aTransferable->SetTransferData(flavorStr, file, dataLength);
       
       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<nsISupports> 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)) {
       pString = GetStringForType(item, (const NSString*)kUTTypeUTF8PlainText);
     } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
       pString = GetStringForType(item, (const NSString*)kUTTypeHTML);
     } else if (flavorStr.EqualsLiteral(kURLMime)) {
       pString = GetStringForType(item, (const NSString*)kUTTypeURL);
@@ -605,17 +632,20 @@ nsDragService::IsDataFlavorSupported(con
     type = (const NSString*)kUTTypeHTML;
   } else if (dataFlavor.EqualsLiteral(kURLMime) ||
              dataFlavor.EqualsLiteral(kURLDataMime)) {
     type = (const NSString*)kUTTypeURL;
   } else if (dataFlavor.EqualsLiteral(kURLDescriptionMime)) {
     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;
   }
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
--- a/widget/gtk/nsDragService.cpp
+++ b/widget/gtk/nsDragService.cpp
@@ -965,22 +965,24 @@ nsDragService::GetData(nsITransferable *
                                    ("failed to get _NETSCAPE_URL data\n"));
                         }
                     }
                 }
 
             } // 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<int*>(&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<int*>(&mTargetDragDataLen));
+                }
         
                 // put it into the transferable.
                 nsCOMPtr<nsISupports> genericDataWrapper;
                 nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr,
                                     mTargetDragData, mTargetDragDataLen,
                                     getter_AddRefs(genericDataWrapper));
                 aTransferable->SetTransferData(flavorStr,
                                                genericDataWrapper,
--- a/widget/nsClipboardProxy.cpp
+++ b/widget/nsClipboardProxy.cpp
@@ -88,17 +88,18 @@ nsClipboardProxy::GetData(nsITransferabl
           flavor.EqualsLiteral(kPNGImageMime) ||
           flavor.EqualsLiteral(kGIFImageMime)) {
         nsCOMPtr<nsIInputStream> stream;
         NS_NewCStringInputStream(getter_AddRefs(stream), item.data().get_nsCString());
 
         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<nsISupportsCString> dataWrapper =
           do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
         NS_ENSURE_SUCCESS(rv, rv);
 
         nsCString data = item.data().get_nsCString();
         rv = dataWrapper->SetData(data);
         NS_ENSURE_SUCCESS(rv, rv);
 
--- a/widget/nsITransferable.idl
+++ b/widget/nsITransferable.idl
@@ -44,16 +44,18 @@ interface nsIDOMNode;
 #define kFilePromiseURLMime         "application/x-moz-file-promise-url"
 // the destination filename for a file promise
 #define kFilePromiseDestFilename    "application/x-moz-file-promise-dest-filename"
 // a dataless flavor used to interact with the OS during file drags
 #define kFilePromiseMime            "application/x-moz-file-promise"
 // 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"
+
 %}
 
 
 /**
   * nsIFlavorDataProvider allows a flavor to 'promise' data later,
   * supplying the data lazily.
   * 
   * To use it, call setTransferData, passing the flavor string,
--- a/widget/nsPrimitiveHelpers.cpp
+++ b/widget/nsPrimitiveHelpers.cpp
@@ -43,17 +43,17 @@
 void
 nsPrimitiveHelpers :: CreatePrimitiveForData ( const char* aFlavor, const void* aDataBuff,
                                                  uint32_t aDataLen, nsISupports** aPrimitive )
 {
   if ( !aPrimitive )
     return;
 
   if ( strcmp(aFlavor,kTextMime) == 0 || strcmp(aFlavor,kNativeHTMLMime) == 0 ||
-       strcmp(aFlavor,kRTFMime) == 0) {
+       strcmp(aFlavor,kRTFMime) == 0 || strcmp(aFlavor,kCustomTypesMime) == 0) {
     nsCOMPtr<nsISupportsCString> primitive =
         do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
     if ( primitive ) {
       const char * start = reinterpret_cast<const char*>(aDataBuff);
       primitive->SetData(Substring(start, start + aDataLen));
       NS_ADDREF(*aPrimitive = primitive);
     }
   }
@@ -128,17 +128,17 @@ void
 nsPrimitiveHelpers :: CreateDataFromPrimitive ( const char* aFlavor, nsISupports* aPrimitive,
                                                    void** aDataBuff, uint32_t aDataLen )
 {
   if ( !aDataBuff )
     return;
 
   *aDataBuff = nullptr;
 
-  if ( strcmp(aFlavor,kTextMime) == 0 ) {
+  if ( strcmp(aFlavor,kTextMime) == 0 || strcmp(aFlavor,kCustomTypesMime) == 0) {
     nsCOMPtr<nsISupportsCString> plainText ( do_QueryInterface(aPrimitive) );
     if ( plainText ) {
       nsAutoCString data;
       plainText->GetData ( data );
       *aDataBuff = ToNewCString(data);
     }
   }
   else {
--- a/widget/windows/nsClipboard.cpp
+++ b/widget/windows/nsClipboard.cpp
@@ -37,16 +37,17 @@
 #include "nsIObserverService.h"
 
 using mozilla::LogLevel;
 
 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");
 
 
 //-------------------------------------------------------------------------
 //
 // nsClipboard constructor
 //
 //-------------------------------------------------------------------------
 nsClipboard::nsClipboard() : nsBaseClipboard()
@@ -103,16 +104,18 @@ UINT nsClipboard::GetFormat(const char* 
            strcmp(aMimeStr, kPNGImageMime) == 0)
     format = CF_DIBV5;
   else if (strcmp(aMimeStr, kFileMime) == 0 ||
            strcmp(aMimeStr, kFilePromiseMime) == 0)
     format = CF_HDROP;
   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());
 
   return format;
 }
 
 //-------------------------------------------------------------------------
 nsresult nsClipboard::CreateNativeDataObject(nsITransferable * aTransferable, IDataObject ** aDataObj, nsIURI * uri)
@@ -529,16 +532,19 @@ nsresult nsClipboard::GetNativeDataOffCl
                 uint32_t allocLen = 0;
                 if ( NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen)) ) {
                   if ( fe.cfFormat == CF_HTML ) {
                     // CF_HTML is actually UTF8, not unicode, so disregard the assumption
                     // above. We have to check the header for the actual length, and we'll
                     // 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
                     // Reference: http://msdn.microsoft.com/en-us/library/bb776902(v=vs.85).aspx
                     NS_ASSERTION(allocLen == sizeof(DWORD),
                       "CFSTR_PREFERREDDROPEFFECT should return a DWORD");
                     *aLen = allocLen;
                   } else {
@@ -677,26 +683,29 @@ nsresult nsClipboard::GetDataFromDataObj
         else if ( strcmp(flavorStr, kJPEGImageMime) == 0 ||
                   strcmp(flavorStr, kJPGImageMime) == 0 ||
                   strcmp(flavorStr, kPNGImageMime) == 0) {
           nsIInputStream * imageStream = reinterpret_cast<nsIInputStream*>(data);
           genericDataWrapper = do_QueryInterface(imageStream);
           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<int32_t>(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<int32_t>(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<char*>(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<char*>(data)[dataLen - 1] == '\0')
+                dataLen--;
+            }
           }
 
           nsPrimitiveHelpers::CreatePrimitiveForData ( flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper) );
           free(data);
         }
         
         NS_ASSERTION ( genericDataWrapper, "About to put null data into the transferable" );
         aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLen);
--- a/widget/windows/nsClipboard.h
+++ b/widget/windows/nsClipboard.h
@@ -55,16 +55,17 @@ public:
   // This function returns the internal Windows clipboard format identifier
   // for a given Mime string. The default is to map kHTMLMime ("text/html")
   // to the clipboard format CF_HTML ("HTLM Format"), but it can also be
   // registered as clipboard format "text/html" to support previous versions
   // of Gecko.
   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;
   NS_IMETHOD GetNativeClipboardData ( nsITransferable * aTransferable, int32_t aWhichClipboard ) override;
   
   static bool IsInternetShortcut ( const nsAString& inFileName ) ;
   static bool FindURLFromLocalFile ( IDataObject* inDataObject, UINT inIndex, void** outData, uint32_t* outDataLen ) ;
   static bool FindURLFromNativeURL ( IDataObject* inDataObject, UINT inIndex, void** outData, uint32_t* outDataLen ) ;
--- a/widget/windows/nsDataObj.cpp
+++ b/widget/windows/nsDataObj.cpp
@@ -1323,17 +1323,17 @@ HRESULT nsDataObj::GetText(const nsACStr
       data = utf8HTML;
       allocLen = strlen(utf8HTML) + sizeof(char);
     }
     else {
       NS_WARNING ( "Oh no, couldn't convert to HTML" );
       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);
   }
 
   hGlobalMemory = (HGLOBAL)GlobalAlloc(GMEM_MOVEABLE, allocLen);
 
   // Copy text to Global Memory Area