Bug 1472338 - Part 1: Read data from clipboard asynchronously, r=enndeakin,mccr8 draft
authorAnny Gakhokidze <agakhokidze@mozilla.com>
Sun, 01 Jul 2018 11:20:01 -0400
changeset 828138 f50cd14ed1de055123e05b184dccab77bb9149f7
parent 828137 33ad4d845715c7238d60c48fd5f6bb9586a3fcbc
child 828139 fdbf727b02e93b5641a3adbf011af19c34c2e260
child 828141 9d47c2bcaea63b199e003de8ea430a6be6b5b6b4
child 828142 496acc6af58240e8fdf88c0b0ef5739d69af9300
push id118639
push userbmo:agakhokidze@mozilla.com
push dateFri, 10 Aug 2018 04:45:40 +0000
reviewersenndeakin, mccr8
bugs1472338
milestone63.0a1
Bug 1472338 - Part 1: Read data from clipboard asynchronously, r=enndeakin,mccr8 Improve performance of Clipboard::Read and Clipboard::ReadText by reading data from clipboard asynchronously. MozReview-Commit-ID: 3KyFKTZPJw1
dom/events/Clipboard.cpp
dom/events/Clipboard.h
dom/events/DataTransfer.cpp
dom/events/DataTransfer.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
--- a/dom/events/Clipboard.cpp
+++ b/dom/events/Clipboard.cpp
@@ -40,58 +40,109 @@ Clipboard::ReadHelper(JSContext* aCx, ns
   // Create a new promise
   RefPtr<Promise> p = dom::Promise::Create(GetOwnerGlobal(), aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
   // We want to disable security check for automated tests that have the pref
   //  dom.events.testing.asyncClipboard set to true
-  if (!IsTestingPrefEnabled() && !nsContentUtils::PrincipalHasPermission(&aSubjectPrincipal,
+  if (!IsTestingPrefEnabled() && !nsContentUtils::PrincipalHasPermission(
+                                                         &aSubjectPrincipal,
                                                          nsGkAtoms::clipboardRead)) {
     MOZ_LOG(GetClipboardLog(), LogLevel::Debug, ("Clipboard, ReadHelper, "
             "Don't have permissions for reading\n"));
     p->MaybeRejectWithUndefined();
     return p.forget();
   }
 
-  // Want isExternal = true in order to use the data transfer object to perform a read
-  RefPtr<DataTransfer> dataTransfer = new DataTransfer(this, ePaste, /* is external */ true,
-                                                       nsIClipboard::kGlobalClipboard);
 
   // Create a new runnable
   RefPtr<nsIRunnable> r = NS_NewRunnableFunction(
     "Clipboard::Read",
-    [p, dataTransfer, &aSubjectPrincipal, aClipboardReadType]() {
+    [this, p, &aSubjectPrincipal, aClipboardReadType]() {
       IgnoredErrorResult ier;
-      switch (aClipboardReadType) {
-        case eRead:
-          MOZ_LOG(GetClipboardLog(), LogLevel::Debug,
-                  ("Clipboard, ReadHelper, read case\n"));
-          dataTransfer->FillAllExternalData();
-          // If there are items on the clipboard, data transfer will contain those,
-          // else, data transfer will be empty and we will be resolving with an empty data transfer
-          p->MaybeResolve(dataTransfer);
-          break;
-        case eReadText:
-          MOZ_LOG(GetClipboardLog(), LogLevel::Debug,
-                  ("Clipboard, ReadHelper, read text case\n"));
-          nsAutoString str;
-          dataTransfer->GetData(NS_LITERAL_STRING(kTextMime), str, aSubjectPrincipal, ier);
-          // Either resolve with a string extracted from data transfer item
-          // or resolve with an empty string if nothing was found
-          p->MaybeResolve(str);
-          break;
+      if (XRE_IsContentProcess()) {
+        RefPtr<DataTransfer> dataTransfer = new DataTransfer(this, ePaste, /* is external */ false,
+                                                             nsIClipboard::kGlobalClipboard);
+        MOZ_LOG(GetClipboardLog(), LogLevel::Debug,
+                ("Clipboard, Content Process case\n"));
+        auto contentChild = ContentChild::GetSingleton();
+        auto promise = contentChild->SendGetClipboardAsync(
+                                nsIClipboard::kGlobalClipboard,
+                                /* is plain text only */ aClipboardReadType == eReadText);
+        promise->Then(AbstractThread::MainThread(),
+                      __func__,
+                      /* success */ [dataTransfer, p, aClipboardReadType,
+                                    &aSubjectPrincipal](IPCDataTransfer ipcDataTransfer) {
+                        DataTransfer::ConvertFromIPCDataTransfer(ipcDataTransfer,
+                                                                 dataTransfer);
+                        ProcessDataTransfer(dataTransfer, p, aClipboardReadType,
+                                            aSubjectPrincipal, /* need to fill */ false);
+                      },
+                      /* failure */ [p](mozilla::ipc::ResponseRejectReason aReason) {
+                        p->MaybeRejectWithUndefined();
+                      }
+      );
+    } else {
+      // If we are not content process, use data transfer to read the clipboard
+      // by setting isExternal = true
+      RefPtr<DataTransfer> dataTransfer = new DataTransfer(this, ePaste,
+                                                           /* is external */ true,
+                                                           nsIClipboard::kGlobalClipboard);
+      MOZ_LOG(GetClipboardLog(), LogLevel::Debug,
+              ("Clipboard, Chrome Process case\n"));
+      ProcessDataTransfer(dataTransfer, p, aClipboardReadType, aSubjectPrincipal,
+                          /* need to fill */ true);
       }
-    });
-  // Dispatch the runnable
+     });
+   // Dispatch the runnable
   GetParentObject()->Dispatch(TaskCategory::Other, r.forget());
   return p.forget();
 }
 
+
+/* static */
+void
+Clipboard::ProcessDataTransfer(RefPtr<DataTransfer> aDataTransfer,
+                               RefPtr<Promise> aPromise,
+                               ClipboardReadType aClipboardReadType,
+                               nsIPrincipal& aSubjectPrincipal,
+                               bool aNeedToFill)
+{
+  IgnoredErrorResult ier;
+  switch (aClipboardReadType) {
+    case eRead:
+      MOZ_LOG(GetClipboardLog(), LogLevel::Debug,
+              ("Clipboard, ReadHelper, read case\n"));
+      if (aNeedToFill) {
+        aDataTransfer->FillAllExternalData();
+      }
+      // If there are items on the clipboard, data transfer will contain those,
+      // else, data transfer will be empty and we will be resolving with an empty data transfer
+      aPromise->MaybeResolve(aDataTransfer);
+      break;
+    case eReadText:
+      MOZ_LOG(GetClipboardLog(), LogLevel::Debug,
+              ("Clipboard, ReadHelper, read text case\n"));
+      nsAutoString str;
+      // If we haven't filled the data transfer with the items from the clipboard yet,
+      // then DataTransfer::GetData will get data from the clipboard
+      // and retrieve the item in the desired format if it exists.
+      // Otherwise, if our data transfer is filled with items from the clipboard
+      // and we call DataTransfer::GetData, it will simply try to retrieve the
+      // item in the desired format among the items it already has.
+      aDataTransfer->GetData(NS_LITERAL_STRING(kTextMime), str, aSubjectPrincipal, ier);
+      // Either resolve with a string extracted from data transfer item
+      // or resolve with an empty string if nothing was found
+      aPromise->MaybeResolve(str);
+      break;
+  }
+}
+
 already_AddRefed<Promise>
 Clipboard::Read(JSContext* aCx, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv)
 {
   return ReadHelper(aCx, aSubjectPrincipal, eRead, aRv);
 }
 
 already_AddRefed<Promise>
 Clipboard::ReadText(JSContext* aCx, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv)
--- a/dom/events/Clipboard.h
+++ b/dom/events/Clipboard.h
@@ -53,16 +53,22 @@ private:
   // Checks if dom.events.testing.asyncClipboard pref is enabled.
   // The aforementioned pref allows automated tests to bypass the security checks when writing to
   //  or reading from the clipboard.
   bool IsTestingPrefEnabled();
 
   already_AddRefed<Promise> ReadHelper(JSContext* aCx, nsIPrincipal& aSubjectPrincipal,
                                        ClipboardReadType aClipboardReadType, ErrorResult& aRv);
 
+  // If necessary, fill the data transfer with data from the clipboard and
+  // resolve a promise with the appropriate object based on aClipboardReadType
+  static void ProcessDataTransfer(RefPtr<DataTransfer> aDataTransfer, RefPtr<Promise> aPromise,
+                                 ClipboardReadType aClipboardReadType, nsIPrincipal& aSubjectPrincipal,
+                                 bool aNeedToFill);
+
   ~Clipboard();
 
 
 };
 
 } // namespace dom
 } // namespace mozilla
 #endif // mozilla_dom_Clipboard_h_
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -34,16 +34,17 @@
 #include "mozilla/dom/DataTransferItemList.h"
 #include "mozilla/dom/Directory.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/FileList.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/OSFileSystem.h"
 #include "mozilla/dom/Promise.h"
 #include "nsNetUtil.h"
+#include "mozilla/dom/IPCBlobUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(DataTransfer)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DataTransfer)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
@@ -1518,10 +1519,52 @@ DataTransfer::SetMode(DataTransfer::Mode
 {
   if (!PrefProtected() && aMode == Mode::Protected) {
     mMode = Mode::ReadOnly;
   } else {
     mMode = aMode;
   }
 }
 
+/* static */
+void
+DataTransfer::ConvertFromIPCDataTransfer(const IPCDataTransfer& aIpcDataTransfer,
+                                         const RefPtr<DataTransfer>& aDataTransfer)
+{
+  // Verify this method is only called from content process
+  MOZ_ASSERT(XRE_IsContentProcess());
+
+  bool hasFiles = false;
+  for (uint32_t j = 0; j < aIpcDataTransfer.items().Length(); ++j) {
+    if (aIpcDataTransfer.items()[j].data().type() == IPCDataTransferData::TIPCBlob) {
+      hasFiles = true;
+      break;
+    }
+  }
+
+  for (uint32_t j = 0; j < aIpcDataTransfer.items().Length(); ++j) {
+    const IPCDataTransferItem& item = aIpcDataTransfer.items()[j];
+    RefPtr<nsVariantCC> variant = new nsVariantCC();
+    if (item.data().type() == IPCDataTransferData::TnsString) {
+      const nsString& data = item.data().get_nsString();
+      variant->SetAsAString(data);
+    } else if (item.data().type() == IPCDataTransferData::TShmem) {
+      auto data = item.data().get_Shmem();
+      variant->SetAsACString(nsDependentCString(data.get<char>(), data.Size<char>()));
+      Unused << ContentChild::GetSingleton()->DeallocShmem(data);
+    } else if (item.data().type() == IPCDataTransferData::TIPCBlob) {
+      RefPtr<BlobImpl> blobImpl =
+        IPCBlobUtils::Deserialize(item.data().get_IPCBlob());
+      variant->SetAsISupports(blobImpl);
+    } else {
+      continue;
+    }
+
+    // We should hide this data from content if we have a file, and we aren't a file.
+    bool hidden = hasFiles && item.data().type() != IPCDataTransferData::TIPCBlob;
+    aDataTransfer->SetDataWithPrincipalFromOtherProcess(
+      NS_ConvertUTF8toUTF16(item.flavor()), variant, j,
+      nsContentUtils::GetSystemPrincipal(), hidden);
+  }
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/events/DataTransfer.h
+++ b/dom/events/DataTransfer.h
@@ -26,16 +26,17 @@ class nsITransferable;
 class nsILoadContext;
 
 namespace mozilla {
 
 class EventStateManager;
 
 namespace dom {
 
+class IPCDataTransfer;
 class DataTransferItem;
 class DataTransferItemList;
 class DOMStringList;
 class Element;
 class FileList;
 class Promise;
 template<typename T> class Optional;
 
@@ -423,16 +424,21 @@ public:
   //
   // If kFileMime is supported, then it will be placed either at
   // index 0 or at index 1 in aResult
   static void
   GetExternalClipboardFormats(const int32_t& aWhichClipboard,
                               const bool& aPlainTextOnly,
                               nsTArray<nsCString>* aResult);
 
+  // Convert filled IPCDataTransfer to DataTransfer
+  static void
+  ConvertFromIPCDataTransfer(const IPCDataTransfer& aIpcDataTransfer,
+                             const RefPtr<DataTransfer>& aDataTransfer);
+
 protected:
 
   // caches text and uri-list data formats that exist in the drag service or
   // clipboard for retrieval later.
   nsresult CacheExternalData(const char* aFormat, uint32_t aIndex,
                              nsIPrincipal* aPrincipal, bool aHidden);
 
   // caches the formats that exist in the drag service that were added by an
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2838,31 +2838,18 @@ ContentParent::RecvSetClipboard(const IP
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 ContentParent::RecvGetClipboard(nsTArray<nsCString>&& aTypes,
                                 const int32_t& aWhichClipboard,
                                 IPCDataTransfer* aDataTransfer)
 {
-  nsresult rv;
-  nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
-  NS_ENSURE_SUCCESS(rv, IPC_OK());
-
-  nsCOMPtr<nsITransferable> trans = do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
+  nsresult rv = GetDataFromClipboard(aTypes, aWhichClipboard, aDataTransfer);
   NS_ENSURE_SUCCESS(rv, IPC_OK());
-  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,
-                                                true, nullptr, this);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 ContentParent::RecvEmptyClipboard(const int32_t& aWhichClipboard)
 {
   nsresult rv;
   nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
@@ -2899,16 +2886,72 @@ ContentParent::RecvGetExternalClipboardF
                                      const bool& aPlainTextOnly,
                                      nsTArray<nsCString>* aTypes)
 {
   MOZ_ASSERT(aTypes);
   DataTransfer::GetExternalClipboardFormats(aWhichClipboard, aPlainTextOnly, aTypes);
   return IPC_OK();
 }
 
+nsresult
+ContentParent::GetDataFromClipboard(nsTArray<nsCString>& aTypes,
+                                    const int32_t& aWhichClipboard,
+                                    IPCDataTransfer* aDataTransfer)
+{
+  nsresult rv;
+  // Retrieve clipboard
+  nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // Create transferable
+  nsCOMPtr<nsITransferable> trans = do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  trans->Init(nullptr);
+
+  // Fill out flavors for transferable
+  for (uint32_t t = 0; t < aTypes.Length(); t++) {
+    trans->AddDataFlavor(aTypes[t].get());
+  }
+
+  // Get data from clipboard
+  clipboard->GetData(trans, aWhichClipboard);
+
+  // Fill IPC data transfer
+  nsContentUtils::TransferableToIPCTransferable(trans, aDataTransfer,
+                                                true, nullptr, this);
+  return NS_OK;
+}
+
+mozilla::ipc::IPCResult
+ContentParent::RecvGetClipboardAsync(const int32_t& aWhichClipboard,
+                                     const bool& aPlainTextOnly,
+                                     GetClipboardAsyncResolver&& aResolver)
+{
+  // Retrieve supported clipboard formats
+  nsTArray<nsCString> types;
+  DataTransfer::GetExternalClipboardFormats(aWhichClipboard, aPlainTextOnly, &types);
+
+  // Create IPC data transfer
+  IPCDataTransfer ipcDataTransfer;
+
+  // Fetch data from the clipboard into ipc data transfer
+  nsresult rv = GetDataFromClipboard(types, aWhichClipboard, &ipcDataTransfer);
+  if (NS_FAILED(rv)) {
+    return IPC_FAIL(this, "Clipboard: Read failed");
+  }
+
+  // Resolve the promise
+  aResolver(std::move(ipcDataTransfer));
+  return IPC_OK();
+}
+
 mozilla::ipc::IPCResult
 ContentParent::RecvPlaySound(const URIParams& aURI)
 {
   nsCOMPtr<nsIURI> soundURI = DeserializeURI(aURI);
   bool isChrome = false;
   // If the check here fails, it can only mean that this message was spoofed.
   if (!soundURI || NS_FAILED(soundURI->SchemeIs("chrome", &isChrome)) || !isChrome) {
     // PlaySound only accepts a valid chrome URI.
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -1027,16 +1027,20 @@ private:
   virtual mozilla::ipc::IPCResult RecvClipboardHasType(nsTArray<nsCString>&& aTypes,
                                                        const int32_t& aWhichClipboard,
                                                        bool* aHasType) override;
 
   virtual mozilla::ipc::IPCResult RecvGetExternalClipboardFormats(const int32_t& aWhichClipboard,
                                                                   const bool& aPlainTextOnly,
                                                                   nsTArray<nsCString>* aTypes) override;
 
+  virtual mozilla::ipc::IPCResult RecvGetClipboardAsync(const int32_t& aWhichClipboard,
+                                                        const bool& aPlainTextOnly,
+                                                        GetClipboardAsyncResolver&& aResolver) override;
+
   virtual mozilla::ipc::IPCResult RecvPlaySound(const URIParams& aURI) override;
   virtual mozilla::ipc::IPCResult RecvBeep() override;
   virtual mozilla::ipc::IPCResult RecvPlayEventSound(const uint32_t& aEventId) override;
 
   virtual mozilla::ipc::IPCResult RecvGetSystemColors(const uint32_t& colorsCount,
                                                       InfallibleTArray<uint32_t>* colors) override;
 
   virtual mozilla::ipc::IPCResult RecvGetIconForExtension(const nsCString& aFileExt,
@@ -1109,16 +1113,22 @@ private:
                                                            const uint32_t& aLineNumber,
                                                            const uint32_t& aColNumber,
                                                            const uint32_t& aFlags,
                                                            const nsCString& aCategory,
                                                            const bool& aIsFromPrivateWindow,
                                                            const ClonedMessageData& aStack) override;
 
 private:
+  // Get data from clipboard and fill out IPC data transfer
+  nsresult
+  GetDataFromClipboard(nsTArray<nsCString>& aTypes,
+                       const int32_t& aWhichClipboard,
+                       IPCDataTransfer* aDataTransfer);
+
   mozilla::ipc::IPCResult RecvScriptErrorInternal(const nsString& aMessage,
                                                   const nsString& aSourceName,
                                                   const nsString& aSourceLine,
                                                   const uint32_t& aLineNumber,
                                                   const uint32_t& aColNumber,
                                                   const uint32_t& aFlags,
                                                   const nsCString& aCategory,
                                                   const bool& aIsFromPrivateWindow,
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -876,16 +876,19 @@ parent:
     // 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);
 
     // Returns a list of formats supported by the clipboard
     sync GetExternalClipboardFormats(int32_t aWhichClipboard, bool aPlainTextOnly) returns (nsCString[] aTypes);
 
+    // Read data from the clipboard asynchronously
+    async GetClipboardAsync(int32_t aWhichClipboard, bool aPlainTextOnly) returns (IPCDataTransfer dataTransfer);
+
     // Clears the clipboard.
     async 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);
 
     // 'Play', 'Beep' and 'PlayEventSound' are the only nsISound methods used in