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
--- 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