Backed out changeset c8561b4726a2 (bug 1852947) for causing bustage on nsClipboardProxy.cpp CLOSED TREE

This commit is contained in:
Norisz Fay 2023-11-08 23:44:50 +02:00
parent 9d4a3c9bdb
commit ced0683cec
25 changed files with 418 additions and 1417 deletions

View file

@ -78,169 +78,6 @@ static bool MaybeCreateAndDispatchMozClipboardReadPasteEvent(
Cancelable::eNo)));
}
namespace {
/**
* This is a base class for ClipboardGetCallbackForRead and
* ClipboardGetCallbackForReadText.
*/
class ClipboardGetCallback : public nsIAsyncClipboardGetCallback {
public:
explicit ClipboardGetCallback(RefPtr<Promise>&& aPromise)
: mPromise(std::move(aPromise)) {}
// This object will never be held by a cycle-collected object, so it doesn't
// need to be cycle-collected despite holding alive cycle-collected objects.
NS_DECL_ISUPPORTS
// nsIAsyncClipboardGetCallback
NS_IMETHOD OnError(nsresult aResult) override final {
MOZ_ASSERT(mPromise);
RefPtr<Promise> p(std::move(mPromise));
p->MaybeRejectWithNotAllowedError(
"Clipboard read operation is not allowed.");
return NS_OK;
}
protected:
virtual ~ClipboardGetCallback() { MOZ_ASSERT(!mPromise); };
// Not cycle-collected, because it should be nulled when the request is
// answered, rejected or aborted.
RefPtr<Promise> mPromise;
};
NS_IMPL_ISUPPORTS(ClipboardGetCallback, nsIAsyncClipboardGetCallback)
class ClipboardGetCallbackForRead final : public ClipboardGetCallback {
public:
explicit ClipboardGetCallbackForRead(nsIGlobalObject* aGlobal,
RefPtr<Promise>&& aPromise)
: ClipboardGetCallback(std::move(aPromise)), mGlobal(aGlobal) {}
// This object will never be held by a cycle-collected object, so it doesn't
// need to be cycle-collected despite holding alive cycle-collected objects.
NS_DECL_ISUPPORTS_INHERITED
// nsIAsyncClipboardGetCallback
NS_IMETHOD OnSuccess(
nsIAsyncGetClipboardData* aAsyncGetClipboardData) override {
MOZ_ASSERT(mPromise);
MOZ_ASSERT(aAsyncGetClipboardData);
nsTArray<nsCString> flavorList;
nsresult rv = aAsyncGetClipboardData->GetFlavorList(flavorList);
if (NS_FAILED(rv)) {
return OnError(rv);
}
AutoTArray<RefPtr<ClipboardItem::ItemEntry>, 3> entries;
for (const auto& format : flavorList) {
auto entry = MakeRefPtr<ClipboardItem::ItemEntry>(
mGlobal, NS_ConvertUTF8toUTF16(format));
entry->LoadDataFromSystemClipboard(aAsyncGetClipboardData);
entries.AppendElement(std::move(entry));
}
RefPtr<Promise> p(std::move(mPromise));
// We currently only support one clipboard item.
p->MaybeResolve(
AutoTArray<RefPtr<ClipboardItem>, 1>{MakeRefPtr<ClipboardItem>(
mGlobal, PresentationStyle::Unspecified, std::move(entries))});
return NS_OK;
}
protected:
~ClipboardGetCallbackForRead() = default;
nsCOMPtr<nsIGlobalObject> mGlobal;
};
NS_IMPL_ISUPPORTS_INHERITED0(ClipboardGetCallbackForRead, ClipboardGetCallback)
class ClipboardGetCallbackForReadText final
: public ClipboardGetCallback,
public nsIAsyncClipboardRequestCallback {
public:
explicit ClipboardGetCallbackForReadText(RefPtr<Promise>&& aPromise)
: ClipboardGetCallback(std::move(aPromise)) {}
// This object will never be held by a cycle-collected object, so it doesn't
// need to be cycle-collected despite holding alive cycle-collected objects.
NS_DECL_ISUPPORTS_INHERITED
// nsIAsyncClipboardGetCallback
NS_IMETHOD OnSuccess(
nsIAsyncGetClipboardData* aAsyncGetClipboardData) override {
MOZ_ASSERT(mPromise);
MOZ_ASSERT(!mTransferable);
MOZ_ASSERT(aAsyncGetClipboardData);
AutoTArray<nsCString, 3> flavors;
nsresult rv = aAsyncGetClipboardData->GetFlavorList(flavors);
if (NS_FAILED(rv)) {
return OnError(rv);
}
mTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1");
if (NS_WARN_IF(!mTransferable)) {
return OnError(NS_ERROR_UNEXPECTED);
}
mTransferable->Init(nullptr);
mTransferable->AddDataFlavor(kTextMime);
if (!flavors.Contains(kTextMime)) {
return OnComplete(NS_OK);
}
rv = aAsyncGetClipboardData->GetData(mTransferable, this);
if (NS_FAILED(rv)) {
return OnError(rv);
}
return NS_OK;
}
// nsIAsyncClipboardRequestCallback
NS_IMETHOD OnComplete(nsresult aResult) override {
MOZ_ASSERT(mPromise);
MOZ_ASSERT(mTransferable);
if (NS_FAILED(aResult)) {
return OnError(aResult);
}
nsAutoString str;
nsCOMPtr<nsISupports> data;
nsresult rv =
mTransferable->GetTransferData(kTextMime, getter_AddRefs(data));
if (!NS_WARN_IF(NS_FAILED(rv))) {
nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data);
MOZ_ASSERT(supportsstr);
if (supportsstr) {
supportsstr->GetData(str);
}
}
RefPtr<Promise> p(std::move(mPromise));
p->MaybeResolve(str);
return NS_OK;
}
protected:
~ClipboardGetCallbackForReadText() = default;
nsCOMPtr<nsITransferable> mTransferable;
};
NS_IMPL_ISUPPORTS_INHERITED(ClipboardGetCallbackForReadText,
ClipboardGetCallback,
nsIAsyncClipboardRequestCallback)
} // namespace
void Clipboard::ReadRequest::Answer() {
RefPtr<Promise> p(std::move(mPromise));
RefPtr<nsPIDOMWindowInner> owner(std::move(mOwner));
@ -253,30 +90,89 @@ void Clipboard::ReadRequest::Answer() {
return;
}
RefPtr<ClipboardGetCallback> callback;
switch (mType) {
case ReadRequestType::eRead: {
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(owner);
if (NS_WARN_IF(!global)) {
clipboardService
->AsyncHasDataMatchingFlavors(
// Mandatory data types defined in
// https://w3c.github.io/clipboard-apis/#mandatory-data-types-x
AutoTArray<nsCString, 3>{nsDependentCString(kHTMLMime),
nsDependentCString(kTextMime),
nsDependentCString(kPNGImageMime)},
nsIClipboard::kGlobalClipboard)
->Then(
GetMainThreadSerialEventTarget(), __func__,
/* resolve */
[owner, p](nsTArray<nsCString> formats) {
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(owner);
if (NS_WARN_IF(!global)) {
p->MaybeReject(NS_ERROR_UNEXPECTED);
return;
}
AutoTArray<RefPtr<ClipboardItem::ItemEntry>, 3> entries;
for (const auto& format : formats) {
nsCOMPtr<nsITransferable> trans =
do_CreateInstance("@mozilla.org/widget/transferable;1");
if (NS_WARN_IF(!trans)) {
continue;
}
trans->Init(nullptr);
trans->AddDataFlavor(format.get());
RefPtr<ClipboardItem::ItemEntry> entry =
MakeRefPtr<ClipboardItem::ItemEntry>(
global, NS_ConvertUTF8toUTF16(format));
entry->LoadDataFromSystemClipboard(*trans);
entries.AppendElement(std::move(entry));
}
// We currently only support one clipboard item.
AutoTArray<RefPtr<ClipboardItem>, 1> items;
items.AppendElement(MakeRefPtr<ClipboardItem>(
global, PresentationStyle::Unspecified,
std::move(entries)));
p->MaybeResolve(std::move(items));
},
/* reject */
[p](nsresult rv) { p->MaybeReject(rv); });
break;
}
case ReadRequestType::eReadText: {
nsCOMPtr<nsITransferable> trans =
do_CreateInstance("@mozilla.org/widget/transferable;1");
if (NS_WARN_IF(!trans)) {
p->MaybeReject(NS_ERROR_UNEXPECTED);
return;
}
callback = MakeRefPtr<ClipboardGetCallbackForRead>(global, std::move(p));
rv = clipboardService->AsyncGetData(
// Mandatory data types defined in
// https://w3c.github.io/clipboard-apis/#mandatory-data-types-x
AutoTArray<nsCString, 3>{nsDependentCString(kHTMLMime),
nsDependentCString(kTextMime),
nsDependentCString(kPNGImageMime)},
nsIClipboard::kGlobalClipboard, callback);
break;
}
case ReadRequestType::eReadText: {
callback = MakeRefPtr<ClipboardGetCallbackForReadText>(std::move(p));
rv = clipboardService->AsyncGetData(
AutoTArray<nsCString, 1>{nsDependentCString(kTextMime)},
nsIClipboard::kGlobalClipboard, callback);
trans->Init(nullptr);
trans->AddDataFlavor(kTextMime);
clipboardService->AsyncGetData(trans, nsIClipboard::kGlobalClipboard)
->Then(
GetMainThreadSerialEventTarget(), __func__,
/* resolve */
[trans, p]() {
nsCOMPtr<nsISupports> data;
nsresult rv =
trans->GetTransferData(kTextMime, getter_AddRefs(data));
nsAutoString str;
if (!NS_WARN_IF(NS_FAILED(rv))) {
nsCOMPtr<nsISupportsString> supportsstr =
do_QueryInterface(data);
MOZ_ASSERT(supportsstr);
if (supportsstr) {
supportsstr->GetData(str);
}
}
p->MaybeResolve(str);
},
/* reject */
[p](nsresult rv) { p->MaybeReject(rv); });
break;
}
default: {
@ -284,12 +180,6 @@ void Clipboard::ReadRequest::Answer() {
break;
}
}
if (NS_FAILED(rv)) {
MOZ_ASSERT(callback);
callback->OnError(rv);
return;
}
}
static bool IsReadTextExposedToContent() {
@ -865,8 +755,7 @@ already_AddRefed<Promise> Clipboard::WriteText(const nsAString& aData,
void Clipboard::ReadRequest::MaybeRejectWithNotAllowedError(
const nsACString& aMessage) {
RefPtr<Promise> p(std::move(mPromise));
p->MaybeRejectWithNotAllowedError(aMessage);
mPromise->MaybeRejectWithNotAllowedError(aMessage);
}
void Clipboard::OnUserReactedToPasteMenuPopup(const bool aAllowed) {

View file

@ -21,8 +21,7 @@ NS_IMPL_CYCLE_COLLECTION(ClipboardItem::ItemEntry, mGlobal, mData,
mPendingGetTypeRequests)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ClipboardItem::ItemEntry)
NS_INTERFACE_MAP_ENTRY(nsIAsyncClipboardRequestCallback)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, PromiseNativeHandler)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(ClipboardItem::ItemEntry)
@ -31,7 +30,7 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(ClipboardItem::ItemEntry)
void ClipboardItem::ItemEntry::ResolvedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue,
ErrorResult& aRv) {
MOZ_ASSERT(!mTransferable);
MOZ_ASSERT(!mLoadingPromise.Exists());
mIsLoadingData = false;
OwningStringOrBlob clipboardData;
if (!clipboardData.Init(aCx, aValue)) {
@ -46,7 +45,7 @@ void ClipboardItem::ItemEntry::ResolvedCallback(JSContext* aCx,
void ClipboardItem::ItemEntry::RejectedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue,
ErrorResult& aRv) {
MOZ_ASSERT(!mTransferable);
MOZ_ASSERT(!mLoadingPromise.Exists());
mIsLoadingData = false;
RejectPendingPromises(NS_ERROR_DOM_DATA_ERR);
}
@ -78,95 +77,94 @@ ClipboardItem::ItemEntry::GetData() {
return GetDataPromise::CreateAndResolve(std::move(data), __func__);
}
NS_IMETHODIMP ClipboardItem::ItemEntry::OnComplete(nsresult aResult) {
MOZ_ASSERT(mIsLoadingData);
mIsLoadingData = false;
nsCOMPtr<nsITransferable> trans = std::move(mTransferable);
if (NS_FAILED(aResult)) {
RejectPendingPromises(aResult);
return NS_OK;
}
MOZ_ASSERT(trans);
nsCOMPtr<nsISupports> data;
nsresult rv = trans->GetTransferData(NS_ConvertUTF16toUTF8(mType).get(),
getter_AddRefs(data));
if (NS_WARN_IF(NS_FAILED(rv))) {
RejectPendingPromises(rv);
return NS_OK;
}
RefPtr<Blob> blob;
if (nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data)) {
nsAutoString str;
supportsstr->GetData(str);
blob = Blob::CreateStringBlob(mGlobal, NS_ConvertUTF16toUTF8(str), mType);
} else if (nsCOMPtr<nsIInputStream> istream = do_QueryInterface(data)) {
uint64_t available;
void* data = nullptr;
rv = NS_ReadInputStreamToBuffer(istream, &data, -1, &available);
if (NS_WARN_IF(NS_FAILED(rv))) {
RejectPendingPromises(rv);
return NS_OK;
}
blob = Blob::CreateMemoryBlob(mGlobal, data, available, mType);
} else if (nsCOMPtr<nsISupportsCString> supportscstr =
do_QueryInterface(data)) {
nsAutoCString str;
supportscstr->GetData(str);
blob = Blob::CreateStringBlob(mGlobal, str, mType);
}
if (!blob) {
RejectPendingPromises(NS_ERROR_DOM_DATA_ERR);
return NS_OK;
}
OwningStringOrBlob clipboardData;
clipboardData.SetAsBlob() = std::move(blob);
MaybeResolvePendingPromises(std::move(clipboardData));
return NS_OK;
}
void ClipboardItem::ItemEntry::LoadDataFromSystemClipboard(
nsIAsyncGetClipboardData* aDataGetter) {
MOZ_ASSERT(aDataGetter);
nsITransferable& aTransferable) {
// XXX maybe we could consider adding a method to check whether the union
// object is uninitialized or initialized.
MOZ_DIAGNOSTIC_ASSERT(!mData.IsString() && !mData.IsBlob(),
"Data should be uninitialized.");
MOZ_DIAGNOSTIC_ASSERT(mLoadResult.isNothing(), "Should have no load result");
MOZ_DIAGNOSTIC_ASSERT(!mIsLoadingData && !mTransferable,
MOZ_DIAGNOSTIC_ASSERT(!mIsLoadingData && !mLoadingPromise.Exists(),
"Should not be in the process of loading data");
mIsLoadingData = true;
mTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1");
if (NS_WARN_IF(!mTransferable)) {
OnComplete(NS_ERROR_FAILURE);
return;
}
mTransferable->Init(nullptr);
mTransferable->AddDataFlavor(NS_ConvertUTF16toUTF8(mType).get());
nsresult rv = aDataGetter->GetData(mTransferable, this);
nsresult rv;
nsCOMPtr<nsIClipboard> clipboard(
do_GetService("@mozilla.org/widget/clipboard;1", &rv));
if (NS_FAILED(rv)) {
OnComplete(rv);
return;
}
mIsLoadingData = true;
nsCOMPtr<nsITransferable> trans(&aTransferable);
clipboard->AsyncGetData(trans, nsIClipboard::kGlobalClipboard)
->Then(
GetMainThreadSerialEventTarget(), __func__,
/* resolved */
[self = RefPtr{this}, trans]() {
self->mIsLoadingData = false;
self->mLoadingPromise.Complete();
nsCOMPtr<nsISupports> data;
nsresult rv = trans->GetTransferData(
NS_ConvertUTF16toUTF8(self->Type()).get(),
getter_AddRefs(data));
if (NS_WARN_IF(NS_FAILED(rv))) {
self->RejectPendingPromises(rv);
return;
}
RefPtr<Blob> blob;
if (nsCOMPtr<nsISupportsString> supportsstr =
do_QueryInterface(data)) {
nsAutoString str;
supportsstr->GetData(str);
blob = Blob::CreateStringBlob(
self->mGlobal, NS_ConvertUTF16toUTF8(str), self->Type());
} else if (nsCOMPtr<nsIInputStream> istream =
do_QueryInterface(data)) {
uint64_t available;
void* data = nullptr;
nsresult rv =
NS_ReadInputStreamToBuffer(istream, &data, -1, &available);
if (NS_WARN_IF(NS_FAILED(rv))) {
self->RejectPendingPromises(rv);
return;
}
blob = Blob::CreateMemoryBlob(self->mGlobal, data, available,
self->Type());
} else if (nsCOMPtr<nsISupportsCString> supportscstr =
do_QueryInterface(data)) {
nsAutoCString str;
supportscstr->GetData(str);
blob = Blob::CreateStringBlob(self->mGlobal, str, self->Type());
}
if (!blob) {
self->RejectPendingPromises(NS_ERROR_DOM_DATA_ERR);
return;
}
OwningStringOrBlob clipboardData;
clipboardData.SetAsBlob() = std::move(blob);
self->MaybeResolvePendingPromises(std::move(clipboardData));
},
/* rejected */
[self = RefPtr{this}](nsresult rv) {
self->mIsLoadingData = false;
self->mLoadingPromise.Complete();
self->RejectPendingPromises(rv);
})
->Track(mLoadingPromise);
}
void ClipboardItem::ItemEntry::LoadDataFromDataPromise(Promise& aDataPromise) {
MOZ_DIAGNOSTIC_ASSERT(!mData.IsString() && !mData.IsBlob(),
"Data should be uninitialized");
MOZ_DIAGNOSTIC_ASSERT(mLoadResult.isNothing(), "Should have no load result");
MOZ_DIAGNOSTIC_ASSERT(!mIsLoadingData && !mTransferable,
MOZ_DIAGNOSTIC_ASSERT(!mIsLoadingData && !mLoadingPromise.Exists(),
"Should not be in the process of loading data");
mIsLoadingData = true;
@ -223,7 +221,7 @@ void ClipboardItem::ItemEntry::RejectPendingPromises(nsresult aRv) {
MOZ_DIAGNOSTIC_ASSERT(!mData.IsString() && !mData.IsBlob(),
"Data should be uninitialized");
MOZ_DIAGNOSTIC_ASSERT(mLoadResult.isNothing(), "Should not have load result");
MOZ_DIAGNOSTIC_ASSERT(!mIsLoadingData && !mTransferable,
MOZ_DIAGNOSTIC_ASSERT(!mIsLoadingData && !mLoadingPromise.Exists(),
"Should not be in the process of loading data");
mLoadResult.emplace(aRv);
auto promiseHolders = std::move(mPendingGetDataRequests);
@ -241,7 +239,7 @@ void ClipboardItem::ItemEntry::MaybeResolvePendingPromises(
MOZ_DIAGNOSTIC_ASSERT(!mData.IsString() && !mData.IsBlob(),
"Data should be uninitialized");
MOZ_DIAGNOSTIC_ASSERT(mLoadResult.isNothing(), "Should not have load result");
MOZ_DIAGNOSTIC_ASSERT(!mIsLoadingData && !mTransferable,
MOZ_DIAGNOSTIC_ASSERT(!mIsLoadingData && !mLoadingPromise.Exists(),
"Should not be in the process of loading data");
mLoadResult.emplace(NS_OK);
mData = std::move(aData);

View file

@ -12,7 +12,6 @@
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/MozPromise.h"
#include "nsIClipboard.h"
#include "nsWrapperCache.h"
class nsITransferable;
@ -26,15 +25,13 @@ class Promise;
class ClipboardItem final : public nsWrapperCache {
public:
class ItemEntry final : public PromiseNativeHandler,
public nsIAsyncClipboardRequestCallback {
class ItemEntry final : public PromiseNativeHandler {
public:
using GetDataPromise =
MozPromise<OwningStringOrBlob, nsresult, /* IsExclusive = */ true>;
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_NSIASYNCCLIPBOARDREQUESTCALLBACK
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ItemEntry, PromiseNativeHandler)
NS_DECL_CYCLE_COLLECTION_CLASS(ItemEntry)
explicit ItemEntry(nsIGlobalObject* aGlobal, const nsAString& aType)
: mGlobal(aGlobal), mType(aType) {
@ -57,7 +54,7 @@ class ClipboardItem final : public nsWrapperCache {
RefPtr<GetDataPromise> GetData();
// Load data from system clipboard.
void LoadDataFromSystemClipboard(nsIAsyncGetClipboardData* aDataGetter);
void LoadDataFromSystemClipboard(nsITransferable& aTransferable);
void LoadDataFromDataPromise(Promise& aDataPromise);
// If clipboard data is in the process of loading from either system
@ -68,6 +65,7 @@ class ClipboardItem final : public nsWrapperCache {
private:
~ItemEntry() {
mLoadingPromise.DisconnectIfExists();
if (!mPendingGetDataRequests.IsEmpty()) {
RejectPendingPromises(NS_ERROR_FAILURE);
}
@ -89,7 +87,7 @@ class ClipboardItem final : public nsWrapperCache {
// Indicates if the data is still being loaded.
bool mIsLoadingData = false;
nsCOMPtr<nsITransferable> mTransferable;
MozPromiseRequestHolder<GenericPromise> mLoadingPromise;
// Pending promises for data retrieval requests.
nsTArray<MozPromiseHolder<GetDataPromise>> mPendingGetDataRequests;

View file

@ -24,7 +24,6 @@
#include "imgLoader.h"
#include "ScrollingMetrics.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/ClipboardReadRequestChild.h"
#include "mozilla/Components.h"
#include "mozilla/HangDetails.h"
#include "mozilla/LoadInfo.h"
@ -1988,12 +1987,6 @@ PRemotePrintJobChild* ContentChild::AllocPRemotePrintJobChild() {
#endif
}
already_AddRefed<PClipboardReadRequestChild>
ContentChild::AllocPClipboardReadRequestChild(
const nsTArray<nsCString>& aTypes) {
return MakeAndAddRef<ClipboardReadRequestChild>(aTypes);
}
media::PMediaChild* ContentChild::AllocPMediaChild() {
return media::AllocPMediaChild();
}

View file

@ -229,9 +229,6 @@ class ContentChild final : public PContentChild,
PRemotePrintJobChild* AllocPRemotePrintJobChild();
already_AddRefed<PClipboardReadRequestChild> AllocPClipboardReadRequestChild(
const nsTArray<nsCString>& aTypes);
PMediaChild* AllocPMediaChild();
bool DeallocPMediaChild(PMediaChild* aActor);

View file

@ -53,7 +53,6 @@
#include "mozilla/BenchmarkStorageParent.h"
#include "mozilla/Casting.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/ClipboardReadRequestParent.h"
#include "mozilla/ClipboardWriteRequestParent.h"
#include "mozilla/ContentBlockingUserInteraction.h"
#include "mozilla/FOGIPC.h"
@ -3564,6 +3563,26 @@ mozilla::ipc::IPCResult ContentParent::RecvClipboardHasType(
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvClipboardHasTypesAsync(
nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard,
ClipboardHasTypesAsyncResolver&& aResolver) {
nsresult rv;
nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
if (NS_FAILED(rv)) {
return IPC_FAIL(this, "RecvGetClipboardTypes failed.");
}
clipboard->AsyncHasDataMatchingFlavors(aTypes, aWhichClipboard)
->Then(
GetMainThreadSerialEventTarget(), __func__,
/* resolve */
[aResolver](nsTArray<nsCString> types) { aResolver(types); },
/* reject */
[aResolver](nsresult rv) { aResolver(nsTArray<nsCString>{}); });
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvGetExternalClipboardFormats(
const int32_t& aWhichClipboard, const bool& aPlainTextOnly,
nsTArray<nsCString>* aTypes) {
@ -3573,54 +3592,6 @@ mozilla::ipc::IPCResult ContentParent::RecvGetExternalClipboardFormats(
return IPC_OK();
}
namespace {
class ClipboardGetCallback final : public nsIAsyncClipboardGetCallback {
public:
ClipboardGetCallback(ContentParent* aContentParent,
ContentParent::GetClipboardAsyncResolver&& aResolver)
: mContentParent(aContentParent), mResolver(std::move(aResolver)) {}
// This object will never be held by a cycle-collected object, so it doesn't
// need to be cycle-collected despite holding alive cycle-collected objects.
NS_DECL_ISUPPORTS
// nsIAsyncClipboardGetCallback
NS_IMETHOD OnSuccess(
nsIAsyncGetClipboardData* aAsyncGetClipboardData) override {
nsTArray<nsCString> flavors;
nsresult rv = aAsyncGetClipboardData->GetFlavorList(flavors);
if (NS_FAILED(rv)) {
return OnError(rv);
}
auto requestParent = MakeNotNull<RefPtr<ClipboardReadRequestParent>>(
mContentParent, aAsyncGetClipboardData);
if (!mContentParent->SendPClipboardReadRequestConstructor(
requestParent, std::move(flavors))) {
return OnError(NS_ERROR_FAILURE);
}
mResolver(PClipboardReadRequestOrError(requestParent));
return NS_OK;
}
NS_IMETHOD OnError(nsresult aResult) override {
mResolver(aResult);
return NS_OK;
}
protected:
~ClipboardGetCallback() = default;
RefPtr<ContentParent> mContentParent;
ContentParent::GetClipboardAsyncResolver mResolver;
};
NS_IMPL_ISUPPORTS(ClipboardGetCallback, nsIAsyncClipboardGetCallback)
} // namespace
mozilla::ipc::IPCResult ContentParent::RecvGetClipboardAsync(
nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard,
GetClipboardAsyncResolver&& aResolver) {
@ -3632,13 +3603,25 @@ mozilla::ipc::IPCResult ContentParent::RecvGetClipboardAsync(
return IPC_OK();
}
auto callback = MakeRefPtr<ClipboardGetCallback>(this, std::move(aResolver));
rv = clipboard->AsyncGetData(aTypes, aWhichClipboard, callback);
if (NS_FAILED(rv)) {
callback->OnError(rv);
// Create transferable
auto result = CreateTransferable(aTypes);
if (result.isErr()) {
aResolver(result.unwrapErr());
return IPC_OK();
}
// Get data from clipboard
nsCOMPtr<nsITransferable> trans = result.unwrap();
clipboard->AsyncGetData(trans, nsIClipboard::kGlobalClipboard)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[trans, aResolver,
self = RefPtr{this}](GenericPromise::ResolveOrRejectValue&& aValue) {
IPCTransferableData ipcTransferableData;
nsContentUtils::TransferableToIPCTransferableData(
trans, &ipcTransferableData, false /* aInSyncMessage */, self);
aResolver(std::move(ipcTransferableData));
});
return IPC_OK();
}

View file

@ -987,6 +987,10 @@ class ContentParent final : public PContentParent,
const int32_t& aWhichClipboard,
bool* aHasType);
mozilla::ipc::IPCResult RecvClipboardHasTypesAsync(
nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard,
ClipboardHasTypesAsyncResolver&& aResolver);
mozilla::ipc::IPCResult RecvGetExternalClipboardFormats(
const int32_t& aWhichClipboard, const bool& aPlainTextOnly,
nsTArray<nsCString>* aTypes);

View file

@ -74,11 +74,6 @@ struct IPCTransferableData
IPCTransferableDataItem[] items;
};
union IPCTransferableDataOrError {
IPCTransferableData;
nsresult;
};
struct IPCTransferable
{
IPCTransferableData data;

View file

@ -6,7 +6,6 @@
include protocol PBackgroundStarter;
include protocol PBrowser;
include protocol PClipboardReadRequest;
include protocol PClipboardWriteRequest;
include protocol PCompositorManager;
include protocol PContentPermissionRequest;
@ -462,8 +461,8 @@ struct IPCImage {
ImageIntSize size;
};
union PClipboardReadRequestOrError {
PClipboardReadRequest;
union IPCTransferableDataOrError {
IPCTransferableData;
nsresult;
};
@ -476,7 +475,6 @@ union PClipboardReadRequestOrError {
sync protocol PContent
{
manages PBrowser;
manages PClipboardReadRequest;
manages PClipboardWriteRequest;
manages PContentPermissionRequest;
manages PCycleCollectWithLogs;
@ -1045,11 +1043,10 @@ child:
// details.
async InitNextGenLocalStorageEnabled(bool enabled);
async PRemotePrintJob();
async PClipboardReadRequest(nsCString[] aTypes);
async PRemotePrintJob();
parent:
async SynchronizeLayoutHistoryState(MaybeDiscardedBrowsingContext aContext,
nullable nsILayoutHistoryState aState);
@ -1219,9 +1216,9 @@ parent:
// Returns a list of formats supported by the clipboard
sync GetExternalClipboardFormats(int32_t aWhichClipboard, bool aPlainTextOnly) returns (nsCString[] aTypes);
// Requests getting data from clipboard.
async GetClipboardAsync(nsCString[] aTypes, int32_t aWhichClipboard)
returns (PClipboardReadRequestOrError aClipboardReadRequest);
// Given a list of supported types, returns the clipboard data for the
// first type that matches.
async GetClipboardAsync(nsCString[] aTypes, int32_t aWhichClipboard) returns (IPCTransferableDataOrError transferableData);
// Clears the clipboard.
async EmptyClipboard(int32_t aWhichClipboard);
@ -1230,6 +1227,11 @@ parent:
sync ClipboardHasType(nsCString[] aTypes, int32_t aWhichClipboard)
returns (bool hasType);
// Given a list of supported types, returns a list of types that clipboard
// constains the data for the specified type.
async ClipboardHasTypesAsync(nsCString [] aTypes, int32_t aWhichClipboard)
returns (nsCString [] types);
/**
* Notify the parent that the child has started a clipboard write request,
* and that the data will be sent over another IPC message once it is ready.

View file

@ -1,34 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_ClipboardReadRequestChild_h
#define mozilla_ClipboardReadRequestChild_h
#include "mozilla/PClipboardReadRequestChild.h"
class nsITransferable;
namespace mozilla {
class ClipboardReadRequestChild final : public PClipboardReadRequestChild {
public:
explicit ClipboardReadRequestChild(const nsTArray<nsCString>& aFlavorList) {
mFlavorList.AppendElements(aFlavorList);
}
NS_INLINE_DECL_REFCOUNTING(ClipboardReadRequestChild)
const nsTArray<nsCString>& FlavorList() const { return mFlavorList; }
protected:
virtual ~ClipboardReadRequestChild() = default;
private:
nsTArray<nsCString> mFlavorList;
};
} // namespace mozilla
#endif // mozilla_ClipboardReadRequestChild_h

View file

@ -1,115 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/ClipboardReadRequestParent.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/net/CookieJarSettings.h"
#include "nsComponentManagerUtils.h"
#include "nsIClipboard.h"
#include "nsITransferable.h"
#include "nsWidgetsCID.h"
using mozilla::dom::ContentParent;
using mozilla::ipc::IPCResult;
namespace mozilla {
namespace {
class ClipboardGetDataCallback final : public nsIAsyncClipboardRequestCallback {
public:
explicit ClipboardGetDataCallback(std::function<void(nsresult)>&& aCallback)
: mCallback(std::move(aCallback)) {}
// This object will never be held by a cycle-collected object, so it doesn't
// need to be cycle-collected despite holding alive cycle-collected objects.
NS_DECL_ISUPPORTS
// nsIAsyncClipboardRequestCallback
NS_IMETHOD OnComplete(nsresult aResult) override {
mCallback(aResult);
return NS_OK;
}
protected:
~ClipboardGetDataCallback() = default;
std::function<void(nsresult)> mCallback;
};
NS_IMPL_ISUPPORTS(ClipboardGetDataCallback, nsIAsyncClipboardRequestCallback)
static Result<nsCOMPtr<nsITransferable>, nsresult> CreateTransferable(
const nsTArray<nsCString>& aTypes) {
nsresult rv;
nsCOMPtr<nsITransferable> trans =
do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
if (NS_FAILED(rv)) {
return Err(rv);
}
MOZ_TRY(trans->Init(nullptr));
// The private flag is only used to prevent the data from being cached to the
// disk. The flag is not exported to the IPCDataTransfer object.
// The flag is set because we are not sure whether the clipboard data is used
// in a private browsing context. The transferable is only used in this scope,
// so the cache would not reduce memory consumption anyway.
trans->SetIsPrivateData(true);
// Fill out flavors for transferable
for (uint32_t t = 0; t < aTypes.Length(); t++) {
MOZ_TRY(trans->AddDataFlavor(aTypes[t].get()));
}
return std::move(trans);
}
} // namespace
IPCResult ClipboardReadRequestParent::RecvGetData(
const nsTArray<nsCString>& aFlavors, GetDataResolver&& aResolver) {
bool valid = false;
if (NS_FAILED(mAsyncGetClipboardData->GetValid(&valid)) || !valid) {
Unused << PClipboardReadRequestParent::Send__delete__(this);
aResolver(NS_ERROR_FAILURE);
return IPC_OK();
}
// Create transferable
auto result = CreateTransferable(aFlavors);
if (result.isErr()) {
aResolver(result.unwrapErr());
return IPC_OK();
}
nsCOMPtr<nsITransferable> trans = result.unwrap();
RefPtr<ClipboardGetDataCallback> callback =
MakeRefPtr<ClipboardGetDataCallback>([self = RefPtr{this},
resolver = std::move(aResolver),
trans,
manager = mManager](nsresult aRv) {
if (NS_FAILED(aRv)) {
bool valid = false;
if (NS_FAILED(self->mAsyncGetClipboardData->GetValid(&valid)) ||
!valid) {
Unused << PClipboardReadRequestParent::Send__delete__(self);
}
resolver(aRv);
return;
}
dom::IPCTransferableData ipcTransferableData;
nsContentUtils::TransferableToIPCTransferableData(
trans, &ipcTransferableData, false /* aInSyncMessage */, manager);
resolver(std::move(ipcTransferableData));
});
nsresult rv = mAsyncGetClipboardData->GetData(trans, callback);
if (NS_FAILED(rv)) {
callback->OnComplete(rv);
}
return IPC_OK();
}
} // namespace mozilla

View file

@ -1,39 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_ClipboardReadRequestParent_h
#define mozilla_ClipboardReadRequestParent_h
#include "mozilla/dom/ContentParent.h"
#include "mozilla/PClipboardReadRequestParent.h"
#include "nsIClipboard.h"
namespace mozilla {
class ClipboardReadRequestParent final : public PClipboardReadRequestParent {
using IPCResult = mozilla::ipc::IPCResult;
using ContentParent = mozilla::dom::ContentParent;
public:
ClipboardReadRequestParent(ContentParent* aManager,
nsIAsyncGetClipboardData* aAsyncGetClipboardData)
: mManager(aManager), mAsyncGetClipboardData(aAsyncGetClipboardData) {}
NS_INLINE_DECL_REFCOUNTING(ClipboardReadRequestParent, override)
// PClipboardReadRequestParent
IPCResult RecvGetData(const nsTArray<nsCString>& aFlavors,
GetDataResolver&& aResolver);
private:
~ClipboardReadRequestParent() = default;
RefPtr<ContentParent> mManager;
nsCOMPtr<nsIAsyncGetClipboardData> mAsyncGetClipboardData;
};
} // namespace mozilla
#endif // mozilla_ClipboardReadRequestParent_h

View file

@ -1,25 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
include protocol PContent;
include DOMTypes;
include IPCTransferable;
include NeckoChannelParams;
using nsContentPolicyType from "nsIContentPolicy.h";
namespace mozilla {
protocol PClipboardReadRequest {
manager PContent;
parent:
async GetData(nsCString[] aFlavors) returns (IPCTransferableDataOrError aTransferableData);
both:
async __delete__();
};
} // namespace mozilla

View file

@ -166,8 +166,6 @@ EXPORTS += [
EXPORTS.mozilla += [
"BasicEvents.h",
"ClipboardReadRequestChild.h",
"ClipboardReadRequestParent.h",
"ClipboardWriteRequestChild.h",
"ClipboardWriteRequestParent.h",
"ColorScheme.h",
@ -217,7 +215,6 @@ EXPORTS.mozilla.widget += [
]
UNIFIED_SOURCES += [
"ClipboardReadRequestParent.cpp",
"ClipboardWriteRequestChild.cpp",
"ClipboardWriteRequestParent.cpp",
"CompositorWidget.cpp",
@ -377,7 +374,6 @@ else:
IPDL_SOURCES += [
"LookAndFeelTypes.ipdlh",
"PClipboardReadRequest.ipdl",
"PClipboardWriteRequest.ipdl",
]

View file

@ -15,8 +15,6 @@ using mozilla::LogLevel;
using mozilla::UniquePtr;
using mozilla::dom::ClipboardCapabilities;
static const int32_t kGetAvailableFlavorsRetryCount = 5;
NS_IMPL_ISUPPORTS(nsBaseClipboard::AsyncSetClipboardData,
nsIAsyncSetClipboardData)
@ -225,7 +223,29 @@ nsresult nsBaseClipboard::GetDataFromClipboardCache(
return NS_ERROR_FAILURE;
}
return clipboardCache->GetData(aTransferable);
nsITransferable* cachedTransferable = clipboardCache->GetTransferable();
MOZ_ASSERT(cachedTransferable);
// get flavor list that includes all acceptable flavors (including ones
// obtained through conversion)
nsTArray<nsCString> flavors;
if (NS_FAILED(aTransferable->FlavorsTransferableCanImport(flavors))) {
return NS_ERROR_FAILURE;
}
for (const auto& flavor : flavors) {
nsCOMPtr<nsISupports> dataSupports;
if (NS_SUCCEEDED(cachedTransferable->GetTransferData(
flavor.get(), getter_AddRefs(dataSupports)))) {
MOZ_CLIPBOARD_LOG("%s: getting %s from cache.", __FUNCTION__,
flavor.get());
aTransferable->SetTransferData(flavor.get(), dataSupports);
// maybe try to fill in more types? Is there a point?
return NS_OK;
}
}
return NS_ERROR_FAILURE;
}
/**
@ -257,124 +277,41 @@ NS_IMETHODIMP nsBaseClipboard::GetData(nsITransferable* aTransferable,
return GetNativeClipboardData(aTransferable, aWhichClipboard);
}
void nsBaseClipboard::MaybeRetryGetAvailableFlavors(
const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
nsIAsyncClipboardGetCallback* aCallback, int32_t aRetryCount) {
// Note we have to get the clipboard sequence number first before the actual
// read. This is to use it to verify the clipboard data is still the one we
// try to read, instead of the later state.
auto sequenceNumberOrError =
GetNativeClipboardSequenceNumber(aWhichClipboard);
if (sequenceNumberOrError.isErr()) {
MOZ_CLIPBOARD_LOG("%s: unable to get sequence number for clipboard %d.",
__FUNCTION__, aWhichClipboard);
aCallback->OnError(sequenceNumberOrError.unwrapErr());
return;
}
int32_t sequenceNumber = sequenceNumberOrError.unwrap();
AsyncHasNativeClipboardDataMatchingFlavors(
aFlavorList, aWhichClipboard,
[self = RefPtr{this}, callback = nsCOMPtr{aCallback}, aWhichClipboard,
aRetryCount, flavorList = aFlavorList.Clone(),
sequenceNumber](auto aFlavorsOrError) {
if (aFlavorsOrError.isErr()) {
MOZ_CLIPBOARD_LOG(
"%s: unable to get available flavors for clipboard %d.",
__FUNCTION__, aWhichClipboard);
callback->OnError(aFlavorsOrError.unwrapErr());
return;
}
auto sequenceNumberOrError =
self->GetNativeClipboardSequenceNumber(aWhichClipboard);
if (sequenceNumberOrError.isErr()) {
MOZ_CLIPBOARD_LOG(
"%s: unable to get sequence number for clipboard %d.",
__FUNCTION__, aWhichClipboard);
callback->OnError(sequenceNumberOrError.unwrapErr());
return;
}
if (sequenceNumber == sequenceNumberOrError.unwrap()) {
auto asyncGetClipboardData =
mozilla::MakeRefPtr<AsyncGetClipboardData>(
aWhichClipboard, sequenceNumber,
std::move(aFlavorsOrError.unwrap()), false, self);
callback->OnSuccess(asyncGetClipboardData);
return;
}
if (aRetryCount > 0) {
MOZ_CLIPBOARD_LOG(
"%s: clipboard=%d, ignore the data due to the sequence number "
"doesn't match, retry (%d) ..",
__FUNCTION__, aWhichClipboard, aRetryCount);
self->MaybeRetryGetAvailableFlavors(flavorList, aWhichClipboard,
callback, aRetryCount - 1);
return;
}
MOZ_DIAGNOSTIC_ASSERT(false, "How can this happen?!?");
callback->OnError(NS_ERROR_FAILURE);
});
}
NS_IMETHODIMP nsBaseClipboard::AsyncGetData(
const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
nsIAsyncClipboardGetCallback* aCallback) {
RefPtr<GenericPromise> nsBaseClipboard::AsyncGetData(
nsITransferable* aTransferable, int32_t aWhichClipboard) {
MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
if (!aCallback || aFlavorList.IsEmpty()) {
return NS_ERROR_INVALID_ARG;
}
if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
aWhichClipboard);
return NS_ERROR_FAILURE;
if (!aTransferable) {
NS_ASSERTION(false, "clipboard given a null transferable");
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
// If we were the last ones to put something on the navtive clipboard, then
// If we were the last ones to put something on the native clipboard, then
// just use the cached transferable. Otherwise clear it because it isn't
// relevant any more.
if (auto* clipboardCache = GetClipboardCacheIfValid(aWhichClipboard)) {
nsITransferable* cachedTransferable = clipboardCache->GetTransferable();
MOZ_ASSERT(cachedTransferable);
nsTArray<nsCString> transferableFlavors;
if (NS_SUCCEEDED(cachedTransferable->FlavorsTransferableCanExport(
transferableFlavors))) {
nsTArray<nsCString> results;
for (const auto& transferableFlavor : transferableFlavors) {
for (const auto& flavor : aFlavorList) {
// XXX We need special check for image as we always put the image as
// "native" on the clipboard.
if (transferableFlavor.Equals(flavor) ||
(transferableFlavor.Equals(kNativeImageMime) &&
nsContentUtils::IsFlavorImage(flavor))) {
MOZ_CLIPBOARD_LOG(" has %s", flavor.get());
results.AppendElement(flavor);
}
}
}
auto asyncGetClipboardData = mozilla::MakeRefPtr<AsyncGetClipboardData>(
aWhichClipboard, clipboardCache->GetSequenceNumber(),
std::move(results), true, this);
aCallback->OnSuccess(asyncGetClipboardData);
return NS_OK;
}
if (NS_SUCCEEDED(
GetDataFromClipboardCache(aTransferable, aWhichClipboard))) {
// maybe try to fill in more types? Is there a point?
return GenericPromise::CreateAndResolve(true, __func__);
}
// At this point we can't satisfy the request from cache data so let's look
// for things other people put on the system clipboard.
// at this point we can't satisfy the request from cache data so let's look
// for things other people put on the system clipboard
}
MaybeRetryGetAvailableFlavors(aFlavorList, aWhichClipboard, aCallback,
kGetAvailableFlavorsRetryCount);
return NS_OK;
RefPtr<GenericPromise::Private> dataPromise =
mozilla::MakeRefPtr<GenericPromise::Private>(__func__);
AsyncGetNativeClipboardData(aTransferable, aWhichClipboard,
[dataPromise](nsresult aResult) {
if (NS_FAILED(aResult)) {
dataPromise->Reject(aResult, __func__);
return;
}
dataPromise->Resolve(true, __func__);
});
return dataPromise.forget();
}
NS_IMETHODIMP nsBaseClipboard::EmptyClipboard(int32_t aWhichClipboard) {
@ -478,6 +415,52 @@ nsBaseClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
return NS_OK;
}
RefPtr<DataFlavorsPromise> nsBaseClipboard::AsyncHasDataMatchingFlavors(
const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard) {
MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
if (MOZ_CLIPBOARD_LOG_ENABLED()) {
MOZ_CLIPBOARD_LOG(" Asking for content clipboard=%i:\n",
aWhichClipboard);
for (const auto& flavor : aFlavorList) {
MOZ_CLIPBOARD_LOG(" MIME %s", flavor.get());
}
}
if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
// First, check if we have valid data in our cached transferable.
auto flavorsOrError = GetFlavorsFromClipboardCache(aWhichClipboard);
if (flavorsOrError.isOk()) {
nsTArray<nsCString> results;
for (const auto& transferableFlavor : flavorsOrError.unwrap()) {
for (const auto& flavor : aFlavorList) {
// XXX We need special check for image as we always put the image as
// "native" on the clipboard.
if (transferableFlavor.Equals(flavor) ||
(transferableFlavor.Equals(kNativeImageMime) &&
nsContentUtils::IsFlavorImage(flavor))) {
MOZ_CLIPBOARD_LOG(" has %s", flavor.get());
results.AppendElement(flavor);
}
}
}
return DataFlavorsPromise::CreateAndResolve(std::move(results), __func__);
}
}
RefPtr<DataFlavorsPromise::Private> flavorPromise =
mozilla::MakeRefPtr<DataFlavorsPromise::Private>(__func__);
AsyncHasNativeClipboardDataMatchingFlavors(
aFlavorList, aWhichClipboard, [flavorPromise](auto aResultOrError) {
if (aResultOrError.isErr()) {
flavorPromise->Reject(aResultOrError.unwrapErr(), __func__);
return;
}
flavorPromise->Resolve(std::move(aResultOrError.unwrap()), __func__);
});
return flavorPromise.forget();
}
NS_IMETHODIMP
nsBaseClipboard::IsClipboardTypeSupported(int32_t aWhichClipboard,
bool* aRetval) {
@ -537,121 +520,6 @@ void nsBaseClipboard::ClearClipboardCache(int32_t aClipboardType) {
cache->Clear();
}
NS_IMPL_ISUPPORTS(nsBaseClipboard::AsyncGetClipboardData,
nsIAsyncGetClipboardData)
nsBaseClipboard::AsyncGetClipboardData::AsyncGetClipboardData(
int32_t aClipboardType, int32_t aSequenceNumber,
nsTArray<nsCString>&& aFlavors, bool aFromCache,
nsBaseClipboard* aClipboard)
: mClipboardType(aClipboardType),
mSequenceNumber(aSequenceNumber),
mFlavors(std::move(aFlavors)),
mFromCache(aFromCache),
mClipboard(aClipboard) {
MOZ_ASSERT(mClipboard);
MOZ_ASSERT(
mClipboard->nsIClipboard::IsClipboardTypeSupported(mClipboardType));
}
NS_IMETHODIMP nsBaseClipboard::AsyncGetClipboardData::GetValid(
bool* aOutResult) {
*aOutResult = IsValid();
return NS_OK;
}
NS_IMETHODIMP nsBaseClipboard::AsyncGetClipboardData::GetFlavorList(
nsTArray<nsCString>& aFlavors) {
aFlavors.AppendElements(mFlavors);
return NS_OK;
}
NS_IMETHODIMP nsBaseClipboard::AsyncGetClipboardData::GetData(
nsITransferable* aTransferable,
nsIAsyncClipboardRequestCallback* aCallback) {
MOZ_CLIPBOARD_LOG("AsyncGetClipboardData::GetData: %p", this);
if (!aTransferable || !aCallback) {
return NS_ERROR_INVALID_ARG;
}
nsTArray<nsCString> flavors;
nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
if (NS_FAILED(rv)) {
return rv;
}
// If the requested flavor is not in the list, throw an error.
for (const auto& flavor : flavors) {
if (!mFlavors.Contains(flavor)) {
return NS_ERROR_FAILURE;
}
}
if (!IsValid()) {
aCallback->OnComplete(NS_ERROR_FAILURE);
return NS_OK;
}
MOZ_ASSERT(mClipboard);
if (mFromCache) {
const auto* clipboardCache =
mClipboard->GetClipboardCacheIfValid(mClipboardType);
// `IsValid()` above ensures we should get a valid cache and matched
// sequence number here.
MOZ_DIAGNOSTIC_ASSERT(clipboardCache);
MOZ_DIAGNOSTIC_ASSERT(clipboardCache->GetSequenceNumber() ==
mSequenceNumber);
aCallback->OnComplete(clipboardCache->GetData(aTransferable));
return NS_OK;
}
// Since this is an async operation, we need to check if the data is still
// valid after we get the result.
mClipboard->AsyncGetNativeClipboardData(
aTransferable, mClipboardType,
[callback = nsCOMPtr{aCallback}, self = RefPtr{this}](nsresult aResult) {
// `IsValid()` checks the clipboard sequence number to ensure the data
// we are requesting is still valid.
callback->OnComplete(self->IsValid() ? aResult : NS_ERROR_FAILURE);
});
return NS_OK;
}
bool nsBaseClipboard::AsyncGetClipboardData::IsValid() {
if (!mClipboard) {
return false;
}
// If the data should from cache, check if cache is still valid or the
// sequence numbers are matched.
if (mFromCache) {
const auto* clipboardCache =
mClipboard->GetClipboardCacheIfValid(mClipboardType);
if (!clipboardCache) {
mClipboard = nullptr;
return false;
}
return mSequenceNumber == clipboardCache->GetSequenceNumber();
}
auto resultOrError =
mClipboard->GetNativeClipboardSequenceNumber(mClipboardType);
if (resultOrError.isErr()) {
mClipboard = nullptr;
return false;
}
if (mSequenceNumber != resultOrError.unwrap()) {
mClipboard = nullptr;
return false;
}
return true;
}
nsBaseClipboard::ClipboardCache* nsBaseClipboard::GetClipboardCacheIfValid(
int32_t aClipboardType) {
MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
@ -686,32 +554,3 @@ void nsBaseClipboard::ClipboardCache::Clear() {
mTransferable = nullptr;
mSequenceNumber = -1;
}
nsresult nsBaseClipboard::ClipboardCache::GetData(
nsITransferable* aTransferable) const {
MOZ_ASSERT(aTransferable);
MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
// get flavor list that includes all acceptable flavors (including ones
// obtained through conversion)
nsTArray<nsCString> flavors;
if (NS_FAILED(aTransferable->FlavorsTransferableCanImport(flavors))) {
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(mTransferable);
for (const auto& flavor : flavors) {
nsCOMPtr<nsISupports> dataSupports;
if (NS_SUCCEEDED(mTransferable->GetTransferData(
flavor.get(), getter_AddRefs(dataSupports)))) {
MOZ_CLIPBOARD_LOG("%s: getting %s from cache.", __FUNCTION__,
flavor.get());
aTransferable->SetTransferData(flavor.get(), dataSupports);
// XXX we only read the first available type from native clipboard, so
// make cache behave the same.
return NS_OK;
}
}
return NS_ERROR_FAILURE;
}

View file

@ -44,15 +44,17 @@ class nsBaseClipboard : public nsIClipboard {
nsIAsyncSetClipboardData** _retval) override final;
NS_IMETHOD GetData(nsITransferable* aTransferable,
int32_t aWhichClipboard) override final;
NS_IMETHOD AsyncGetData(
const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
nsIAsyncClipboardGetCallback* aCallback) override final;
NS_IMETHOD EmptyClipboard(int32_t aWhichClipboard) override final;
NS_IMETHOD HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
int32_t aWhichClipboard,
bool* aOutResult) override final;
NS_IMETHOD IsClipboardTypeSupported(int32_t aWhichClipboard,
bool* aRetval) override final;
RefPtr<mozilla::GenericPromise> AsyncGetData(
nsITransferable* aTransferable, int32_t aWhichClipboard) override final;
RefPtr<DataFlavorsPromise> AsyncHasDataMatchingFlavors(
const nsTArray<nsCString>& aFlavorList,
int32_t aWhichClipboard) override final;
using GetDataCallback = mozilla::MoveOnlyFunction<void(nsresult)>;
using HasMatchingFlavorsCallback = mozilla::MoveOnlyFunction<void(
@ -111,33 +113,6 @@ class nsBaseClipboard : public nsIClipboard {
nsCOMPtr<nsIAsyncClipboardRequestCallback> mCallback;
};
class AsyncGetClipboardData final : public nsIAsyncGetClipboardData {
public:
AsyncGetClipboardData(int32_t aClipboardType, int32_t aSequenceNumber,
nsTArray<nsCString>&& aFlavors, bool aFromCache,
nsBaseClipboard* aClipboard);
NS_DECL_ISUPPORTS
NS_DECL_NSIASYNCGETCLIPBOARDDATA
private:
virtual ~AsyncGetClipboardData() = default;
bool IsValid();
// The clipboard type defined in nsIClipboard.
const int32_t mClipboardType;
// The sequence number associated with the clipboard content for this
// request. If it doesn't match with the current sequence number in system
// clipboard, this request targets stale data and is deemed invalid.
const int32_t mSequenceNumber;
// List of available data types for clipboard content.
const nsTArray<nsCString> mFlavors;
// Data should be read from cache.
const bool mFromCache;
// This is also used to indicate whether this request is still valid.
RefPtr<nsBaseClipboard> mClipboard;
};
class ClipboardCache final {
public:
~ClipboardCache() {
@ -161,7 +136,6 @@ class nsBaseClipboard : public nsIClipboard {
nsITransferable* GetTransferable() const { return mTransferable; }
nsIClipboardOwner* GetClipboardOwner() const { return mClipboardOwner; }
int32_t GetSequenceNumber() const { return mSequenceNumber; }
nsresult GetData(nsITransferable* aTransferable) const;
private:
nsCOMPtr<nsITransferable> mTransferable;
@ -169,11 +143,6 @@ class nsBaseClipboard : public nsIClipboard {
int32_t mSequenceNumber = -1;
};
void MaybeRetryGetAvailableFlavors(const nsTArray<nsCString>& aFlavorList,
int32_t aWhichClipboard,
nsIAsyncClipboardGetCallback* aCallback,
int32_t aRetryCount);
// Return clipboard cache if the cached data is valid, otherwise clear the
// cached data and returns null.
ClipboardCache* GetClipboardCacheIfValid(int32_t aClipboardType);

View file

@ -7,7 +7,6 @@
#if defined(ACCESSIBILITY) && defined(XP_WIN)
# include "mozilla/a11y/Compatibility.h"
#endif
#include "mozilla/ClipboardReadRequestChild.h"
#include "mozilla/ClipboardWriteRequestChild.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/net/CookieJarSettings.h"
@ -68,149 +67,6 @@ nsClipboardProxy::GetData(nsITransferable* aTransferable,
false /* aFilterUnknownFlavors */);
}
namespace {
class AsyncGetClipboardDataProxy final : public nsIAsyncGetClipboardData {
public:
explicit AsyncGetClipboardDataProxy(ClipboardReadRequestChild* aActor)
: mActor(aActor) {
MOZ_ASSERT(mActor);
}
NS_DECL_ISUPPORTS
NS_DECL_NSIASYNCGETCLIPBOARDDATA
private:
virtual ~AsyncGetClipboardDataProxy() {
MOZ_ASSERT(mActor);
if (mActor->CanSend()) {
PClipboardReadRequestChild::Send__delete__(mActor);
}
};
RefPtr<ClipboardReadRequestChild> mActor;
};
NS_IMPL_ISUPPORTS(AsyncGetClipboardDataProxy, nsIAsyncGetClipboardData)
NS_IMETHODIMP AsyncGetClipboardDataProxy::GetValid(bool* aOutResult) {
MOZ_ASSERT(mActor);
*aOutResult = mActor->CanSend();
return NS_OK;
}
NS_IMETHODIMP AsyncGetClipboardDataProxy::GetFlavorList(
nsTArray<nsCString>& aFlavorList) {
MOZ_ASSERT(mActor);
aFlavorList.AppendElements(mActor->FlavorList());
return NS_OK;
}
NS_IMETHODIMP AsyncGetClipboardDataProxy::GetData(
nsITransferable* aTransferable,
nsIAsyncClipboardRequestCallback* aCallback) {
if (!aTransferable || !aCallback) {
return NS_ERROR_INVALID_ARG;
}
// Get a list of flavors this transferable can import
nsTArray<nsCString> flavors;
nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
if (NS_FAILED(rv)) {
return rv;
}
MOZ_ASSERT(mActor);
// If the requested flavor is not in the list, throw an error.
for (const auto& flavor : flavors) {
if (!mActor->FlavorList().Contains(flavor)) {
return NS_ERROR_FAILURE;
}
}
if (!mActor->CanSend()) {
return aCallback->OnComplete(NS_ERROR_FAILURE);
}
mActor->SendGetData(flavors)->Then(
GetMainThreadSerialEventTarget(), __func__,
/* resolve */
[self = RefPtr{this}, callback = nsCOMPtr{aCallback},
transferable = nsCOMPtr{aTransferable}](
const IPCTransferableDataOrError& aIpcTransferableDataOrError) {
if (aIpcTransferableDataOrError.type() ==
IPCTransferableDataOrError::Tnsresult) {
MOZ_ASSERT(NS_FAILED(aIpcTransferableDataOrError.get_nsresult()));
callback->OnComplete(aIpcTransferableDataOrError.get_nsresult());
return;
}
nsresult rv = nsContentUtils::IPCTransferableDataToTransferable(
aIpcTransferableDataOrError.get_IPCTransferableData(),
false /* aAddDataFlavor */, transferable,
false /* aFilterUnknownFlavors */);
if (NS_FAILED(rv)) {
callback->OnComplete(rv);
return;
}
callback->OnComplete(NS_OK);
},
/* reject */
[callback =
nsCOMPtr{aCallback}](mozilla::ipc::ResponseRejectReason aReason) {
callback->OnComplete(NS_ERROR_FAILURE);
});
return NS_OK;
}
} // namespace
NS_IMETHODIMP nsClipboardProxy::AsyncGetData(
const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
nsIAsyncClipboardGetCallback* aCallback) {
if (!aCallback || aFlavorList.IsEmpty()) {
return NS_ERROR_INVALID_ARG;
}
if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
aWhichClipboard);
return NS_ERROR_FAILURE;
}
ContentChild::GetSingleton()
->SendGetClipboardAsync(aFlavorList, aWhichClipboard)
->Then(
GetMainThreadSerialEventTarget(), __func__,
/* resolve */
[callback = nsCOMPtr{aCallback}](const PClipboardReadRequestOrError&
aClipboardReadRequestOrError) {
if (aClipboardReadRequestOrError.type() ==
PClipboardReadRequestOrError::Tnsresult) {
MOZ_ASSERT(
NS_FAILED(aClipboardReadRequestOrError.get_nsresult()));
callback->OnError(aClipboardReadRequestOrError.get_nsresult());
return;
}
auto asyncGetClipboardData = MakeRefPtr<AsyncGetClipboardDataProxy>(
static_cast<ClipboardReadRequestChild*>(
aClipboardReadRequestOrError.get_PClipboardReadRequest()
.AsChild()
.get()));
callback->OnSuccess(asyncGetClipboardData);
},
/* reject */
[callback = nsCOMPtr{aCallback}](
mozilla::ipc::ResponseRejectReason aReason) {
callback->OnError(NS_ERROR_FAILURE);
});
return NS_OK;
}
NS_IMETHODIMP
nsClipboardProxy::EmptyClipboard(int32_t aWhichClipboard) {
ContentChild::GetSingleton()->SendEmptyClipboard(aWhichClipboard);
@ -256,3 +112,70 @@ void nsClipboardProxy::SetCapabilities(
const ClipboardCapabilities& aClipboardCaps) {
mClipboardCaps = aClipboardCaps;
}
RefPtr<DataFlavorsPromise> nsClipboardProxy::AsyncHasDataMatchingFlavors(
const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard) {
auto promise = MakeRefPtr<DataFlavorsPromise::Private>(__func__);
ContentChild::GetSingleton()
->SendClipboardHasTypesAsync(aFlavorList, aWhichClipboard)
->Then(
GetMainThreadSerialEventTarget(), __func__,
/* resolve */
[promise](nsTArray<nsCString> types) {
promise->Resolve(std::move(types), __func__);
},
/* reject */
[promise](mozilla::ipc::ResponseRejectReason aReason) {
promise->Reject(NS_ERROR_FAILURE, __func__);
});
return promise.forget();
}
RefPtr<GenericPromise> nsClipboardProxy::AsyncGetData(
nsITransferable* aTransferable, int32_t aWhichClipboard) {
if (!aTransferable) {
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
// Get a list of flavors this transferable can import
nsTArray<nsCString> flavors;
nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
if (NS_FAILED(rv)) {
return GenericPromise::CreateAndReject(rv, __func__);
}
nsCOMPtr<nsITransferable> transferable(aTransferable);
auto promise = MakeRefPtr<GenericPromise::Private>(__func__);
ContentChild::GetSingleton()
->SendGetClipboardAsync(flavors, aWhichClipboard)
->Then(
GetMainThreadSerialEventTarget(), __func__,
/* resolve */
[promise, transferable](
const IPCTransferableDataOrError& ipcTransferableDataOrError) {
if (ipcTransferableDataOrError.type() ==
IPCTransferableDataOrError::Tnsresult) {
promise->Reject(ipcTransferableDataOrError.get_nsresult(),
__func__);
return;
}
nsresult rv = nsContentUtils::IPCTransferableDataToTransferable(
ipcTransferableDataOrError.get_IPCTransferableData(),
false /* aAddDataFlavor */, transferable,
false /* aFilterUnknownFlavors */);
if (NS_FAILED(rv)) {
promise->Reject(rv, __func__);
return;
}
promise->Resolve(true, __func__);
},
/* reject */
[promise](mozilla::ipc::ResponseRejectReason aReason) {
promise->Reject(NS_ERROR_FAILURE, __func__);
});
return promise.forget();
}

View file

@ -9,8 +9,17 @@
#include "nsITransferable.idl"
#include "nsIClipboardOwner.idl"
%{C++
#include "mozilla/MozPromise.h"
using DataFlavorsPromise = mozilla::MozPromise<nsTArray<nsCString>, nsresult, true>;
%}
interface nsIArray;
native AsyncGetDataPromise(RefPtr<mozilla::GenericPromise>);
native AsyncDataFlavorsPromise(RefPtr<DataFlavorsPromise>);
[scriptable, builtinclass, uuid(801e2318-c8fa-11ed-afa1-0242ac120002)]
interface nsIAsyncSetClipboardData : nsISupports {
/**
@ -46,54 +55,6 @@ interface nsIAsyncClipboardRequestCallback : nsISupports
void onComplete(in nsresult aResult);
};
[scriptable, builtinclass, uuid(c18ea2f7-6b6f-4a38-9ab3-a8781fdfcc39)]
interface nsIAsyncGetClipboardData : nsISupports {
/**
* Determines whether this request is still valid (e.g., the clipboard content
* associated with this request is not stale).
*/
readonly attribute boolean valid;
/**
* The available flavors in the clipboard.
*/
readonly attribute Array<ACString> flavorList;
/**
* Filters the flavors that `aTransferable` can import (see
* `nsITransferable::flavorsTransferableCanImport`). Every specified flavors
* must exist in `flavorList`, or the request will be rejected. If the request
* remains valid, it retrieves the data for the first flavor. The data is then
* set for `aTransferable`.
*
* @param aTransferable
* The transferable which contains the flavors to be read.
* @param aCallback
* The nsIAsyncClipboardRequestCallback to be invoked once the get
* request is either successfully completed or rejected.
* @result NS_OK if no errors
*/
void getData(in nsITransferable aTransferable,
in nsIAsyncClipboardRequestCallback aCallback);
};
[scriptable, uuid(ce23c1c4-58fd-4c33-8579-fa0796d9652c)]
interface nsIAsyncClipboardGetCallback : nsISupports
{
/**
* Indicates that the clipboard get request has succeeded.
*/
void onSuccess(in nsIAsyncGetClipboardData aAsyncGetClipboardData);
/**
* Indicates that the clipboard get request has rejected.
*
* @param aResult
* The reason for the rejection, can not be NS_OK.
*/
void onError(in nsresult aResult);
};
[scriptable, builtinclass, uuid(ceaa0047-647f-4b8e-ad1c-aff9fa62aa51)]
interface nsIClipboard : nsISupports
{
@ -148,26 +109,6 @@ interface nsIClipboard : nsISupports
void getData ( in nsITransferable aTransferable, in long aWhichClipboard ) ;
/**
* Requests getting data asynchronously from the native clipboard. This does
* not actually retreive the data, but returns a nsIAsyncGetClipboardData
* contains current avaiable data formats. If the native clipboard is
* updated, either by us or other application, the existing
* nsIAsyncGetClipboardData becomes invalid.
*
* @param aFlavorList
* Specific data formats ('flavors') that can be retrieved from the
* clipboard.
* @param aWhichClipboard
* Specifies the clipboard to which this operation applies.
* @param aCallback
* The callback object that will be notified upon completion.
* @result NS_OK if no errors
*/
void asyncGetData(in Array<ACString> aFlavorList,
in long aWhichClipboard,
in nsIAsyncClipboardGetCallback aCallback);
/**
* This empties the clipboard and notifies the clipboard owner.
* This empties the "logical" clipboard. It does not clear the native clipboard.
@ -202,4 +143,29 @@ interface nsIClipboard : nsISupports
*/
[infallible]
boolean isClipboardTypeSupported(in long aWhichClipboard);
/**
* Filters the flavors aTransferable can import (see
* `nsITransferable::flavorsTransferableCanImport`) and gets the data for the
* first flavor. That data is set for aTransferable.
*
* @param aTransferable The transferable
* @param aWhichClipboard Specifies the clipboard to which this operation applies.
* @return MozPromise The returned promise will resolve when the data is ready or reject
* if any error occurs.
*/
[noscript, notxpcom, nostdcall]
AsyncGetDataPromise asyncGetData(in nsITransferable aTransferable, in long aWhichClipboard);
/**
* Check if there is data on the clipboard matching each of the flavors in the
* given list.
*
* @param aFlavorList An array of ASCII strings.
* @param aWhichClipboard Specifies the clipboard to which this operation applies.
* @return MozPromise The returned promise will resolve with the list of matched flavors
* when the check is completed or reject if any error occurs.
*/
[noscript, notxpcom, nostdcall]
AsyncDataFlavorsPromise asyncHasDataMatchingFlavors(in Array<ACString> aFlavorList, in long aWhichClipboard);
};

View file

@ -74,9 +74,6 @@ skip-if = ["toolkit != 'cocoa'"] # Cocoa widget test
["test_clipboard_chrome.html"]
support-files = "file_test_clipboard.js"
["test_clipboard_asyncGetData_chrome.html"]
support-files = "file_test_clipboard_asyncGetData.js"
["test_clipboard_asyncSetData_chrome.html"]
support-files = "file_test_clipboard_asyncSetData.js"

View file

@ -14,25 +14,13 @@ const clipboardTypes = [
clipboard.kSelectionCache,
];
function emptyClipboardData(aType) {
// XXX gtk doesn't support emptying clipboard data which is stored from
// other application (bug 1853884). As a workaround, we set dummy data
// to the clipboard first to ensure the subsequent emptyClipboard call
// works.
if (navigator.platform.includes("Linux")) {
writeStringToClipboard("foo", "text/plain", aType);
}
clipboard.emptyClipboard(aType);
}
function cleanupAllClipboard() {
for (const type of clipboardTypes) {
clipboardTypes.forEach(function (type) {
if (clipboard.isClipboardTypeSupported(type)) {
info(`cleanup clipboard ${type}`);
emptyClipboardData(type);
clipboard.emptyClipboard(type);
}
}
});
}
function generateRandomString() {
@ -65,14 +53,6 @@ function addStringToTransferable(aFlavor, aStr, aTrans) {
aTrans.setTransferData(aFlavor, supportsStr);
}
function updateStringToTransferable(aFlavor, aStr, aTrans) {
let supportsStr = Cc["@mozilla.org/supports-string;1"].createInstance(
Ci.nsISupportsString
);
supportsStr.data = aStr;
aTrans.setTransferData(aFlavor, supportsStr);
}
function writeStringToClipboard(
aStr,
aFlavor,
@ -135,73 +115,3 @@ function getClipboardData(aFlavor, aClipboardType) {
return null;
}
}
function asyncGetClipboardData(aClipboardType) {
return new Promise((resolve, reject) => {
try {
clipboard.asyncGetData(
["text/plain", "text/html", "image/png"],
aClipboardType,
{
QueryInterface: SpecialPowers.ChromeUtils.generateQI([
"nsIAsyncClipboardGetCallback",
]),
// nsIAsyncClipboardGetCallback
onSuccess: SpecialPowers.wrapCallback(function (
aAsyncGetClipboardData
) {
resolve(aAsyncGetClipboardData);
}),
onError: SpecialPowers.wrapCallback(function (aResult) {
reject(aResult);
}),
}
);
} catch (e) {
ok(false, `asyncGetData should not throw`);
reject(e);
}
});
}
function asyncClipboardRequestGetData(aRequest, aFlavor, aThrows = false) {
return new Promise((resolve, reject) => {
var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
Ci.nsITransferable
);
trans.init(null);
trans.addDataFlavor(aFlavor);
try {
aRequest.getData(trans, aResult => {
if (aResult != Cr.NS_OK) {
reject(aResult);
return;
}
try {
var data = SpecialPowers.createBlankObject();
trans.getTransferData(aFlavor, data);
resolve(data.value.QueryInterface(Ci.nsISupportsString).data);
} catch (ex) {
// XXX: should widget set empty string to transferable when there no
// data in system clipboard?
resolve("");
}
});
ok(
!aThrows,
`nsIAsyncGetClipboardData.getData should ${
aThrows ? "throw" : "success"
}`
);
} catch (e) {
ok(
aThrows,
`nsIAsyncGetClipboardData.getData should ${
aThrows ? "throw" : "success"
}`
);
reject(e);
}
});
}

View file

@ -1,160 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from clipboard_helper.js */
"use strict";
clipboardTypes.forEach(function (type) {
if (!clipboard.isClipboardTypeSupported(type)) {
add_task(async function test_clipboard_asyncGetData_not_support() {
info(`Test asyncGetData request throwing on ${type}`);
SimpleTest.doesThrow(
() => clipboard.asyncGetData(["text/plain"], type, {}),
"Passing unsupported clipboard type should throw"
);
});
return;
}
add_task(async function test_clipboard_asyncGetData_throw() {
info(`Test asyncGetData request throwing on ${type}`);
SimpleTest.doesThrow(
() => clipboard.asyncGetData([], type, {}),
"Passing empty flavor list should throw"
);
SimpleTest.doesThrow(
() => clipboard.asyncGetData(["text/plain"], type, null),
"Passing no callback should throw"
);
});
add_task(async function test_clipboard_asyncGetData_no_matched_flavor() {
info(`Test asyncGetData have no matched flavor on ${type}`);
cleanupAllClipboard();
writeRandomStringToClipboard("text/plain", type);
let request = await new Promise(resolve => {
clipboard.asyncGetData(["text/html"], type, {
QueryInterface: SpecialPowers.ChromeUtils.generateQI([
"nsIAsyncClipboardGetCallback",
]),
// nsIAsyncClipboardGetCallback
onSuccess: SpecialPowers.wrapCallback(function (
aAsyncGetClipboardData
) {
resolve(aAsyncGetClipboardData);
}),
});
});
isDeeply(request.flavorList, [], "Check flavorList");
});
add_task(async function test_empty_data() {
info(`Test asyncGetData request with empty data on ${type}`);
cleanupAllClipboard();
let request = await asyncGetClipboardData(type);
isDeeply(request.flavorList, [], "Check flavorList");
await asyncClipboardRequestGetData(request, "text/plain", true).catch(
() => {}
);
});
add_task(async function test_clipboard_asyncGetData_after_write() {
info(`Test asyncGetData request after write on ${type}`);
let str = writeRandomStringToClipboard("text/plain", type);
let request = await asyncGetClipboardData(type);
isDeeply(request.flavorList, ["text/plain"], "Check flavorList");
is(
await asyncClipboardRequestGetData(request, "text/plain"),
str,
"Check data"
);
ok(request.valid, "request should still be valid");
// Requesting a flavor that is not in the list should throw error.
await asyncClipboardRequestGetData(request, "text/html", true).catch(
() => {}
);
ok(request.valid, "request should still be valid");
// Writing a new data should invalid existing get request.
str = writeRandomStringToClipboard("text/plain", type);
await asyncClipboardRequestGetData(request, "text/plain").then(
() => {
ok(false, "asyncClipboardRequestGetData should not success");
},
e => {
ok(true, "asyncClipboardRequestGetData should reject");
}
);
ok(!request.valid, "request should no longer be valid");
info(`check clipboard data again`);
request = await asyncGetClipboardData(type);
isDeeply(request.flavorList, ["text/plain"], "Check flavorList");
is(
await asyncClipboardRequestGetData(request, "text/plain"),
str,
"Check data"
);
cleanupAllClipboard();
});
add_task(async function test_clipboard_asyncGetData_after_empty() {
info(`Test asyncGetData request after empty on ${type}`);
let str = writeRandomStringToClipboard("text/plain", type);
let request = await asyncGetClipboardData(type);
isDeeply(request.flavorList, ["text/plain"], "Check flavorList");
is(
await asyncClipboardRequestGetData(request, "text/plain"),
str,
"Check data"
);
ok(request.valid, "request should still be valid");
// Empty clipboard data
await emptyClipboardData(type);
await asyncClipboardRequestGetData(request, "text/plain").then(
() => {
ok(false, "asyncClipboardRequestGetData should not success");
},
e => {
ok(true, "asyncClipboardRequestGetData should reject");
}
);
ok(!request.valid, "request should no longer be valid");
info(`check clipboard data again`);
request = await asyncGetClipboardData(type);
isDeeply(request.flavorList, [], "Check flavorList");
cleanupAllClipboard();
});
});
add_task(async function test_html_data() {
info(`Test asyncGetData request with html data`);
const html_str = `<img src="https://example.com/oops">`;
writeStringToClipboard(html_str, "text/html", clipboard.kGlobalClipboard);
let request = await asyncGetClipboardData(clipboard.kGlobalClipboard);
isDeeply(request.flavorList, ["text/html"], "Check flavorList");
is(
await asyncClipboardRequestGetData(request, "text/html"),
// On Windows, widget adds extra data into HTML clipboard.
navigator.platform.includes("Win")
? `<html><body>\n<!--StartFragment-->${html_str}<!--EndFragment-->\n</body>\n</html>`
: html_str,
"Check data"
);
// Requesting a flavor that is not in the list should throw error.
await asyncClipboardRequestGetData(request, "text/plain", true).catch(
() => {}
);
});

View file

@ -24,9 +24,6 @@ skip-if = [
]
support-files = ["file_test_clipboard.js"]
["test_clipboard_asyncGetData.html"]
support-files = ["file_test_clipboard_asyncGetData.js"]
["test_clipboard_asyncSetData.html"]
support-files = ["file_test_clipboard_asyncSetData.js"]

View file

@ -1,19 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1852947
-->
<head>
<title>Test for Bug 1852947</title>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="clipboard_helper.js"></script>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<!-- Tests are in file_clipboard_asyncGetData.js -->
<script src="file_test_clipboard_asyncGetData.js"></script>
</body>
</html>

View file

@ -1,19 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1852947
-->
<head>
<title>Test for Bug 1852947</title>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script src="clipboard_helper.js"></script>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<!-- Tests are in file_clipboard_asyncGetData.js -->
<script src="file_test_clipboard_asyncGetData.js"></script>
</body>
</html>

View file

@ -16,7 +16,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1812543
<script class="testbody" type="application/javascript">
function testClipboardCache(aClipboardType, aAsync, aIsSupportGetFromCachedTransferable) {
add_task(async function test_clipboard_get() {
add_task(function test_clipboard_get() {
info(`test_clipboard_get ${aAsync ? "async " : ""}` +
`with pref ${aIsSupportGetFromCachedTransferable ? "enabled" : "disabled"}`);
@ -77,13 +77,13 @@ function testClipboardCache(aClipboardType, aAsync, aIsSupportGetFromCachedTrans
}
info(`Clean all clipboard data`);
await cleanupAllClipboard();
cleanupAllClipboard();
});
}
function runClipboardCacheTests(aIsSupportGetFromCachedTransferable) {
add_task(async function setup() {
await cleanupAllClipboard();
cleanupAllClipboard();
await SpecialPowers.pushPrefEnv({
set: [
[
@ -99,7 +99,7 @@ function runClipboardCacheTests(aIsSupportGetFromCachedTransferable) {
return;
}
add_task(async function test_clipboard_hasDataMatchingFlavors() {
add_task(function test_clipboard_hasDataMatchingFlavors() {
info(`test_clipboard_hasDataMatchingFlavors with pref ` +
`${aIsSupportGetFromCachedTransferable ? "enabled" : "disabled"}`);
@ -165,51 +165,7 @@ function runClipboardCacheTests(aIsSupportGetFromCachedTransferable) {
`Check if there is text/foo flavor on clipboard ${type}`);
// Clean clipboard data.
await cleanupAllClipboard();
});
add_task(async function test_clipboard_asyncGetData() {
const testClipboardData = async function(aRequest, aExpectedData) {
is(aRequest.flavorList.length, Object.keys(aExpectedData).length, "Check flavorList length");
for (const [key, value] of Object.entries(aExpectedData)) {
ok(aRequest.flavorList.includes(key), `${key} should be available`);
is(await asyncClipboardRequestGetData(aRequest, key), value,
`Check ${key} data`);
}
};
info(`test_clipboard_hasDataMatchingFlavors with pref ` +
`${aIsSupportGetFromCachedTransferable ? "enabled" : "disabled"}`);
const clipboardData = { "text/plain": generateRandomString() };
const trans = generateNewTransferable("text/plain", clipboardData["text/plain"]);
info(`Write text/plain data to clipboard ${type}`);
clipboard.setData(trans, null, type);
await testClipboardData(await asyncGetClipboardData(type), clipboardData);
info(`Add text/html data to transferable`);
const htmlString = `<div>${generateRandomString()}</div>`;
addStringToTransferable("text/html", htmlString, trans);
// XXX macOS uses cached transferable to implement kSelectionCache type, too,
// so it behaves differently than other types.
if (aIsSupportGetFromCachedTransferable ||
(type == clipboard.kSelectionCache && !SpecialPowers.isHeadless)) {
clipboardData["text/html"] = htmlString;
}
await testClipboardData(await asyncGetClipboardData(type), clipboardData);
info(`Should not get the data from other clipboard type`);
clipboardTypes.forEach(async function(otherType) {
if (otherType != type &&
clipboard.isClipboardTypeSupported(otherType)) {
info(`Check clipboard type ${otherType}`);
await testClipboardData(await asyncGetClipboardData(otherType), {});
}
});
info(`Check data on clipboard ${type} again`);
await testClipboardData(await asyncGetClipboardData(type), clipboardData);
cleanupAllClipboard();
});
// Test sync set clipboard data.