forked from mirrors/gecko-dev
The Async Clipboard API now allows using arbitrary promises for passing write data, potentially enabling websites to delay writing data to an arbitrary future, which may surprise the user. This patch introduces a solution: a new write request will automatically cancel any previous pending request. To implement that, this patch introduces a new method to nsIClipboard, new XPCOM interfaces, and new IPC to efficiently track individual write requests. Additionally, a new helper base class, ClipboardSetDataHelper, is introduced in widget to facilitate platform code sharing. Differential Revision: https://phabricator.services.mozilla.com/D174090
275 lines
8.4 KiB
C++
275 lines
8.4 KiB
C++
/* -*- 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 "nsBaseClipboard.h"
|
|
|
|
#include "nsIClipboardOwner.h"
|
|
#include "nsError.h"
|
|
#include "nsXPCOM.h"
|
|
|
|
using mozilla::GenericPromise;
|
|
using mozilla::LogLevel;
|
|
|
|
NS_IMPL_ISUPPORTS(ClipboardSetDataHelper::AsyncSetClipboardData,
|
|
nsIAsyncSetClipboardData)
|
|
|
|
ClipboardSetDataHelper::AsyncSetClipboardData::AsyncSetClipboardData(
|
|
int32_t aClipboardType, ClipboardSetDataHelper* aClipboard,
|
|
nsIAsyncSetClipboardDataCallback* aCallback)
|
|
: mClipboardType(aClipboardType),
|
|
mClipboard(aClipboard),
|
|
mCallback(aCallback) {
|
|
MOZ_ASSERT(mClipboard);
|
|
MOZ_ASSERT(mClipboard->IsClipboardTypeSupported(mClipboardType));
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ClipboardSetDataHelper::AsyncSetClipboardData::SetData(
|
|
nsITransferable* aTransferable, nsIClipboardOwner* aOwner) {
|
|
if (!IsValid()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
MOZ_ASSERT(mClipboard);
|
|
MOZ_ASSERT(mClipboard->IsClipboardTypeSupported(mClipboardType));
|
|
MOZ_DIAGNOSTIC_ASSERT(mClipboard->mPendingWriteRequests[mClipboardType] ==
|
|
this);
|
|
|
|
RefPtr<AsyncSetClipboardData> request =
|
|
std::move(mClipboard->mPendingWriteRequests[mClipboardType]);
|
|
nsresult rv = mClipboard->SetData(aTransferable, aOwner, mClipboardType);
|
|
MaybeNotifyCallback(rv);
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ClipboardSetDataHelper::AsyncSetClipboardData::Abort(nsresult aReason) {
|
|
// Note: This may be called during destructor, so it should not attempt to
|
|
// take a reference to mClipboard.
|
|
|
|
if (!IsValid() || !NS_FAILED(aReason)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
MaybeNotifyCallback(aReason);
|
|
return NS_OK;
|
|
}
|
|
|
|
void ClipboardSetDataHelper::AsyncSetClipboardData::MaybeNotifyCallback(
|
|
nsresult aResult) {
|
|
// Note: This may be called during destructor, so it should not attempt to
|
|
// take a reference to mClipboard.
|
|
|
|
MOZ_ASSERT(IsValid());
|
|
if (nsCOMPtr<nsIAsyncSetClipboardDataCallback> callback =
|
|
mCallback.forget()) {
|
|
callback->OnComplete(aResult);
|
|
}
|
|
// Once the callback is notified, setData should not be allowed, so invalidate
|
|
// this request.
|
|
mClipboard = nullptr;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(ClipboardSetDataHelper, nsIClipboard)
|
|
|
|
ClipboardSetDataHelper::~ClipboardSetDataHelper() {
|
|
for (auto& request : mPendingWriteRequests) {
|
|
if (request) {
|
|
request->Abort(NS_ERROR_ABORT);
|
|
request = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClipboardSetDataHelper::RejectPendingAsyncSetDataRequestIfAny(
|
|
int32_t aClipboardType) {
|
|
MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
|
|
auto& request = mPendingWriteRequests[aClipboardType];
|
|
if (request) {
|
|
request->Abort(NS_ERROR_ABORT);
|
|
request = nullptr;
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ClipboardSetDataHelper::SetData(nsITransferable* aTransferable,
|
|
nsIClipboardOwner* aOwner,
|
|
int32_t aWhichClipboard) {
|
|
NS_ENSURE_ARG(aTransferable);
|
|
if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
|
|
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
|
}
|
|
|
|
// Reject existing pending asyncSetData request if any.
|
|
RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard);
|
|
|
|
return SetNativeClipboardData(aTransferable, aOwner, aWhichClipboard);
|
|
}
|
|
|
|
NS_IMETHODIMP ClipboardSetDataHelper::AsyncSetData(
|
|
int32_t aWhichClipboard, nsIAsyncSetClipboardDataCallback* aCallback,
|
|
nsIAsyncSetClipboardData** _retval) {
|
|
*_retval = nullptr;
|
|
if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
|
|
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
|
}
|
|
|
|
// Reject existing pending AsyncSetData request if any.
|
|
RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard);
|
|
|
|
// Create a new AsyncSetClipboardData.
|
|
RefPtr<AsyncSetClipboardData> request =
|
|
mozilla::MakeRefPtr<AsyncSetClipboardData>(aWhichClipboard, this,
|
|
aCallback);
|
|
mPendingWriteRequests[aWhichClipboard] = request;
|
|
request.forget(_retval);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsBaseClipboard::nsBaseClipboard() : mEmptyingForSetData(false) {}
|
|
|
|
nsBaseClipboard::~nsBaseClipboard() {
|
|
EmptyClipboard(kSelectionClipboard);
|
|
EmptyClipboard(kGlobalClipboard);
|
|
EmptyClipboard(kFindClipboard);
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED0(nsBaseClipboard, ClipboardSetDataHelper)
|
|
|
|
/**
|
|
* Sets the transferable object
|
|
*
|
|
*/
|
|
NS_IMETHODIMP nsBaseClipboard::SetData(nsITransferable* aTransferable,
|
|
nsIClipboardOwner* anOwner,
|
|
int32_t aWhichClipboard) {
|
|
NS_ASSERTION(aTransferable, "clipboard given a null transferable");
|
|
|
|
CLIPBOARD_LOG("%s", __FUNCTION__);
|
|
|
|
if (aTransferable == mTransferable && anOwner == mClipboardOwner) {
|
|
CLIPBOARD_LOG("%s: skipping update.", __FUNCTION__);
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!nsIClipboard::IsClipboardTypeSupported(kSelectionClipboard) &&
|
|
!nsIClipboard::IsClipboardTypeSupported(kFindClipboard) &&
|
|
aWhichClipboard != kGlobalClipboard) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mEmptyingForSetData = true;
|
|
if (NS_FAILED(EmptyClipboard(aWhichClipboard))) {
|
|
CLIPBOARD_LOG("%s: emptying clipboard failed.", __FUNCTION__);
|
|
}
|
|
mEmptyingForSetData = false;
|
|
|
|
mClipboardOwner = anOwner;
|
|
mTransferable = aTransferable;
|
|
|
|
nsresult rv = NS_ERROR_FAILURE;
|
|
if (mTransferable) {
|
|
mIgnoreEmptyNotification = true;
|
|
rv = ClipboardSetDataHelper::SetData(aTransferable, anOwner,
|
|
aWhichClipboard);
|
|
mIgnoreEmptyNotification = false;
|
|
}
|
|
if (NS_FAILED(rv)) {
|
|
CLIPBOARD_LOG("%s: setting native clipboard data failed.", __FUNCTION__);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* Gets the transferable object
|
|
*
|
|
*/
|
|
NS_IMETHODIMP nsBaseClipboard::GetData(nsITransferable* aTransferable,
|
|
int32_t aWhichClipboard) {
|
|
NS_ASSERTION(aTransferable, "clipboard given a null transferable");
|
|
|
|
CLIPBOARD_LOG("%s", __FUNCTION__);
|
|
|
|
if (!nsIClipboard::IsClipboardTypeSupported(kSelectionClipboard) &&
|
|
!nsIClipboard::IsClipboardTypeSupported(kFindClipboard) &&
|
|
aWhichClipboard != kGlobalClipboard) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (aTransferable)
|
|
return GetNativeClipboardData(aTransferable, aWhichClipboard);
|
|
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
RefPtr<GenericPromise> nsBaseClipboard::AsyncGetData(
|
|
nsITransferable* aTransferable, int32_t aWhichClipboard) {
|
|
nsresult rv = GetData(aTransferable, aWhichClipboard);
|
|
if (NS_FAILED(rv)) {
|
|
return GenericPromise::CreateAndReject(rv, __func__);
|
|
}
|
|
|
|
return GenericPromise::CreateAndResolve(true, __func__);
|
|
}
|
|
|
|
NS_IMETHODIMP nsBaseClipboard::EmptyClipboard(int32_t aWhichClipboard) {
|
|
CLIPBOARD_LOG("%s: clipboard=%i", __FUNCTION__, aWhichClipboard);
|
|
|
|
if (!nsIClipboard::IsClipboardTypeSupported(kSelectionClipboard) &&
|
|
!nsIClipboard::IsClipboardTypeSupported(kFindClipboard) &&
|
|
aWhichClipboard != kGlobalClipboard) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (mIgnoreEmptyNotification) {
|
|
MOZ_DIAGNOSTIC_ASSERT(false, "How did we get here?");
|
|
return NS_OK;
|
|
}
|
|
|
|
ClearClipboardCache();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
|
|
int32_t aWhichClipboard,
|
|
bool* outResult) {
|
|
*outResult = true; // say we always do.
|
|
return NS_OK;
|
|
}
|
|
|
|
RefPtr<DataFlavorsPromise> nsBaseClipboard::AsyncHasDataMatchingFlavors(
|
|
const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard) {
|
|
nsTArray<nsCString> results;
|
|
for (const auto& flavor : aFlavorList) {
|
|
bool hasMatchingFlavor = false;
|
|
nsresult rv = HasDataMatchingFlavors(AutoTArray<nsCString, 1>{flavor},
|
|
aWhichClipboard, &hasMatchingFlavor);
|
|
if (NS_SUCCEEDED(rv) && hasMatchingFlavor) {
|
|
results.AppendElement(flavor);
|
|
}
|
|
}
|
|
|
|
return DataFlavorsPromise::CreateAndResolve(std::move(results), __func__);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseClipboard::IsClipboardTypeSupported(int32_t aWhichClipboard,
|
|
bool* _retval) {
|
|
NS_ENSURE_ARG_POINTER(_retval);
|
|
// We support global clipboard by default.
|
|
*_retval = kGlobalClipboard == aWhichClipboard;
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsBaseClipboard::ClearClipboardCache() {
|
|
if (mClipboardOwner) {
|
|
mClipboardOwner->LosingOwnership(mTransferable);
|
|
mClipboardOwner = nullptr;
|
|
}
|
|
mTransferable = nullptr;
|
|
}
|