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 332236 4cbc83f4c4b1d7cd3bf9758247dce1f54318fb62
parent 332235 eb1d405b1239f342187201225a6cac0ff21440c2
child 332237 2747cac2de2fcfd3233930a10e4fa560b3376b60
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, jmathies, mstange
bugs860857
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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