Bug 1169268 - Don't crash when pasting files. r=ndeakin
authorBlake Kaplan <mrbkap@gmail.com>
Tue, 27 Oct 2015 14:41:58 -0700
changeset 303261 36fd2c273344330541b951543bd2c5951c798630
parent 303260 c6fa796af5487f5fafdef19f0b436102439d80d6
child 303262 5155bb5301f4138e28ab1df547e755f9afe01edf
push id5392
push userraliiev@mozilla.com
push dateMon, 14 Dec 2015 20:08:23 +0000
treeherdermozilla-beta@16ce8562a975 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersndeakin
bugs1169268
milestone44.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 1169268 - Don't crash when pasting files. r=ndeakin
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/ipc/ContentParent.cpp
editor/libeditor/nsHTMLDataTransfer.cpp
widget/nsClipboardProxy.cpp
widget/nsDragServiceProxy.cpp
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -7367,36 +7367,94 @@ nsContentUtils::CallOnAllRemoteChildren(
       CallOnAllRemoteChildren(windowMM, aCallback, aArg);
     }
   }
 }
 
 void
 nsContentUtils::TransferablesToIPCTransferables(nsISupportsArray* aTransferables,
                                                 nsTArray<IPCDataTransfer>& aIPC,
+                                                bool aInSyncMessage,
                                                 mozilla::dom::nsIContentChild* aChild,
                                                 mozilla::dom::nsIContentParent* aParent)
 {
   aIPC.Clear();
   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> transferable(do_QueryInterface(genericItem));
-      TransferableToIPCTransferable(transferable, dt, aChild, aParent);
-    }
-  }
+      TransferableToIPCTransferable(transferable, dt, aInSyncMessage, aChild, aParent);
+    }
+  }
+}
+
+nsresult
+nsContentUtils::SlurpFileToString(nsIFile* aFile, nsACString& aString)
+{
+  aString.Truncate();
+
+  nsCOMPtr<nsIURI> fileURI;
+  nsresult rv = NS_NewFileURI(getter_AddRefs(fileURI), aFile);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIChannel> channel;
+  rv = NS_NewChannel(getter_AddRefs(channel),
+                     fileURI,
+                     nsContentUtils::GetSystemPrincipal(),
+                     nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+                     nsIContentPolicy::TYPE_OTHER);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIInputStream> stream;
+  rv = channel->Open2(getter_AddRefs(stream));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  rv = NS_ConsumeStream(stream, UINT32_MAX, aString);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  rv = stream->Close();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+bool
+nsContentUtils::IsFileImage(nsIFile* aFile, nsACString& aType)
+{
+  nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1");
+  if (!mime) {
+    return false;
+  }
+
+  nsresult rv = mime->GetTypeFromFile(aFile, aType);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  return StringBeginsWith(aType, NS_LITERAL_CSTRING("image/"));
 }
 
 void
 nsContentUtils::TransferableToIPCTransferable(nsITransferable* aTransferable,
                                               IPCDataTransfer* aIPCDataTransfer,
+                                              bool aInSyncMessage,
                                               mozilla::dom::nsIContentChild* aChild,
                                               mozilla::dom::nsIContentParent* aParent)
 {
   MOZ_ASSERT((aChild && !aParent) || (!aChild && aParent));
 
   if (aTransferable) {
     nsCOMPtr<nsISupportsArray> flavorList;
     aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
@@ -7460,17 +7518,17 @@ nsContentUtils::TransferableToIPCTransfe
                               imgIContainer::FLAG_SYNC_DECODE);
             if (surface) {
               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;
@@ -7480,24 +7538,44 @@ nsContentUtils::TransferableToIPCTransfe
 
             continue;
           }
 
           // Otherwise, handle this as a file.
           nsCOMPtr<BlobImpl> blobImpl;
           nsCOMPtr<nsIFile> file = do_QueryInterface(data);
           if (file) {
+            // If we can send this over as a blob, do so. Otherwise, we're
+            // responding to a sync message and the child can't process the blob
+            // constructor before processing our response, which would crash. In
+            // that case, hope that the caller is nsClipboardProxy::GetData,
+            // called from editor and send over images as raw data.
+            if (aInSyncMessage) {
+              nsAutoCString type;
+              if (IsFileImage(file, type)) {
+                IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+                item->flavor() = type;
+                SlurpFileToString(file, item->data());
+              }
+
+              continue;
+            }
+
             blobImpl = new BlobImplFile(file, false);
             ErrorResult rv;
             // Ensure that file data is cached no that the content process
             // has this data available to it when passed over:
             blobImpl->GetSize(rv);
             blobImpl->GetLastModified(rv);
             blobImpl->LookupAndCacheIsDirectory();
           } else {
+            if (aInSyncMessage) {
+              // Can't do anything.
+              continue;
+            }
             blobImpl = do_QueryInterface(data);
           }
           if (blobImpl) {
             IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
             item->flavor() = nsCString(flavorStr);
             if (aChild) {
               item->data() =
                 mozilla::dom::BlobChild::GetOrCreate(aChild,
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -2403,23 +2403,40 @@ public:
   /*
    * Call the given callback on all remote children of the given top-level
    * window.
    */
   static void CallOnAllRemoteChildren(nsIDOMWindow* aWindow,
                                       CallOnRemoteChildFunction aCallback,
                                       void* aArg);
 
+  /**
+   * Given an nsIFile, attempts to read it into aString.
+   *
+   * Note: Use sparingly! This causes main-thread I/O, which causes jank and all
+   * other bad things.
+   */
+  static nsresult SlurpFileToString(nsIFile* aFile, nsACString& aString);
+
+  /**
+   * Returns true if the mime service thinks this file contains an image.
+   *
+   * The content type is returned in aType.
+   */
+  static bool IsFileImage(nsIFile* aFile, nsACString& aType);
+
   static void TransferablesToIPCTransferables(nsISupportsArray* aTransferables,
                                               nsTArray<mozilla::dom::IPCDataTransfer>& aIPC,
+                                              bool aInSyncMessage,
                                               mozilla::dom::nsIContentChild* aChild,
                                               mozilla::dom::nsIContentParent* aParent);
 
   static void TransferableToIPCTransferable(nsITransferable* aTransferable,
                                             mozilla::dom::IPCDataTransfer* aIPCDataTransfer,
+                                            bool aInSyncMessage,
                                             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,
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2823,17 +2823,17 @@ ContentParent::RecvGetClipboard(nsTArray
     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);
+                                                  true, nullptr, this);
     return true;
 }
 
 bool
 ContentParent::RecvEmptyClipboard(const int32_t& aWhichClipboard)
 {
     nsresult rv;
     nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
@@ -5267,16 +5267,17 @@ ContentParent::MaybeInvokeDragSession(Ta
       // needed.
       transfer->FillAllExternalData();
       nsCOMPtr<nsILoadContext> lc = aParent ?
                                      aParent->GetLoadContext() : nullptr;
       nsCOMPtr<nsISupportsArray> transferables =
         transfer->GetTransferables(lc);
       nsContentUtils::TransferablesToIPCTransferables(transferables,
                                                       dataTransfers,
+                                                      false,
                                                       nullptr,
                                                       this);
       uint32_t action;
       session->GetDragAction(&action);
       mozilla::unused << SendInvokeDragSession(dataTransfers, action);
     }
   }
 }
--- a/editor/libeditor/nsHTMLDataTransfer.cpp
+++ b/editor/libeditor/nsHTMLDataTransfer.cpp
@@ -1016,73 +1016,62 @@ nsHTMLEditor::ParseCFHTML(nsCString & aC
 nsresult nsHTMLEditor::InsertObject(const char* aType, nsISupports* aObject, bool aIsSafe,
                                     nsIDOMDocument *aSourceDoc,
                                     nsIDOMNode *aDestinationNode,
                                     int32_t aDestOffset,
                                     bool aDoDeleteSelection)
 {
   nsresult rv;
 
-  const char* type = aType;
+  nsAutoCString type(aType);
 
   // Check to see if we can insert an image file
   bool insertAsImage = false;
-  nsCOMPtr<nsIURI> fileURI;
-  if (0 == nsCRT::strcmp(type, kFileMime))
+  nsCOMPtr<nsIFile> fileObj;
+  if (type.EqualsLiteral(kFileMime))
   {
-    nsCOMPtr<nsIFile> fileObj = do_QueryInterface(aObject);
+    fileObj = do_QueryInterface(aObject);
     if (fileObj)
     {
-      rv = NS_NewFileURI(getter_AddRefs(fileURI), fileObj);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1");
-      NS_ENSURE_TRUE(mime, NS_ERROR_FAILURE);
-      nsAutoCString contentType;
-      rv = mime->GetTypeFromFile(fileObj, contentType);
-      NS_ENSURE_SUCCESS(rv, rv);
-
       // Accept any image type fed to us
-      if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("image/"))) {
+      if (nsContentUtils::IsFileImage(fileObj, type))
+      {
         insertAsImage = true;
-        type = contentType.get();
+      }
+      else
+      {
+        // Reset type.
+        type.AssignLiteral(kFileMime);
       }
     }
   }
 
-  if (0 == nsCRT::strcmp(type, kJPEGImageMime) ||
-      0 == nsCRT::strcmp(type, kJPGImageMime) ||
-      0 == nsCRT::strcmp(type, kPNGImageMime) ||
-      0 == nsCRT::strcmp(type, kGIFImageMime) ||
+  if (type.EqualsLiteral(kJPEGImageMime) ||
+      type.EqualsLiteral(kJPGImageMime) ||
+      type.EqualsLiteral(kPNGImageMime) ||
+      type.EqualsLiteral(kGIFImageMime) ||
       insertAsImage)
   {
-    nsCOMPtr<nsIInputStream> imageStream;
-    if (insertAsImage) {
-      NS_ASSERTION(fileURI, "The file URI should be retrieved earlier");
-
-      nsCOMPtr<nsIChannel> channel;
-      rv = NS_NewChannel(getter_AddRefs(channel),
-                         fileURI,
-                         nsContentUtils::GetSystemPrincipal(),
-                         nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
-                         nsIContentPolicy::TYPE_OTHER);
+    nsCString imageData;
+    if (insertAsImage)
+    {
+      rv = nsContentUtils::SlurpFileToString(fileObj, imageData);
       NS_ENSURE_SUCCESS(rv, rv);
-      rv = channel->Open2(getter_AddRefs(imageStream));
-      NS_ENSURE_SUCCESS(rv, rv);
-    } else {
-      imageStream = do_QueryInterface(aObject);
+    }
+    else
+    {
+      nsCOMPtr<nsIInputStream> imageStream = do_QueryInterface(aObject);
       NS_ENSURE_TRUE(imageStream, NS_ERROR_FAILURE);
-    }
 
-    nsCString imageData;
-    rv = NS_ConsumeStream(imageStream, UINT32_MAX, imageData);
-    NS_ENSURE_SUCCESS(rv, rv);
+      rv = NS_ConsumeStream(imageStream, UINT32_MAX, imageData);
+      NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = imageStream->Close();
-    NS_ENSURE_SUCCESS(rv, rv);
+      rv = imageStream->Close();
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
 
     nsAutoCString data64;
     rv = Base64Encode(imageData, data64);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsAutoString stuffToPaste;
     stuffToPaste.AssignLiteral("<IMG src=\"data:");
     AppendUTF8toUTF16(type, stuffToPaste);
--- a/widget/nsClipboardProxy.cpp
+++ b/widget/nsClipboardProxy.cpp
@@ -24,17 +24,17 @@ nsClipboardProxy::nsClipboardProxy()
 NS_IMETHODIMP
 nsClipboardProxy::SetData(nsITransferable *aTransferable,
                           nsIClipboardOwner *anOwner, int32_t aWhichClipboard)
 {
   ContentChild* child = ContentChild::GetSingleton();
 
   IPCDataTransfer ipcDataTransfer;
   nsContentUtils::TransferableToIPCTransferable(aTransferable, &ipcDataTransfer,
-                                                child, nullptr);
+                                                false, child, nullptr);
 
   bool isPrivateData = false;
   aTransferable->GetIsPrivateData(&isPrivateData);
   child->SendSetClipboard(ipcDataTransfer, isPrivateData, aWhichClipboard);
 
   return NS_OK;
 }
 
--- a/widget/nsDragServiceProxy.cpp
+++ b/widget/nsDragServiceProxy.cpp
@@ -31,16 +31,17 @@ nsDragServiceProxy::InvokeDragSessionImp
   nsCOMPtr<nsIDocument> doc = do_QueryInterface(mSourceDocument);
   NS_ENSURE_STATE(doc->GetDocShell());
   mozilla::dom::TabChild* child =
     mozilla::dom::TabChild::GetFrom(doc->GetDocShell());
   NS_ENSURE_STATE(child);
   nsTArray<mozilla::dom::IPCDataTransfer> dataTransfers;
   nsContentUtils::TransferablesToIPCTransferables(aArrayTransferables,
                                                   dataTransfers,
+                                                  false,
                                                   child->Manager(),
                                                   nullptr);
 
   if (mHasImage || mSelection) {
     nsIntRect dragRect;
     nsPresContext* pc;
     RefPtr<mozilla::gfx::SourceSurface> surface;
     DrawDrag(mSourceNode, aRegion, mScreenX, mScreenY,