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 271021 802512ee57aec9d975ab078c48b9f37956012bc6
parent 271020 ffd1b16f058b7ddb85f7f003a993fb730d7d2016
child 271022 e812687a81cdc72c7b3379ad96864a0ce380a003
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1071562
milestone40.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 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);