Bug 1071562, e10s, support non-text types in clipboard (html, images, etc), r=smaug
authorNeil Deakin <neil@mozilla.com>
Thu, 16 Apr 2015 15:38:12 -0400
changeset 239660 802512ee57aec9d975ab078c48b9f37956012bc6
parent 239659 ffd1b16f058b7ddb85f7f003a993fb730d7d2016
child 239661 e812687a81cdc72c7b3379ad96864a0ce380a003
push id12444
push userryanvm@gmail.com
push dateFri, 17 Apr 2015 20:04:42 +0000
treeherderfx-team@560a202db924 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1071562
milestone40.0a1
Bug 1071562, e10s, support non-text types in clipboard (html, images, etc), r=smaug
browser/base/content/test/general/browser.ini
browser/base/content/test/general/browser_clipboard.js
dom/base/moz.build
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/DOMTypes.ipdlh
dom/ipc/PContent.ipdl
widget/nsClipboardProxy.cpp
widget/nsDragServiceProxy.cpp
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -282,16 +282,17 @@ skip-if = buildapp == "mulet" || e10s # 
 [browser_bug970746.js]
 skip-if = e10s # Bug 1093155 - tries to use context menu from browser-chrome and gets in a mess when in e10s mode
 [browser_bug1015721.js]
 skip-if = os == 'win' || e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
 [browser_bug1064280_changeUrlInPinnedTab.js]
 [browser_bug1070778.js]
 [browser_canonizeURL.js]
 skip-if = e10s # Bug 1094510 - test hits the network in e10s mode only
+[browser_clipboard.js]
 [browser_contentAreaClick.js]
 [browser_contextSearchTabPosition.js]
 skip-if = os == "mac" || e10s # bug 967013; e10s: bug 1094761 - test hits the network in e10s, causing next test to crash
 [browser_ctrlTab.js]
 [browser_datareporting_notification.js]
 skip-if = !datareporting
 [browser_devedition.js]
 [browser_devices_get_user_media.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_clipboard.js
@@ -0,0 +1,123 @@
+// This test is used to check copy and paste in editable areas to ensure that non-text
+// types (html and images) are copied to and pasted from the clipboard properly.
+
+let testPage = "<div id='main' contenteditable='true'>Test <b>Bold</b> After Text</div>";
+
+add_task(function*() {
+  let tab = gBrowser.addTab();
+  let browser = gBrowser.getBrowserForTab(tab);
+
+  gBrowser.selectedTab = tab;
+
+  yield promiseTabLoadEvent(tab, "data:text/html," + escape(testPage));
+  yield SimpleTest.promiseFocus(browser.contentWindowAsCPOW);
+
+  let results = yield ContentTask.spawn(browser, {}, function* () {
+    var doc = content.document;
+    var main = doc.getElementById("main");
+    main.focus();
+
+    const utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                         .getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+    const modifier = (content.navigator.platform.indexOf("Mac") >= 0) ?
+                     Components.interfaces.nsIDOMWindowUtils.MODIFIER_META :
+                     Components.interfaces.nsIDOMWindowUtils.MODIFIER_CONTROL;
+
+    function sendKey(key)
+    {
+     if (utils.sendKeyEvent("keydown", key, 0, modifier)) {
+       utils.sendKeyEvent("keypress", key, key.charCodeAt(0), modifier);
+     }
+     utils.sendKeyEvent("keyup", key, 0, modifier);
+    }
+
+    let results = [];
+    function is(l, r, v) {
+      results.push(((l === r) ? "PASSED" : "FAILED") + " got: " + l + " expected: " + r + " - " + v);
+    }
+
+    // Select an area of the text.
+    let selection = doc.getSelection();
+    selection.modify("move", "left", "line");
+    selection.modify("move", "right", "character");
+    selection.modify("move", "right", "character");
+    selection.modify("move", "right", "character");
+    selection.modify("extend", "right", "word");
+    selection.modify("extend", "right", "word");
+
+    yield new content.Promise((resolve, reject) => {
+      addEventListener("copy", function copyEvent(event) {
+        removeEventListener("copy", copyEvent, true);
+        // The data is empty as the selection is copied during the event default phase.
+        is(event.clipboardData.mozItemCount, 0, "Zero items on clipboard");
+        resolve();
+      }, true)
+
+      sendKey("c");
+    });
+
+    selection.modify("move", "right", "line");
+
+    yield new content.Promise((resolve, reject) => {
+      addEventListener("paste", function copyEvent(event) {
+        removeEventListener("paste", copyEvent, true);
+        let clipboardData = event.clipboardData; 
+        is(clipboardData.mozItemCount, 1, "One item on clipboard");
+        is(clipboardData.types.length, 2, "Two types on clipboard");
+        is(clipboardData.types[0], "text/html", "text/html on clipboard");
+        is(clipboardData.types[1], "text/plain", "text/plain on clipboard");
+        is(clipboardData.getData("text/html"), "t <b>Bold</b>", "text/html value");
+        is(clipboardData.getData("text/plain"), "t Bold", "text/plain value");
+        resolve();
+      }, true)
+      sendKey("v");
+    });
+
+    is(main.innerHTML, "Test <b>Bold</b> After Textt <b>Bold</b>", "Copy and paste html");
+
+    selection.modify("extend", "left", "word");
+    selection.modify("extend", "left", "word");
+    selection.modify("extend", "left", "character");
+
+    yield new content.Promise((resolve, reject) => {
+      addEventListener("cut", function copyEvent(event) {
+        removeEventListener("cut", copyEvent, true);
+        event.clipboardData.setData("text/plain", "Some text");
+        event.clipboardData.setData("text/html", "<i>Italic</i> ");
+        selection.deleteFromDocument();
+        event.preventDefault();
+        resolve();
+      }, true)
+      sendKey("x");
+    });
+
+    selection.modify("move", "left", "line");
+
+    yield new content.Promise((resolve, reject) => {
+      addEventListener("paste", function copyEvent(event) {
+        removeEventListener("paste", copyEvent, true);
+        let clipboardData = event.clipboardData; 
+        is(clipboardData.mozItemCount, 1, "One item on clipboard 2");
+        is(clipboardData.types.length, 2, "Two types on clipboard 2");
+        is(clipboardData.types[0], "text/html", "text/html on clipboard 2");
+        is(clipboardData.types[1], "text/plain", "text/plain on clipboard 2");
+        is(clipboardData.getData("text/html"), "<i>Italic</i> ", "text/html value 2");
+        is(clipboardData.getData("text/plain"), "Some text", "text/plain value 2");
+        resolve();
+      }, true)
+      sendKey("v");
+    });
+
+    is(main.innerHTML, "<i>Italic</i> Test <b>Bold</b> After<b></b>", "Copy and paste html 2");
+    return results;
+  });
+
+  is(results.length, 15, "Correct number of results");
+  for (var t = 0; t < results.length; t++) {
+    ok(results[t].startsWith("PASSED"), results[t]);
+  }
+
+  gBrowser.removeCurrentTab();
+});
+
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -412,16 +412,17 @@ LOCAL_INCLUDES += [
     '/dom/ipc',
     '/dom/storage',
     '/dom/svg',
     '/dom/workers',
     '/dom/xbl',
     '/dom/xml',
     '/dom/xslt/xpath',
     '/dom/xul',
+    '/gfx/2d',
     '/image/src',
     '/js/xpconnect/src',
     '/js/xpconnect/wrappers',
     '/layout/base',
     '/layout/generic',
     '/layout/style',
     '/layout/svg',
     '/layout/xul',
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -166,16 +166,17 @@
 #include "nsParserCIID.h"
 #include "nsParserConstants.h"
 #include "nsPIDOMWindow.h"
 #include "nsPresContext.h"
 #include "nsPrintfCString.h"
 #include "nsReferencedElement.h"
 #include "nsSandboxFlags.h"
 #include "nsScriptSecurityManager.h"
+#include "nsStreamUtils.h"
 #include "nsSVGFeatures.h"
 #include "nsTextEditorState.h"
 #include "nsTextFragment.h"
 #include "nsTextNode.h"
 #include "nsThreadUtils.h"
 #include "nsUnicharUtilCIID.h"
 #include "nsUnicodeProperties.h"
 #include "nsViewManager.h"
@@ -7214,92 +7215,179 @@ nsContentUtils::CallOnAllRemoteChildren(
 
 void
 nsContentUtils::TransferablesToIPCTransferables(nsISupportsArray* aTransferables,
                                                 nsTArray<IPCDataTransfer>& aIPC,
                                                 mozilla::dom::nsIContentChild* aChild,
                                                 mozilla::dom::nsIContentParent* aParent)
 {
   aIPC.Clear();
-  MOZ_ASSERT((aChild && !aParent) || (!aChild && aParent));
   if (aTransferables) {
     uint32_t transferableCount = 0;
     aTransferables->Count(&transferableCount);
     for (uint32_t i = 0; i < transferableCount; ++i) {
       IPCDataTransfer* dt = aIPC.AppendElement();
       nsCOMPtr<nsISupports> genericItem;
       aTransferables->GetElementAt(i, getter_AddRefs(genericItem));
-      nsCOMPtr<nsITransferable> item(do_QueryInterface(genericItem));
-      if (item) {
-        nsCOMPtr<nsISupportsArray> flavorList;
-        item->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
-        if (flavorList) {
-          uint32_t flavorCount = 0;
-          flavorList->Count(&flavorCount);
-          for (uint32_t j = 0; j < flavorCount; ++j) {
-            nsCOMPtr<nsISupportsCString> flavor = do_QueryElementAt(flavorList, j);
-            if (!flavor) {
-              continue;
+      nsCOMPtr<nsITransferable> transferable(do_QueryInterface(genericItem));
+      TransferableToIPCTransferable(transferable, dt, aChild, aParent);
+    }
+  }
+}
+
+void
+nsContentUtils::TransferableToIPCTransferable(nsITransferable* aTransferable,
+                                              IPCDataTransfer* aIPCDataTransfer,
+                                              mozilla::dom::nsIContentChild* aChild,
+                                              mozilla::dom::nsIContentParent* aParent)
+{
+  MOZ_ASSERT((aChild && !aParent) || (!aChild && aParent));
+
+  if (aTransferable) {
+    nsCOMPtr<nsISupportsArray> flavorList;
+    aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
+    if (flavorList) {
+      uint32_t flavorCount = 0;
+      flavorList->Count(&flavorCount);
+      for (uint32_t j = 0; j < flavorCount; ++j) {
+        nsCOMPtr<nsISupportsCString> flavor = do_QueryElementAt(flavorList, j);
+        if (!flavor) {
+          continue;
+        }
+
+        nsAutoCString flavorStr;
+        flavor->GetData(flavorStr);
+        if (!flavorStr.Length()) {
+          continue;
+        }
+
+        nsCOMPtr<nsISupports> data;
+        uint32_t dataLen = 0;
+        aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(data), &dataLen);
+
+        nsCOMPtr<nsISupportsString> text = do_QueryInterface(data);
+        nsCOMPtr<nsISupportsCString> ctext = do_QueryInterface(data);
+        if (text) {
+          nsAutoString dataAsString;
+          text->GetData(dataAsString);
+          IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+          item->flavor() = nsCString(flavorStr);
+          item->data() = nsString(dataAsString);
+        } else if (ctext) {
+          nsAutoCString dataAsString;
+          ctext->GetData(dataAsString);
+          IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+          item->flavor() = nsCString(flavorStr);
+          item->data() = nsCString(dataAsString);
+        } else {
+          nsCOMPtr<nsISupportsInterfacePointer> sip =
+            do_QueryInterface(data);
+          if (sip) {
+            sip->GetData(getter_AddRefs(data));
+          }
+
+          // Images to be pasted on the clipboard are nsIInputStreams
+          nsCOMPtr<nsIInputStream> stream(do_QueryInterface(data));
+          if (stream) {
+            IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+            item->flavor() = nsCString(flavorStr);
+
+            nsCString imageData;
+            NS_ConsumeStream(stream, UINT32_MAX, imageData);
+            item->data() = imageData;
+            continue;
+          }
+
+          // Images to be placed on the clipboard are imgIContainers.
+          nsCOMPtr<imgIContainer> image(do_QueryInterface(data));
+          if (image) {
+            RefPtr<mozilla::gfx::SourceSurface> surface =
+              image->GetFrame(imgIContainer::FRAME_CURRENT,
+                              imgIContainer::FLAG_SYNC_DECODE);
+
+            mozilla::RefPtr<mozilla::gfx::DataSourceSurface> dataSurface =
+              surface->GetDataSurface();
+            size_t length;
+            int32_t stride;
+            mozilla::UniquePtr<char[]> surfaceData =
+              nsContentUtils::GetSurfaceData(dataSurface, &length, &stride);
+            
+            IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+            item->flavor() = nsCString(flavorStr);
+            item->data() = nsCString(surfaceData.get(), length);
+
+            IPCDataTransferImage& imageDetails = item->imageDetails();
+            mozilla::gfx::IntSize size = dataSurface->GetSize();
+            imageDetails.width() = size.width;
+            imageDetails.height() = size.height;
+            imageDetails.stride() = stride;
+            imageDetails.format() = static_cast<uint8_t>(dataSurface->GetFormat());
+
+            continue;
+          }
+
+          // Otherwise, handle this as a file.
+          nsCOMPtr<FileImpl> fileImpl;
+          nsCOMPtr<nsIFile> file = do_QueryInterface(data);
+          if (file) {
+            fileImpl = new FileImplFile(file, false);
+            ErrorResult rv;
+            fileImpl->GetSize(rv);
+            fileImpl->GetLastModified(rv);
+          } else {
+            fileImpl = do_QueryInterface(data);
+          }
+          if (fileImpl) {
+            IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+            item->flavor() = nsCString(flavorStr);
+            if (aChild) {
+              item->data() =
+                mozilla::dom::BlobChild::GetOrCreate(aChild,
+                  static_cast<FileImpl*>(fileImpl.get()));
+            } else if (aParent) {
+              item->data() =
+                mozilla::dom::BlobParent::GetOrCreate(aParent,
+                  static_cast<FileImpl*>(fileImpl.get()));
             }
-
-            nsAutoCString flavorStr;
-            flavor->GetData(flavorStr);
-            if (!flavorStr.Length()) {
-              continue;
-            }
-
-            nsCOMPtr<nsISupports> data;
-            uint32_t dataLen = 0;
-            item->GetTransferData(flavorStr.get(), getter_AddRefs(data), &dataLen);
-
-            nsCOMPtr<nsISupportsString> text = do_QueryInterface(data);
-            if (text) {
-              nsAutoString dataAsString;
-              text->GetData(dataAsString);
-              IPCDataTransferItem* item = dt->items().AppendElement();
+          } else {
+            // This is a hack to support kFilePromiseMime.
+            // On Windows there just needs to be an entry for it, 
+            // and for OSX we need to create
+            // nsContentAreaDragDropDataProvider as nsIFlavorDataProvider.
+            if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
+              IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
               item->flavor() = nsCString(flavorStr);
-              item->data() = nsString(dataAsString);
-            } else {
-              nsCOMPtr<nsISupportsInterfacePointer> sip =
-                do_QueryInterface(data);
-              if (sip) {
-                sip->GetData(getter_AddRefs(data));
-              }
-              nsCOMPtr<FileImpl> fileImpl;
-              nsCOMPtr<nsIFile> file = do_QueryInterface(data);
-              if (file) {
-                fileImpl = new FileImplFile(file, false);
-                ErrorResult rv;
-                fileImpl->GetSize(rv);
-                fileImpl->GetLastModified(rv);
-              } else {
-                fileImpl = do_QueryInterface(data);
-              }
-              if (fileImpl) {
-                IPCDataTransferItem* item = dt->items().AppendElement();
-                item->flavor() = nsCString(flavorStr);
-                if (aChild) {
-                  item->data() =
-                    mozilla::dom::BlobChild::GetOrCreate(aChild,
-                      static_cast<FileImpl*>(fileImpl.get()));
-                } else if (aParent) {
-                  item->data() =
-                    mozilla::dom::BlobParent::GetOrCreate(aParent,
-                      static_cast<FileImpl*>(fileImpl.get()));
-                }
-              } else {
-                // This is a hack to support kFilePromiseMime.
-                // On Windows there just needs to be an entry for it, 
-                // and for OSX we need to create
-                // nsContentAreaDragDropDataProvider as nsIFlavorDataProvider.
-                if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
-                  IPCDataTransferItem* item = dt->items().AppendElement();
-                  item->flavor() = nsCString(flavorStr);
-                  item->data() = NS_ConvertUTF8toUTF16(flavorStr);
-                }
-              }
+              item->data() = NS_ConvertUTF8toUTF16(flavorStr);
             }
           }
         }
       }
     }
   }
 }
+
+mozilla::UniquePtr<char[]>
+nsContentUtils::GetSurfaceData(mozilla::gfx::DataSourceSurface* aSurface,
+                               size_t* aLength, int32_t* aStride)
+{
+  mozilla::gfx::DataSourceSurface::MappedSurface map;
+  aSurface->Map(mozilla::gfx::DataSourceSurface::MapType::READ, &map);
+  mozilla::gfx::IntSize size = aSurface->GetSize();
+  mozilla::CheckedInt32 requiredBytes =
+    mozilla::CheckedInt32(map.mStride) * mozilla::CheckedInt32(size.height);
+  size_t maxBufLen = requiredBytes.isValid() ? requiredBytes.value() : 0;
+  mozilla::gfx::SurfaceFormat format = aSurface->GetFormat();
+
+  // Surface data handling is totally nuts. This is the magic one needs to
+  // know to access the data.
+  size_t bufLen = maxBufLen - map.mStride + (size.width * BytesPerPixel(format));
+
+  // nsDependentCString wants null-terminated string.
+  mozilla::UniquePtr<char[]> surfaceData(new char[maxBufLen + 1]);
+  memcpy(surfaceData.get(), reinterpret_cast<char*>(map.mData), bufLen);
+  memset(surfaceData.get() + bufLen, 0, maxBufLen - bufLen + 1);
+
+  *aLength = maxBufLen;
+  *aStride = map.mStride;
+
+  aSurface->Unmap();
+  return surfaceData;
+}
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -98,16 +98,17 @@ class nsPIDOMWindow;
 class nsPresContext;
 class nsScriptObjectTracer;
 class nsStringBuffer;
 class nsStringHashKey;
 class nsTextFragment;
 class nsViewportInfo;
 class nsWrapperCache;
 class nsAttrValue;
+class nsITransferable;
 
 struct JSPropertyDescriptor;
 struct JSRuntime;
 struct nsIntMargin;
 
 template<class E> class nsCOMArray;
 template<class K, class V> class nsDataHashtable;
 template<class K, class V> class nsRefPtrHashtable;
@@ -124,16 +125,20 @@ class EventTarget;
 class IPCDataTransfer;
 class NodeInfo;
 class nsIContentChild;
 class nsIContentParent;
 class Selection;
 class TabParent;
 } // namespace dom
 
+namespace gfx {
+class DataSourceSurface;
+} // namespace gfx
+
 namespace layers {
 class LayerManager;
 } // namespace layers
 
 } // namespace mozilla
 
 class nsIBidiKeyboard;
 
@@ -2293,16 +2298,29 @@ public:
   static void CallOnAllRemoteChildren(nsIDOMWindow* aWindow,
                                       CallOnRemoteChildFunction aCallback,
                                       void* aArg);
 
   static void TransferablesToIPCTransferables(nsISupportsArray* aTransferables,
                                               nsTArray<mozilla::dom::IPCDataTransfer>& aIPC,
                                               mozilla::dom::nsIContentChild* aChild,
                                               mozilla::dom::nsIContentParent* aParent);
+
+  static void TransferableToIPCTransferable(nsITransferable* aTransferable,
+                                            mozilla::dom::IPCDataTransfer* aIPCDataTransfer,
+                                            mozilla::dom::nsIContentChild* aChild,
+                                            mozilla::dom::nsIContentParent* aParent);
+
+  /*
+   * Get the pixel data from the given source surface and return it as a buffer.
+   * The length and stride will be assigned from the surface.
+   */
+  static mozilla::UniquePtr<char[]> GetSurfaceData(mozilla::gfx::DataSourceSurface* aSurface,
+                                                   size_t* aLength, int32_t* aStride);
+
 private:
   static bool InitializeEventTable();
 
   static nsresult EnsureStringBundle(PropertiesFile aFile);
 
   static bool CanCallerAccess(nsIPrincipal* aSubjectPrincipal,
                                 nsIPrincipal* aPrincipal);
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -125,28 +125,31 @@
 #include "nsISpellChecker.h"
 #include "nsIStyleSheet.h"
 #include "nsISupportsPrimitives.h"
 #include "nsISystemMessagesInternal.h"
 #include "nsITimer.h"
 #include "nsIURIFixup.h"
 #include "nsIWindowWatcher.h"
 #include "nsIXULRuntime.h"
+#include "gfxDrawable.h"
+#include "ImageOps.h"
 #include "nsMemoryInfoDumper.h"
 #include "nsMemoryReporterManager.h"
 #include "nsServiceManagerUtils.h"
 #include "nsStyleSheetService.h"
 #include "nsThreadUtils.h"
 #include "nsThreadManager.h"
 #include "nsToolkitCompsCID.h"
 #include "nsWidgetsCID.h"
 #include "PreallocatedProcessManager.h"
 #include "ProcessPriorityManager.h"
 #include "SandboxHal.h"
 #include "ScreenManagerParent.h"
+#include "SourceSurfaceRawData.h"
 #include "StructuredCloneUtils.h"
 #include "TabParent.h"
 #include "URIUtils.h"
 #include "nsIWebBrowserChrome.h"
 #include "nsIDocShell.h"
 #include "mozilla/net/NeckoMessageUtils.h"
 #include "gfxPrefs.h"
 #include "prio.h"
@@ -216,17 +219,16 @@ using namespace mozilla::system;
 #endif
 
 #ifdef MOZ_ENABLE_PROFILER_SPS
 #include "nsIProfiler.h"
 #include "nsIProfileSaveEvent.h"
 #endif
 
 static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
-static const char* sClipboardTextFlavors[] = { kUnicodeMime };
 
 using base::ChildPrivileges;
 using base::KillProcess;
 
 #ifdef MOZ_CRASHREPORTER
 using namespace CrashReporter;
 #endif
 using namespace mozilla::dom::bluetooth;
@@ -2551,98 +2553,137 @@ ContentParent::RecvReadPermissions(Infal
     // Ask for future changes
     mSendPermissionUpdates = true;
 #endif
 
     return true;
 }
 
 bool
-ContentParent::RecvSetClipboardText(const nsString& text,
-                                       const bool& isPrivateData,
-                                       const int32_t& whichClipboard)
-{
-    nsresult rv;
-    nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
-    NS_ENSURE_SUCCESS(rv, true);
-
-    nsCOMPtr<nsISupportsString> dataWrapper =
-        do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
-    NS_ENSURE_SUCCESS(rv, true);
-
-    rv = dataWrapper->SetData(text);
-    NS_ENSURE_SUCCESS(rv, true);
-
-    nsCOMPtr<nsITransferable> trans = do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
-    NS_ENSURE_SUCCESS(rv, true);
-    trans->Init(nullptr);
-
-    // If our data flavor has already been added, this will fail. But we don't care
-    trans->AddDataFlavor(kUnicodeMime);
-    trans->SetIsPrivateData(isPrivateData);
-
-    nsCOMPtr<nsISupports> nsisupportsDataWrapper =
-        do_QueryInterface(dataWrapper);
-
-    rv = trans->SetTransferData(kUnicodeMime, nsisupportsDataWrapper,
-                                text.Length() * sizeof(char16_t));
-    NS_ENSURE_SUCCESS(rv, true);
-
-    clipboard->SetData(trans, nullptr, whichClipboard);
-    return true;
-}
-
-bool
-ContentParent::RecvGetClipboardText(const int32_t& whichClipboard, nsString* text)
+ContentParent::RecvSetClipboard(const IPCDataTransfer& aDataTransfer,
+                                const bool& aIsPrivateData,
+                                const int32_t& aWhichClipboard)
 {
     nsresult rv;
     nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
     NS_ENSURE_SUCCESS(rv, true);
 
     nsCOMPtr<nsITransferable> trans = do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
     NS_ENSURE_SUCCESS(rv, true);
     trans->Init(nullptr);
-    trans->AddDataFlavor(kUnicodeMime);
-
-    clipboard->GetData(trans, whichClipboard);
-    nsCOMPtr<nsISupports> tmp;
-    uint32_t len;
-    rv = trans->GetTransferData(kUnicodeMime, getter_AddRefs(tmp), &len);
-    if (NS_FAILED(rv))
-        return true;
-
-    nsCOMPtr<nsISupportsString> supportsString = do_QueryInterface(tmp);
-    // No support for non-text data
-    if (!supportsString)
-        return true;
-    supportsString->GetData(*text);
+
+    const nsTArray<IPCDataTransferItem>& items = aDataTransfer.items();
+    for (uint32_t j = 0; j < items.Length(); ++j) {
+      const IPCDataTransferItem& item = items[j];
+
+      trans->AddDataFlavor(item.flavor().get());
+
+      if (item.data().type() == IPCDataTransferData::TnsString) {
+        nsCOMPtr<nsISupportsString> dataWrapper =
+          do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+        NS_ENSURE_SUCCESS(rv, true);
+
+        nsString text = item.data().get_nsString();
+        rv = dataWrapper->SetData(text);
+        NS_ENSURE_SUCCESS(rv, true);
+
+        rv = trans->SetTransferData(item.flavor().get(), dataWrapper,
+                                    text.Length() * sizeof(char16_t));
+
+        NS_ENSURE_SUCCESS(rv, true);
+      } else if (item.data().type() == IPCDataTransferData::TnsCString) {
+        const IPCDataTransferImage& imageDetails = item.imageDetails();
+        const gfxIntSize size(imageDetails.width(), imageDetails.height());
+        if (!size.width || !size.height) {
+          return true;
+        }
+
+        nsCString text = item.data().get_nsCString();
+        mozilla::RefPtr<gfx::DataSourceSurface> image =
+          new mozilla::gfx::SourceSurfaceRawData();
+        mozilla::gfx::SourceSurfaceRawData* raw =
+          static_cast<mozilla::gfx::SourceSurfaceRawData*>(image.get());
+        raw->InitWrappingData(
+          reinterpret_cast<uint8_t*>(const_cast<nsCString&>(text).BeginWriting()),
+          size, imageDetails.stride(),
+          static_cast<mozilla::gfx::SurfaceFormat>(imageDetails.format()), false);
+        raw->GuaranteePersistance();
+
+        nsRefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(image, size);
+        nsCOMPtr<imgIContainer> imageContainer(image::ImageOps::CreateFromDrawable(drawable)); 
+
+        nsCOMPtr<nsISupportsInterfacePointer>
+          imgPtr(do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv));
+
+        rv = imgPtr->SetData(imageContainer);
+        NS_ENSURE_SUCCESS(rv, true);
+
+        trans->SetTransferData(item.flavor().get(), imgPtr, sizeof(nsISupports*));
+      }
+    }
+
+    trans->SetIsPrivateData(aIsPrivateData);
+
+    clipboard->SetData(trans, nullptr, aWhichClipboard);
     return true;
 }
 
 bool
-ContentParent::RecvEmptyClipboard(const int32_t& whichClipboard)
+ContentParent::RecvGetClipboard(nsTArray<nsCString>&& aTypes,
+                                const int32_t& aWhichClipboard,
+                                IPCDataTransfer* aDataTransfer)
 {
     nsresult rv;
     nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
     NS_ENSURE_SUCCESS(rv, true);
 
-    clipboard->EmptyClipboard(whichClipboard);
+    nsCOMPtr<nsITransferable> trans = do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
+    NS_ENSURE_SUCCESS(rv, true);
+    trans->Init(nullptr);
+
+    for (uint32_t t = 0; t < aTypes.Length(); t++) {
+      trans->AddDataFlavor(aTypes[t].get());
+    }
+
+    clipboard->GetData(trans, aWhichClipboard);
+    nsContentUtils::TransferableToIPCTransferable(trans, aDataTransfer,
+                                                  nullptr, this);
+    return true;
+}
+
+bool
+ContentParent::RecvEmptyClipboard(const int32_t& aWhichClipboard)
+{
+    nsresult rv;
+    nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
+    NS_ENSURE_SUCCESS(rv, true);
+
+    clipboard->EmptyClipboard(aWhichClipboard);
 
     return true;
 }
 
 bool
-ContentParent::RecvClipboardHasText(const int32_t& whichClipboard, bool* hasText)
+ContentParent::RecvClipboardHasType(nsTArray<nsCString>&& aTypes,
+                                    const int32_t& aWhichClipboard,
+                                    bool* aHasType)
 {
     nsresult rv;
     nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
     NS_ENSURE_SUCCESS(rv, true);
 
-    clipboard->HasDataMatchingFlavors(sClipboardTextFlavors, 1,
-                                      whichClipboard, hasText);
+    const char** typesChrs = new const char *[aTypes.Length()];
+    for (uint32_t t = 0; t < aTypes.Length(); t++) {
+      typesChrs[t] = aTypes[t].get();
+    }
+
+    clipboard->HasDataMatchingFlavors(typesChrs, aTypes.Length(),
+                                      aWhichClipboard, aHasType);
+
+    delete [] typesChrs;
     return true;
 }
 
 bool
 ContentParent::RecvGetSystemColors(const uint32_t& colorsCount, InfallibleTArray<uint32_t>* colors)
 {
 #ifdef MOZ_WIDGET_ANDROID
     NS_ASSERTION(AndroidBridge::Bridge() != nullptr, "AndroidBridge is not available");
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -655,22 +655,26 @@ private:
     virtual bool DeallocPSpeechSynthesisParent(PSpeechSynthesisParent* aActor) override;
     virtual bool RecvPSpeechSynthesisConstructor(PSpeechSynthesisParent* aActor) override;
 
     virtual bool RecvReadPrefsArray(InfallibleTArray<PrefSetting>* aPrefs) override;
     virtual bool RecvReadFontList(InfallibleTArray<FontListEntry>* retValue) override;
 
     virtual bool RecvReadPermissions(InfallibleTArray<IPC::Permission>* aPermissions) override;
 
-    virtual bool RecvSetClipboardText(const nsString& text,
-                                      const bool& isPrivateData,
-                                      const int32_t& whichClipboard) override;
-    virtual bool RecvGetClipboardText(const int32_t& whichClipboard, nsString* text) override;
-    virtual bool RecvEmptyClipboard(const int32_t& whichClipboard) override;
-    virtual bool RecvClipboardHasText(const int32_t& whichClipboard, bool* hasText) override;
+    virtual bool RecvSetClipboard(const IPCDataTransfer& aDataTransfer,
+                                  const bool& aIsPrivateData,
+                                  const int32_t& aWhichClipboard) override;
+    virtual bool RecvGetClipboard(nsTArray<nsCString>&& aTypes,
+                                  const int32_t& aWhichClipboard,
+                                  IPCDataTransfer* aDataTransfer) override;
+    virtual bool RecvEmptyClipboard(const int32_t& aWhichClipboard) override;
+    virtual bool RecvClipboardHasType(nsTArray<nsCString>&& aTypes,
+                                      const int32_t& aWhichClipboard,
+                                      bool* aHasType) override;
 
     virtual bool RecvGetSystemColors(const uint32_t& colorsCount,
                                      InfallibleTArray<uint32_t>* colors) override;
     virtual bool RecvGetIconForExtension(const nsCString& aFileExt,
                                          const uint32_t& aIconSize,
                                          InfallibleTArray<uint8_t>* bits) override;
     virtual bool RecvGetShowPasswordSetting(bool* showPassword) override;
 
--- a/dom/ipc/DOMTypes.ipdlh
+++ b/dom/ipc/DOMTypes.ipdlh
@@ -132,23 +132,34 @@ struct ParentBlobConstructorParams
 union BlobConstructorParams
 {
   ChildBlobConstructorParams;
   ParentBlobConstructorParams;
 };
 
 union IPCDataTransferData
 {
-  nsString;
-  PBlob;
+  nsString;  // text
+  nsCString; // images
+  PBlob;     // files
+};
+
+struct IPCDataTransferImage
+{
+  uint32_t width;
+  uint32_t height;
+  uint32_t stride;
+  uint8_t format;
 };
 
 struct IPCDataTransferItem
 {
   nsCString flavor;
+  // The image details are only used when transferring images.
+  IPCDataTransferImage imageDetails;
   IPCDataTransferData data;
 };
 
 struct IPCDataTransfer
 {
   IPCDataTransferItem[] items;
 };
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -771,22 +771,32 @@ parent:
     ConsoleMessage(nsString message);
     ScriptError(nsString message, nsString sourceName, nsString sourceLine,
                 uint32_t lineNumber, uint32_t colNumber, uint32_t flags,
                 nsCString category);
 
     // nsIPermissionManager messages
     sync ReadPermissions() returns (Permission[] permissions);
 
-    SetClipboardText(nsString text, bool isPrivateData, int32_t whichClipboard);
-    sync GetClipboardText(int32_t whichClipboard)
-        returns (nsString text);
-    EmptyClipboard(int32_t whichClipboard);
-    sync ClipboardHasText(int32_t whichClipboard)
-        returns (bool hasText);
+    // Places the items within dataTransfer on the clipboard.
+    SetClipboard(IPCDataTransfer aDataTransfer,
+                 bool aIsPrivateData,
+                 int32_t aWhichClipboard);
+
+    // Given a list of supported types, returns the clipboard data for the
+    // first type that matches.
+    sync GetClipboard(nsCString[] aTypes, int32_t aWhichClipboard)
+        returns (IPCDataTransfer dataTransfer);
+
+    // Clears the clipboard.
+    EmptyClipboard(int32_t aWhichClipboard);
+
+    // Returns true if data of one of the specified types is on the clipboard.
+    sync ClipboardHasType(nsCString[] aTypes, int32_t aWhichClipboard)
+        returns (bool hasType);
 
     sync GetSystemColors(uint32_t colorsCount)
         returns (uint32_t[] colors);
 
     sync GetIconForExtension(nsCString aFileExt, uint32_t aIconSize)
         returns (uint8_t[] bits);
 
     sync GetShowPasswordSetting()
--- a/widget/nsClipboardProxy.cpp
+++ b/widget/nsClipboardProxy.cpp
@@ -3,90 +3,140 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/ContentChild.h"
 #include "nsClipboardProxy.h"
 #include "nsISupportsPrimitives.h"
 #include "nsCOMPtr.h"
 #include "nsComponentManagerUtils.h"
 #include "nsXULAppAPI.h"
+#include "nsContentUtils.h"
+#include "nsStringStream.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 NS_IMPL_ISUPPORTS(nsClipboardProxy, nsIClipboard, nsIClipboardProxy)
 
 nsClipboardProxy::nsClipboardProxy()
   : mClipboardCaps(false, false)
 {
 }
 
 NS_IMETHODIMP
 nsClipboardProxy::SetData(nsITransferable *aTransferable,
                           nsIClipboardOwner *anOwner, int32_t aWhichClipboard)
 {
-  nsCOMPtr<nsISupports> tmp;
-  uint32_t len;
-  nsresult rv  = aTransferable->GetTransferData(kUnicodeMime, getter_AddRefs(tmp),
-                                                &len);
-  NS_ENSURE_SUCCESS(rv, rv);
-  nsCOMPtr<nsISupportsString> supportsString = do_QueryInterface(tmp);
-  // No support for non-text data
-  NS_ENSURE_TRUE(supportsString, NS_ERROR_NOT_IMPLEMENTED);
-  nsAutoString buffer;
-  supportsString->GetData(buffer);
+  ContentChild* child = ContentChild::GetSingleton();
+
+  IPCDataTransfer ipcDataTransfer;
+  nsContentUtils::TransferableToIPCTransferable(aTransferable, &ipcDataTransfer,
+                                                child, nullptr);
 
   bool isPrivateData = false;
   aTransferable->GetIsPrivateData(&isPrivateData);
-  ContentChild::GetSingleton()->SendSetClipboardText(buffer, isPrivateData,
-                                                     aWhichClipboard);
+  child->SendSetClipboard(ipcDataTransfer, isPrivateData, aWhichClipboard);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsClipboardProxy::GetData(nsITransferable *aTransferable, int32_t aWhichClipboard)
 {
-  nsAutoString buffer;
-  ContentChild::GetSingleton()->SendGetClipboardText(aWhichClipboard, &buffer);
+   nsTArray<nsCString> types;
+  
+  nsCOMPtr<nsISupportsArray> flavorList;
+  aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
+  if (flavorList) {
+    uint32_t flavorCount = 0;
+    flavorList->Count(&flavorCount);
+    for (uint32_t j = 0; j < flavorCount; ++j) {
+      nsCOMPtr<nsISupportsCString> flavor = do_QueryElementAt(flavorList, j);
+      if (flavor) {
+        nsAutoCString flavorStr;
+        flavor->GetData(flavorStr);
+        if (flavorStr.Length()) {
+          types.AppendElement(flavorStr);
+        }
+      }
+    }
+  }
 
   nsresult rv;
-  nsCOMPtr<nsISupportsString> dataWrapper =
-    do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
+  IPCDataTransfer dataTransfer;
+  ContentChild::GetSingleton()->SendGetClipboard(types, aWhichClipboard, &dataTransfer);
+
+  auto& items = dataTransfer.items();
+  for (uint32_t j = 0; j < items.Length(); ++j) {
+    const IPCDataTransferItem& item = items[j];
 
-  rv = dataWrapper->SetData(buffer);
-  NS_ENSURE_SUCCESS(rv, rv);
+    if (item.data().type() == IPCDataTransferData::TnsString) {
+      nsCOMPtr<nsISupportsString> dataWrapper =
+        do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      nsString data = item.data().get_nsString();
+      rv = dataWrapper->SetData(data);
+      NS_ENSURE_SUCCESS(rv, rv);
 
-  // If our data flavor has already been added, this will fail. But we don't care
-  aTransferable->AddDataFlavor(kUnicodeMime);
+      rv = aTransferable->SetTransferData(item.flavor().get(), dataWrapper,
+                                          data.Length() * sizeof(char16_t));
+      NS_ENSURE_SUCCESS(rv, rv);
+    } else if (item.data().type() == IPCDataTransferData::TnsCString) {
+      // If this is an image, convert it into an nsIInputStream.
+      nsCString flavor = item.flavor();
+      if (flavor.EqualsLiteral(kJPEGImageMime) ||
+          flavor.EqualsLiteral(kJPGImageMime) ||
+          flavor.EqualsLiteral(kPNGImageMime) ||
+          flavor.EqualsLiteral(kGIFImageMime)) {
+        nsCOMPtr<nsIInputStream> stream;
+        NS_NewCStringInputStream(getter_AddRefs(stream), item.data().get_nsCString());
 
-  nsCOMPtr<nsISupports> nsisupportsDataWrapper =
-    do_QueryInterface(dataWrapper);
-  rv = aTransferable->SetTransferData(kUnicodeMime, nsisupportsDataWrapper,
-                                      buffer.Length() * sizeof(char16_t));
-  NS_ENSURE_SUCCESS(rv, rv);
+        rv = aTransferable->SetTransferData(flavor.get(), stream, sizeof(nsISupports*));
+        NS_ENSURE_SUCCESS(rv, rv);
+      } else if (flavor.EqualsLiteral(kNativeHTMLMime)) {
+        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);
+
+        rv = aTransferable->SetTransferData(item.flavor().get(), dataWrapper,
+                                            data.Length());
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+    }
+  }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsClipboardProxy::EmptyClipboard(int32_t aWhichClipboard)
 {
   ContentChild::GetSingleton()->SendEmptyClipboard(aWhichClipboard);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsClipboardProxy::HasDataMatchingFlavors(const char **aFlavorList,
-                                    uint32_t aLength, int32_t aWhichClipboard,
-                                    bool *aHasText)
+                                         uint32_t aLength, int32_t aWhichClipboard,
+                                         bool *aHasType)
 {
-  *aHasText = false;
-  ContentChild::GetSingleton()->SendClipboardHasText(aWhichClipboard, aHasText);
+  *aHasType = false;
+
+  nsTArray<nsCString> types;
+  for (uint32_t j = 0; j < aLength; ++j) {
+    types.AppendElement(nsDependentCString(aFlavorList[j]));
+  }
+
+  ContentChild::GetSingleton()->SendClipboardHasType(types, aWhichClipboard, aHasType);
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsClipboardProxy::SupportsSelectionClipboard(bool *aIsSupported)
 {
   *aIsSupported = mClipboardCaps.supportsSelectionClipboard();
   return NS_OK;
--- a/widget/nsDragServiceProxy.cpp
+++ b/widget/nsDragServiceProxy.cpp
@@ -53,40 +53,29 @@ nsDragServiceProxy::InvokeDragSession(ns
     nsPresContext* pc;
     mozilla::RefPtr<mozilla::gfx::SourceSurface> surface;
     DrawDrag(mSourceNode, aRegion, mScreenX, mScreenY,
              &dragRect, &surface, &pc);
 
     if (surface) {
       mozilla::RefPtr<mozilla::gfx::DataSourceSurface> dataSurface =
         surface->GetDataSurface();
-      mozilla::gfx::DataSourceSurface::MappedSurface map;
-      dataSurface->Map(mozilla::gfx::DataSourceSurface::MapType::READ, &map);
       mozilla::gfx::IntSize size = dataSurface->GetSize();
-      mozilla::CheckedInt32 requiredBytes =
-        mozilla::CheckedInt32(map.mStride) * mozilla::CheckedInt32(size.height);
-      size_t maxBufLen = requiredBytes.isValid() ? requiredBytes.value() : 0;
-      mozilla::gfx::SurfaceFormat format = dataSurface->GetFormat();
-      // Surface data handling is totally nuts. This is the magic one needs to
-      // know to access the data.
-      size_t bufLen =
-        maxBufLen - map.mStride + (size.width * BytesPerPixel(format));
 
-      // nsDependentCString wants null-terminated string.
-      mozilla::UniquePtr<char[]> dragImageData(new char[maxBufLen + 1]);
-      memcpy(dragImageData.get(), reinterpret_cast<char*>(map.mData), bufLen);
-      memset(dragImageData.get() + bufLen, 0, maxBufLen - bufLen + 1);
-      nsDependentCString dragImage(dragImageData.get(), maxBufLen);
+      size_t length;
+      int32_t stride;
+      mozilla::UniquePtr<char[]> surfaceData =
+        nsContentUtils::GetSurfaceData(dataSurface, &length, &stride);
+      nsDependentCString dragImage(surfaceData.get(), length);
 
       mozilla::unused <<
         child->SendInvokeDragSession(dataTransfers, aActionType, dragImage,
-                                     size.width, size.height, map.mStride,
-                                     static_cast<uint8_t>(format),
+                                     size.width, size.height, stride,
+                                     static_cast<uint8_t>(dataSurface->GetFormat()),
                                      dragRect.x, dragRect.y);
-      dataSurface->Unmap();
       StartDragSession();
       return NS_OK;
     }
   }
 
   mozilla::unused << child->SendInvokeDragSession(dataTransfers, aActionType,
                                                   nsCString(),
                                                   0, 0, 0, 0, 0, 0);