fune/widget/nsBaseClipboard.cpp
Edgar Chen 73ebcf0f96 Bug 1884020 - Make reading from clipboard cache returns same flavor order as reading from system clipboard; r=spohl
This basically makes the macOS behave the same as other platform (currently only
macOS would read data from clipboard cache). And also this would ensure behavior
doesn't change if one day we would like to enable reading-from-clipboard-cache
on other platforms as well.

Differential Revision: https://phabricator.services.mozilla.com/D203893
2024-03-14 08:25:02 +00:00

1381 lines
48 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 "ContentAnalysis.h"
#include "mozilla/Components.h"
#include "mozilla/contentanalysis/ContentAnalysisIPCTypes.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/WindowContext.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/MoveOnlyFunction.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Services.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_widget.h"
#include "nsContentUtils.h"
#include "nsFocusManager.h"
#include "nsIClipboardOwner.h"
#include "nsIPromptService.h"
#include "nsISupportsPrimitives.h"
#include "nsError.h"
#include "nsXPCOM.h"
using mozilla::GenericPromise;
using mozilla::LogLevel;
using mozilla::UniquePtr;
using mozilla::dom::BrowsingContext;
using mozilla::dom::CanonicalBrowsingContext;
using mozilla::dom::ClipboardCapabilities;
using mozilla::dom::Document;
static const int32_t kGetAvailableFlavorsRetryCount = 5;
namespace {
struct ClipboardGetRequest {
ClipboardGetRequest(const nsTArray<nsCString>& aFlavorList,
nsIAsyncClipboardGetCallback* aCallback)
: mFlavorList(aFlavorList.Clone()), mCallback(aCallback) {}
const nsTArray<nsCString> mFlavorList;
const nsCOMPtr<nsIAsyncClipboardGetCallback> mCallback;
};
class UserConfirmationRequest final
: public mozilla::dom::PromiseNativeHandler {
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(UserConfirmationRequest)
UserConfirmationRequest(int32_t aClipboardType,
Document* aRequestingChromeDocument,
nsIPrincipal* aRequestingPrincipal,
nsBaseClipboard* aClipboard,
mozilla::dom::WindowContext* aRequestingWindowContext)
: mClipboardType(aClipboardType),
mRequestingChromeDocument(aRequestingChromeDocument),
mRequestingPrincipal(aRequestingPrincipal),
mClipboard(aClipboard),
mRequestingWindowContext(aRequestingWindowContext) {
MOZ_ASSERT(
mClipboard->nsIClipboard::IsClipboardTypeSupported(aClipboardType));
}
void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
mozilla::ErrorResult& aRv) override;
void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
mozilla::ErrorResult& aRv) override;
bool IsEqual(int32_t aClipboardType, Document* aRequestingChromeDocument,
nsIPrincipal* aRequestingPrincipal,
mozilla::dom::WindowContext* aRequestingWindowContext) const {
if (!(ClipboardType() == aClipboardType &&
RequestingChromeDocument() == aRequestingChromeDocument &&
RequestingPrincipal()->Equals(aRequestingPrincipal) &&
(mRequestingWindowContext && aRequestingWindowContext))) {
return false;
}
// Only check requesting window contexts if content analysis is active
nsCOMPtr<nsIContentAnalysis> contentAnalysis =
mozilla::components::nsIContentAnalysis::Service();
if (!contentAnalysis) {
return false;
}
bool contentAnalysisIsActive;
nsresult rv = contentAnalysis->GetIsActive(&contentAnalysisIsActive);
if (MOZ_LIKELY(NS_FAILED(rv) || !contentAnalysisIsActive)) {
return true;
}
return mRequestingWindowContext->Id() == aRequestingWindowContext->Id();
}
int32_t ClipboardType() const { return mClipboardType; }
Document* RequestingChromeDocument() const {
return mRequestingChromeDocument;
}
nsIPrincipal* RequestingPrincipal() const { return mRequestingPrincipal; }
void AddClipboardGetRequest(const nsTArray<nsCString>& aFlavorList,
nsIAsyncClipboardGetCallback* aCallback) {
MOZ_ASSERT(!aFlavorList.IsEmpty());
MOZ_ASSERT(aCallback);
mPendingClipboardGetRequests.AppendElement(
mozilla::MakeUnique<ClipboardGetRequest>(aFlavorList, aCallback));
}
void RejectPendingClipboardGetRequests(nsresult aError) {
MOZ_ASSERT(NS_FAILED(aError));
auto requests = std::move(mPendingClipboardGetRequests);
for (const auto& request : requests) {
MOZ_ASSERT(request);
MOZ_ASSERT(request->mCallback);
request->mCallback->OnError(aError);
}
}
void ProcessPendingClipboardGetRequests() {
auto requests = std::move(mPendingClipboardGetRequests);
for (const auto& request : requests) {
MOZ_ASSERT(request);
MOZ_ASSERT(!request->mFlavorList.IsEmpty());
MOZ_ASSERT(request->mCallback);
mClipboard->AsyncGetDataInternal(request->mFlavorList, mClipboardType,
mRequestingWindowContext,
request->mCallback);
}
}
nsTArray<UniquePtr<ClipboardGetRequest>>& GetPendingClipboardGetRequests() {
return mPendingClipboardGetRequests;
}
private:
~UserConfirmationRequest() = default;
const int32_t mClipboardType;
RefPtr<Document> mRequestingChromeDocument;
const nsCOMPtr<nsIPrincipal> mRequestingPrincipal;
const RefPtr<nsBaseClipboard> mClipboard;
const RefPtr<mozilla::dom::WindowContext> mRequestingWindowContext;
// Track the pending read requests that wait for user confirmation.
nsTArray<UniquePtr<ClipboardGetRequest>> mPendingClipboardGetRequests;
};
NS_IMPL_CYCLE_COLLECTION(UserConfirmationRequest, mRequestingChromeDocument)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UserConfirmationRequest)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(UserConfirmationRequest)
NS_IMPL_CYCLE_COLLECTING_RELEASE(UserConfirmationRequest)
static mozilla::StaticRefPtr<UserConfirmationRequest> sUserConfirmationRequest;
void UserConfirmationRequest::ResolvedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue,
mozilla::ErrorResult& aRv) {
MOZ_DIAGNOSTIC_ASSERT(sUserConfirmationRequest == this);
sUserConfirmationRequest = nullptr;
JS::Rooted<JSObject*> detailObj(aCx, &aValue.toObject());
nsCOMPtr<nsIPropertyBag2> propBag;
nsresult rv = mozilla::dom::UnwrapArg<nsIPropertyBag2>(
aCx, detailObj, getter_AddRefs(propBag));
if (NS_FAILED(rv)) {
RejectPendingClipboardGetRequests(rv);
return;
}
bool result = false;
rv = propBag->GetPropertyAsBool(u"ok"_ns, &result);
if (NS_FAILED(rv)) {
RejectPendingClipboardGetRequests(rv);
return;
}
if (!result) {
RejectPendingClipboardGetRequests(NS_ERROR_DOM_NOT_ALLOWED_ERR);
return;
}
ProcessPendingClipboardGetRequests();
}
void UserConfirmationRequest::RejectedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue,
mozilla::ErrorResult& aRv) {
MOZ_DIAGNOSTIC_ASSERT(sUserConfirmationRequest == this);
sUserConfirmationRequest = nullptr;
RejectPendingClipboardGetRequests(NS_ERROR_FAILURE);
}
} // namespace
NS_IMPL_ISUPPORTS(nsBaseClipboard::AsyncSetClipboardData,
nsIAsyncSetClipboardData)
nsBaseClipboard::AsyncSetClipboardData::AsyncSetClipboardData(
int32_t aClipboardType, nsBaseClipboard* aClipboard,
nsIAsyncClipboardRequestCallback* aCallback)
: mClipboardType(aClipboardType),
mClipboard(aClipboard),
mCallback(aCallback) {
MOZ_ASSERT(mClipboard);
MOZ_ASSERT(
mClipboard->nsIClipboard::IsClipboardTypeSupported(mClipboardType));
}
NS_IMETHODIMP
nsBaseClipboard::AsyncSetClipboardData::SetData(nsITransferable* aTransferable,
nsIClipboardOwner* aOwner) {
MOZ_CLIPBOARD_LOG("AsyncSetClipboardData::SetData (%p): clipboard=%d", this,
mClipboardType);
if (!IsValid()) {
return NS_ERROR_FAILURE;
}
if (MOZ_CLIPBOARD_LOG_ENABLED()) {
nsTArray<nsCString> flavors;
if (NS_SUCCEEDED(aTransferable->FlavorsTransferableCanImport(flavors))) {
for (const auto& flavor : flavors) {
MOZ_CLIPBOARD_LOG(" MIME %s", flavor.get());
}
}
}
MOZ_ASSERT(mClipboard);
MOZ_ASSERT(
mClipboard->nsIClipboard::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
nsBaseClipboard::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 nsBaseClipboard::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<nsIAsyncClipboardRequestCallback> callback =
mCallback.forget()) {
callback->OnComplete(aResult);
}
// Once the callback is notified, setData should not be allowed, so invalidate
// this request.
mClipboard = nullptr;
}
void nsBaseClipboard::RejectPendingAsyncSetDataRequestIfAny(
int32_t aClipboardType) {
MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
auto& request = mPendingWriteRequests[aClipboardType];
if (request) {
request->Abort(NS_ERROR_ABORT);
request = nullptr;
}
}
NS_IMETHODIMP nsBaseClipboard::AsyncSetData(
int32_t aWhichClipboard, nsIAsyncClipboardRequestCallback* aCallback,
nsIAsyncSetClipboardData** _retval) {
MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
*_retval = nullptr;
if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
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;
}
namespace {
class SafeContentAnalysisResultCallback final
: public nsIContentAnalysisCallback {
public:
explicit SafeContentAnalysisResultCallback(
std::function<void(RefPtr<nsIContentAnalysisResult>&&)> aResolver)
: mResolver(std::move(aResolver)) {}
void Callback(RefPtr<nsIContentAnalysisResult>&& aResult) {
MOZ_ASSERT(mResolver, "Called SafeContentAnalysisResultCallback twice!");
if (auto resolver = std::move(mResolver)) {
resolver(std::move(aResult));
}
}
NS_IMETHODIMP ContentResult(nsIContentAnalysisResponse* aResponse) override {
using namespace mozilla::contentanalysis;
RefPtr<ContentAnalysisResult> result =
ContentAnalysisResult::FromContentAnalysisResponse(aResponse);
Callback(result);
return NS_OK;
}
NS_IMETHODIMP Error(nsresult aError) override {
using namespace mozilla::contentanalysis;
Callback(ContentAnalysisResult::FromNoResult(
NoContentAnalysisResult::ERROR_OTHER));
return NS_OK;
}
NS_DECL_THREADSAFE_ISUPPORTS
private:
// Private destructor to force this to be allocated in a RefPtr, which is
// necessary for safe usage.
~SafeContentAnalysisResultCallback() {
MOZ_ASSERT(!mResolver, "SafeContentAnalysisResultCallback never called!");
}
mozilla::MoveOnlyFunction<void(RefPtr<nsIContentAnalysisResult>&&)> mResolver;
};
NS_IMPL_ISUPPORTS(SafeContentAnalysisResultCallback,
nsIContentAnalysisCallback);
} // namespace
// Returning:
// - true means a content analysis request was fired
// - false means there is no text data in the transferable
// - NoContentAnalysisResult means there was an error
static mozilla::Result<bool, mozilla::contentanalysis::NoContentAnalysisResult>
CheckClipboardContentAnalysisAsText(
uint64_t aInnerWindowId, SafeContentAnalysisResultCallback* aResolver,
nsIURI* aDocumentURI, nsIContentAnalysis* aContentAnalysis,
nsITransferable* aTextTrans) {
using namespace mozilla::contentanalysis;
nsCOMPtr<nsISupports> transferData;
if (NS_FAILED(aTextTrans->GetTransferData(kTextMime,
getter_AddRefs(transferData)))) {
return false;
}
nsCOMPtr<nsISupportsString> textData = do_QueryInterface(transferData);
if (MOZ_UNLIKELY(!textData)) {
return false;
}
nsString text;
if (NS_FAILED(textData->GetData(text))) {
return mozilla::Err(NoContentAnalysisResult::ERROR_OTHER);
}
RefPtr<mozilla::dom::WindowGlobalParent> window =
mozilla::dom::WindowGlobalParent::GetByInnerWindowId(aInnerWindowId);
if (!window) {
// The window has gone away in the meantime
return mozilla::Err(NoContentAnalysisResult::ERROR_OTHER);
}
nsCOMPtr<nsIContentAnalysisRequest> contentAnalysisRequest =
new ContentAnalysisRequest(
nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry,
std::move(text), false, EmptyCString(), aDocumentURI,
nsIContentAnalysisRequest::OperationType::eClipboard, window);
nsresult rv = aContentAnalysis->AnalyzeContentRequestCallback(
contentAnalysisRequest, /* aAutoAcknowledge */ true, aResolver);
if (NS_FAILED(rv)) {
return mozilla::Err(NoContentAnalysisResult::ERROR_OTHER);
}
return true;
}
// Returning:
// - true means a content analysis request was fired
// - false means there is no file data in the transferable
// - NoContentAnalysisResult means there was an error
static mozilla::Result<bool, mozilla::contentanalysis::NoContentAnalysisResult>
CheckClipboardContentAnalysisAsFile(
uint64_t aInnerWindowId, SafeContentAnalysisResultCallback* aResolver,
nsIURI* aDocumentURI, nsIContentAnalysis* aContentAnalysis,
nsITransferable* aFileTrans) {
using namespace mozilla::contentanalysis;
nsCOMPtr<nsISupports> transferData;
nsresult rv =
aFileTrans->GetTransferData(kFileMime, getter_AddRefs(transferData));
nsString filePath;
if (NS_SUCCEEDED(rv)) {
if (nsCOMPtr<nsIFile> file = do_QueryInterface(transferData)) {
rv = file->GetPath(filePath);
} else {
MOZ_ASSERT_UNREACHABLE("clipboard data had kFileMime but no nsIFile!");
return mozilla::Err(NoContentAnalysisResult::ERROR_OTHER);
}
}
if (NS_FAILED(rv) || filePath.IsEmpty()) {
return false;
}
RefPtr<mozilla::dom::WindowGlobalParent> window =
mozilla::dom::WindowGlobalParent::GetByInnerWindowId(aInnerWindowId);
if (!window) {
// The window has gone away in the meantime
return mozilla::Err(NoContentAnalysisResult::ERROR_OTHER);
}
// Let the content analysis code calculate the digest
nsCOMPtr<nsIContentAnalysisRequest> contentAnalysisRequest =
new ContentAnalysisRequest(
nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry,
std::move(filePath), true, EmptyCString(), aDocumentURI,
nsIContentAnalysisRequest::OperationType::eCustomDisplayString,
window);
rv = aContentAnalysis->AnalyzeContentRequestCallback(
contentAnalysisRequest,
/* aAutoAcknowledge */ true, aResolver);
if (NS_FAILED(rv)) {
return mozilla::Err(NoContentAnalysisResult::ERROR_OTHER);
}
return true;
}
static void CheckClipboardContentAnalysis(
mozilla::dom::WindowGlobalParent* aWindow, nsITransferable* aTransferable,
SafeContentAnalysisResultCallback* aResolver) {
using namespace mozilla::contentanalysis;
// Content analysis is only needed if an outside webpage has access to
// the data. So, skip content analysis if there is:
// - no associated window (for example, scripted clipboard read by system
// code)
// - the window is a chrome docshell
// - the window is being rendered in the parent process (for example,
// about:support and the like)
if (!aWindow || aWindow->GetBrowsingContext()->IsChrome() ||
aWindow->IsInProcess()) {
aResolver->Callback(ContentAnalysisResult::FromNoResult(
NoContentAnalysisResult::CONTEXT_EXEMPT_FROM_CONTENT_ANALYSIS));
return;
}
nsCOMPtr<nsIContentAnalysis> contentAnalysis =
mozilla::components::nsIContentAnalysis::Service();
if (!contentAnalysis) {
aResolver->Callback(ContentAnalysisResult::FromNoResult(
NoContentAnalysisResult::ERROR_OTHER));
return;
}
bool contentAnalysisIsActive;
nsresult rv = contentAnalysis->GetIsActive(&contentAnalysisIsActive);
if (MOZ_LIKELY(NS_FAILED(rv) || !contentAnalysisIsActive)) {
aResolver->Callback(ContentAnalysisResult::FromNoResult(
NoContentAnalysisResult::CONTENT_ANALYSIS_NOT_ACTIVE));
return;
}
nsCOMPtr<nsIURI> currentURI = aWindow->Canonical()->GetDocumentURI();
uint64_t innerWindowId = aWindow->InnerWindowId();
nsTArray<nsCString> flavors;
rv = aTransferable->FlavorsTransferableCanExport(flavors);
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver->Callback(ContentAnalysisResult::FromNoResult(
NoContentAnalysisResult::ERROR_OTHER));
return;
}
bool keepChecking = true;
if (flavors.Contains(kFileMime)) {
auto fileResult = CheckClipboardContentAnalysisAsFile(
innerWindowId, aResolver, currentURI, contentAnalysis, aTransferable);
if (fileResult.isErr()) {
aResolver->Callback(
ContentAnalysisResult::FromNoResult(fileResult.unwrapErr()));
return;
}
keepChecking = !fileResult.unwrap();
}
if (keepChecking) {
// Failed to get the clipboard data as a file, so try as text
auto textResult = CheckClipboardContentAnalysisAsText(
innerWindowId, aResolver, currentURI, contentAnalysis, aTransferable);
if (textResult.isErr()) {
aResolver->Callback(
ContentAnalysisResult::FromNoResult(textResult.unwrapErr()));
return;
}
if (!textResult.unwrap()) {
// Couldn't get file or text data from this
aResolver->Callback(ContentAnalysisResult::FromNoResult(
NoContentAnalysisResult::ERROR_COULD_NOT_GET_DATA));
return;
}
}
}
static bool CheckClipboardContentAnalysisSync(
mozilla::dom::WindowGlobalParent* aWindow,
const nsCOMPtr<nsITransferable>& trans) {
bool requestDone = false;
RefPtr<nsIContentAnalysisResult> result;
auto callback = mozilla::MakeRefPtr<SafeContentAnalysisResultCallback>(
[&requestDone, &result](RefPtr<nsIContentAnalysisResult>&& aResult) {
result = std::move(aResult);
requestDone = true;
});
CheckClipboardContentAnalysis(aWindow, trans, callback);
mozilla::SpinEventLoopUntil("CheckClipboardContentAnalysisSync"_ns,
[&requestDone]() -> bool { return requestDone; });
return result->GetShouldAllowContent();
}
nsBaseClipboard::nsBaseClipboard(const ClipboardCapabilities& aClipboardCaps)
: mClipboardCaps(aClipboardCaps) {
using mozilla::MakeUnique;
// Initialize clipboard cache.
mCaches[kGlobalClipboard] = MakeUnique<ClipboardCache>();
if (mClipboardCaps.supportsSelectionClipboard()) {
mCaches[kSelectionClipboard] = MakeUnique<ClipboardCache>();
}
if (mClipboardCaps.supportsFindClipboard()) {
mCaches[kFindClipboard] = MakeUnique<ClipboardCache>();
}
if (mClipboardCaps.supportsSelectionCache()) {
mCaches[kSelectionCache] = MakeUnique<ClipboardCache>();
}
}
nsBaseClipboard::~nsBaseClipboard() {
for (auto& request : mPendingWriteRequests) {
if (request) {
request->Abort(NS_ERROR_ABORT);
request = nullptr;
}
}
}
NS_IMPL_ISUPPORTS(nsBaseClipboard, nsIClipboard)
/**
* Sets the transferable object
*
*/
NS_IMETHODIMP nsBaseClipboard::SetData(nsITransferable* aTransferable,
nsIClipboardOwner* aOwner,
int32_t aWhichClipboard) {
NS_ASSERTION(aTransferable, "clipboard given a null transferable");
MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
aWhichClipboard);
return NS_ERROR_FAILURE;
}
if (MOZ_CLIPBOARD_LOG_ENABLED()) {
nsTArray<nsCString> flavors;
if (NS_SUCCEEDED(aTransferable->FlavorsTransferableCanImport(flavors))) {
for (const auto& flavor : flavors) {
MOZ_CLIPBOARD_LOG(" MIME %s", flavor.get());
}
}
}
const auto& clipboardCache = mCaches[aWhichClipboard];
MOZ_ASSERT(clipboardCache);
if (aTransferable == clipboardCache->GetTransferable() &&
aOwner == clipboardCache->GetClipboardOwner()) {
MOZ_CLIPBOARD_LOG("%s: skipping update.", __FUNCTION__);
return NS_OK;
}
clipboardCache->Clear();
nsresult rv = NS_ERROR_FAILURE;
if (aTransferable) {
mIgnoreEmptyNotification = true;
// Reject existing pending asyncSetData request if any.
RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard);
rv = SetNativeClipboardData(aTransferable, aWhichClipboard);
mIgnoreEmptyNotification = false;
}
if (NS_FAILED(rv)) {
MOZ_CLIPBOARD_LOG("%s: setting native clipboard data failed.",
__FUNCTION__);
return rv;
}
auto result = GetNativeClipboardSequenceNumber(aWhichClipboard);
if (result.isErr()) {
MOZ_CLIPBOARD_LOG("%s: getting native clipboard change count failed.",
__FUNCTION__);
return result.unwrapErr();
}
clipboardCache->Update(aTransferable, aOwner, result.unwrap());
return NS_OK;
}
nsresult nsBaseClipboard::GetDataFromClipboardCache(
nsITransferable* aTransferable, int32_t aClipboardType) {
MOZ_ASSERT(aTransferable);
MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
const auto* clipboardCache = GetClipboardCacheIfValid(aClipboardType);
if (!clipboardCache) {
return NS_ERROR_FAILURE;
}
return clipboardCache->GetData(aTransferable);
}
/**
* Gets the transferable object from system clipboard.
*/
NS_IMETHODIMP nsBaseClipboard::GetData(
nsITransferable* aTransferable, int32_t aWhichClipboard,
mozilla::dom::WindowContext* aWindowContext) {
MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
if (!aTransferable) {
NS_ASSERTION(false, "clipboard given a null transferable");
return NS_ERROR_FAILURE;
}
if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
aWhichClipboard);
return NS_ERROR_FAILURE;
}
if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
// 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 (NS_SUCCEEDED(
GetDataFromClipboardCache(aTransferable, aWhichClipboard))) {
// maybe try to fill in more types? Is there a point?
if (!CheckClipboardContentAnalysisSync(aWindowContext->Canonical(),
aTransferable)) {
aTransferable->ClearAllData();
return NS_ERROR_CONTENT_BLOCKED;
}
return NS_OK;
}
// 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
}
nsresult rv = GetNativeClipboardData(aTransferable, aWhichClipboard);
if (NS_FAILED(rv)) {
return rv;
}
if (!CheckClipboardContentAnalysisSync(aWindowContext->Canonical(),
aTransferable)) {
aTransferable->ClearAllData();
return NS_ERROR_CONTENT_BLOCKED;
}
return NS_OK;
}
void nsBaseClipboard::MaybeRetryGetAvailableFlavors(
const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
nsIAsyncClipboardGetCallback* aCallback, int32_t aRetryCount,
mozilla::dom::WindowContext* aRequestingWindowContext) {
// 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,
requestingWindowContext =
RefPtr{aRequestingWindowContext}](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,
requestingWindowContext);
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,
requestingWindowContext);
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,
mozilla::dom::WindowContext* aRequestingWindowContext,
nsIPrincipal* aRequestingPrincipal,
nsIAsyncClipboardGetCallback* aCallback) {
MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
if (!aCallback || !aRequestingPrincipal || 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;
}
// We want to disable security check for automated tests that have the pref
// set to true, or extension that have clipboard read permission.
if (mozilla::StaticPrefs::
dom_events_testing_asyncClipboard_DoNotUseDirectly() ||
nsContentUtils::PrincipalHasPermission(*aRequestingPrincipal,
nsGkAtoms::clipboardRead)) {
AsyncGetDataInternal(aFlavorList, aWhichClipboard, aRequestingWindowContext,
aCallback);
return NS_OK;
}
// If cache data is valid, we are the last ones to put something on the native
// clipboard, then check if the data is from the same-origin page,
if (auto* clipboardCache = GetClipboardCacheIfValid(aWhichClipboard)) {
nsCOMPtr<nsITransferable> trans = clipboardCache->GetTransferable();
MOZ_ASSERT(trans);
if (nsCOMPtr<nsIPrincipal> principal = trans->GetRequestingPrincipal()) {
if (aRequestingPrincipal->Subsumes(principal)) {
MOZ_CLIPBOARD_LOG("%s: native clipboard data is from same-origin page.",
__FUNCTION__);
AsyncGetDataInternal(aFlavorList, aWhichClipboard,
aRequestingWindowContext, aCallback);
return NS_OK;
}
}
}
// TODO: enable showing the "Paste" button in this case; see bug 1773681.
if (aRequestingPrincipal->GetIsAddonOrExpandedAddonPrincipal()) {
MOZ_CLIPBOARD_LOG("%s: Addon without read permission.", __FUNCTION__);
return aCallback->OnError(NS_ERROR_FAILURE);
}
RequestUserConfirmation(aWhichClipboard, aFlavorList,
aRequestingWindowContext, aRequestingPrincipal,
aCallback);
return NS_OK;
}
already_AddRefed<nsIAsyncGetClipboardData>
nsBaseClipboard::MaybeCreateGetRequestFromClipboardCache(
const nsTArray<nsCString>& aFlavorList, int32_t aClipboardType,
mozilla::dom::WindowContext* aRequestingWindowContext) {
MOZ_DIAGNOSTIC_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
if (!mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
return nullptr;
}
// 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.
ClipboardCache* clipboardCache = GetClipboardCacheIfValid(aClipboardType);
if (!clipboardCache) {
return nullptr;
}
nsITransferable* cachedTransferable = clipboardCache->GetTransferable();
MOZ_ASSERT(cachedTransferable);
nsTArray<nsCString> transferableFlavors;
if (NS_FAILED(cachedTransferable->FlavorsTransferableCanExport(
transferableFlavors))) {
return nullptr;
}
nsTArray<nsCString> results;
for (const auto& flavor : aFlavorList) {
for (const auto& transferableFlavor : transferableFlavors) {
// 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);
}
}
}
// XXX Do we need to check system clipboard for the flavors that cannot
// be found in cache?
return mozilla::MakeAndAddRef<AsyncGetClipboardData>(
aClipboardType, clipboardCache->GetSequenceNumber(), std::move(results),
true /* aFromCache */, this, aRequestingWindowContext);
}
void nsBaseClipboard::AsyncGetDataInternal(
const nsTArray<nsCString>& aFlavorList, int32_t aClipboardType,
mozilla::dom::WindowContext* aRequestingWindowContext,
nsIAsyncClipboardGetCallback* aCallback) {
MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
if (nsCOMPtr<nsIAsyncGetClipboardData> asyncGetClipboardData =
MaybeCreateGetRequestFromClipboardCache(aFlavorList, aClipboardType,
aRequestingWindowContext)) {
aCallback->OnSuccess(asyncGetClipboardData);
return;
}
// 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, aClipboardType, aCallback,
kGetAvailableFlavorsRetryCount,
aRequestingWindowContext);
}
NS_IMETHODIMP nsBaseClipboard::GetDataSnapshotSync(
const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
mozilla::dom::WindowContext* aRequestingWindowContext,
nsIAsyncGetClipboardData** _retval) {
MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
*_retval = nullptr;
if (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 (nsCOMPtr<nsIAsyncGetClipboardData> asyncGetClipboardData =
MaybeCreateGetRequestFromClipboardCache(aFlavorList, aWhichClipboard,
aRequestingWindowContext)) {
asyncGetClipboardData.forget(_retval);
return NS_OK;
}
auto sequenceNumberOrError =
GetNativeClipboardSequenceNumber(aWhichClipboard);
if (sequenceNumberOrError.isErr()) {
MOZ_CLIPBOARD_LOG("%s: unable to get sequence number for clipboard %d.",
__FUNCTION__, aWhichClipboard);
return sequenceNumberOrError.unwrapErr();
}
nsTArray<nsCString> results;
for (const auto& flavor : aFlavorList) {
auto resultOrError = HasNativeClipboardDataMatchingFlavors(
AutoTArray<nsCString, 1>{flavor}, aWhichClipboard);
if (resultOrError.isOk() && resultOrError.unwrap()) {
results.AppendElement(flavor);
}
}
*_retval =
mozilla::MakeAndAddRef<AsyncGetClipboardData>(
aWhichClipboard, sequenceNumberOrError.unwrap(), std::move(results),
false /* aFromCache */, this, aRequestingWindowContext)
.take();
return NS_OK;
}
NS_IMETHODIMP nsBaseClipboard::EmptyClipboard(int32_t aWhichClipboard) {
MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
aWhichClipboard);
return NS_ERROR_FAILURE;
}
EmptyNativeClipboardData(aWhichClipboard);
const auto& clipboardCache = mCaches[aWhichClipboard];
MOZ_ASSERT(clipboardCache);
if (mIgnoreEmptyNotification) {
MOZ_DIAGNOSTIC_ASSERT(!clipboardCache->GetTransferable() &&
!clipboardCache->GetClipboardOwner() &&
clipboardCache->GetSequenceNumber() == -1,
"How did we have data in clipboard cache here?");
return NS_OK;
}
clipboardCache->Clear();
return NS_OK;
}
mozilla::Result<nsTArray<nsCString>, nsresult>
nsBaseClipboard::GetFlavorsFromClipboardCache(int32_t aClipboardType) {
MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
const auto* clipboardCache = GetClipboardCacheIfValid(aClipboardType);
if (!clipboardCache) {
return mozilla::Err(NS_ERROR_FAILURE);
}
nsITransferable* cachedTransferable = clipboardCache->GetTransferable();
MOZ_ASSERT(cachedTransferable);
nsTArray<nsCString> flavors;
nsresult rv = cachedTransferable->FlavorsTransferableCanExport(flavors);
if (NS_FAILED(rv)) {
return mozilla::Err(rv);
}
if (MOZ_CLIPBOARD_LOG_ENABLED()) {
MOZ_CLIPBOARD_LOG(" Cached transferable types (nums %zu)\n",
flavors.Length());
for (const auto& flavor : flavors) {
MOZ_CLIPBOARD_LOG(" MIME %s", flavor.get());
}
}
return std::move(flavors);
}
NS_IMETHODIMP
nsBaseClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
int32_t aWhichClipboard,
bool* aOutResult) {
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());
}
}
*aOutResult = false;
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()) {
for (const auto& transferableFlavor : flavorsOrError.unwrap()) {
for (const auto& flavor : aFlavorList) {
if (transferableFlavor.Equals(flavor)) {
MOZ_CLIPBOARD_LOG(" has %s", flavor.get());
*aOutResult = true;
return NS_OK;
}
}
}
}
}
auto resultOrError =
HasNativeClipboardDataMatchingFlavors(aFlavorList, aWhichClipboard);
if (resultOrError.isErr()) {
MOZ_CLIPBOARD_LOG(
"%s: checking native clipboard data matching flavors falied.",
__FUNCTION__);
return resultOrError.unwrapErr();
}
*aOutResult = resultOrError.unwrap();
return NS_OK;
}
NS_IMETHODIMP
nsBaseClipboard::IsClipboardTypeSupported(int32_t aWhichClipboard,
bool* aRetval) {
NS_ENSURE_ARG_POINTER(aRetval);
switch (aWhichClipboard) {
case kGlobalClipboard:
// We always support the global clipboard.
*aRetval = true;
return NS_OK;
case kSelectionClipboard:
*aRetval = mClipboardCaps.supportsSelectionClipboard();
return NS_OK;
case kFindClipboard:
*aRetval = mClipboardCaps.supportsFindClipboard();
return NS_OK;
case kSelectionCache:
*aRetval = mClipboardCaps.supportsSelectionCache();
return NS_OK;
default:
*aRetval = false;
return NS_OK;
}
}
void nsBaseClipboard::AsyncHasNativeClipboardDataMatchingFlavors(
const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
HasMatchingFlavorsCallback&& aCallback) {
MOZ_DIAGNOSTIC_ASSERT(
nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
MOZ_CLIPBOARD_LOG(
"nsBaseClipboard::AsyncHasNativeClipboardDataMatchingFlavors: "
"clipboard=%d",
aWhichClipboard);
nsTArray<nsCString> results;
for (const auto& flavor : aFlavorList) {
auto resultOrError = HasNativeClipboardDataMatchingFlavors(
AutoTArray<nsCString, 1>{flavor}, aWhichClipboard);
if (resultOrError.isOk() && resultOrError.unwrap()) {
results.AppendElement(flavor);
}
}
aCallback(std::move(results));
}
void nsBaseClipboard::AsyncGetNativeClipboardData(
nsITransferable* aTransferable, int32_t aWhichClipboard,
GetDataCallback&& aCallback) {
aCallback(GetNativeClipboardData(aTransferable, aWhichClipboard));
}
void nsBaseClipboard::ClearClipboardCache(int32_t aClipboardType) {
MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
const mozilla::UniquePtr<ClipboardCache>& cache = mCaches[aClipboardType];
MOZ_ASSERT(cache);
cache->Clear();
}
void nsBaseClipboard::RequestUserConfirmation(
int32_t aClipboardType, const nsTArray<nsCString>& aFlavorList,
mozilla::dom::WindowContext* aWindowContext,
nsIPrincipal* aRequestingPrincipal,
nsIAsyncClipboardGetCallback* aCallback) {
MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
MOZ_ASSERT(aCallback);
if (!aWindowContext) {
aCallback->OnError(NS_ERROR_FAILURE);
return;
}
CanonicalBrowsingContext* cbc =
CanonicalBrowsingContext::Cast(aWindowContext->GetBrowsingContext());
MOZ_ASSERT(
cbc->IsContent(),
"Should not require user confirmation when access from chrome window");
RefPtr<CanonicalBrowsingContext> chromeTop = cbc->TopCrossChromeBoundary();
Document* chromeDoc = chromeTop ? chromeTop->GetDocument() : nullptr;
if (!chromeDoc || !chromeDoc->HasFocus(mozilla::IgnoreErrors())) {
MOZ_CLIPBOARD_LOG("%s: reject due to not in the focused window",
__FUNCTION__);
aCallback->OnError(NS_ERROR_FAILURE);
return;
}
mozilla::dom::Element* activeElementInChromeDoc =
chromeDoc->GetActiveElement();
if (activeElementInChromeDoc != cbc->Top()->GetEmbedderElement()) {
// Reject if the request is not from web content that is in the focused tab.
MOZ_CLIPBOARD_LOG("%s: reject due to not in the focused tab", __FUNCTION__);
aCallback->OnError(NS_ERROR_FAILURE);
return;
}
// If there is a pending user confirmation request, check if we could reuse
// it. If not, reject the request.
if (sUserConfirmationRequest) {
if (sUserConfirmationRequest->IsEqual(
aClipboardType, chromeDoc, aRequestingPrincipal, aWindowContext)) {
sUserConfirmationRequest->AddClipboardGetRequest(aFlavorList, aCallback);
return;
}
aCallback->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR);
return;
}
nsresult rv = NS_ERROR_FAILURE;
nsCOMPtr<nsIPromptService> promptService =
do_GetService("@mozilla.org/prompter;1", &rv);
if (NS_FAILED(rv)) {
aCallback->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR);
return;
}
RefPtr<mozilla::dom::Promise> promise;
if (NS_FAILED(promptService->ConfirmUserPaste(aWindowContext->Canonical(),
getter_AddRefs(promise)))) {
aCallback->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR);
return;
}
sUserConfirmationRequest = new UserConfirmationRequest(
aClipboardType, chromeDoc, aRequestingPrincipal, this, aWindowContext);
sUserConfirmationRequest->AddClipboardGetRequest(aFlavorList, aCallback);
promise->AppendNativeHandler(sUserConfirmationRequest);
}
NS_IMPL_ISUPPORTS(nsBaseClipboard::AsyncGetClipboardData,
nsIAsyncGetClipboardData)
nsBaseClipboard::AsyncGetClipboardData::AsyncGetClipboardData(
int32_t aClipboardType, int32_t aSequenceNumber,
nsTArray<nsCString>&& aFlavors, bool aFromCache,
nsBaseClipboard* aClipboard,
mozilla::dom::WindowContext* aRequestingWindowContext)
: mClipboardType(aClipboardType),
mSequenceNumber(aSequenceNumber),
mFlavors(std::move(aFlavors)),
mFromCache(aFromCache),
mClipboard(aClipboard),
mRequestingWindowContext(aRequestingWindowContext) {
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);
auto contentAnalysisCallback =
mozilla::MakeRefPtr<SafeContentAnalysisResultCallback>(
[transferable = nsCOMPtr{aTransferable},
callback = nsCOMPtr{aCallback}](
RefPtr<nsIContentAnalysisResult>&& aResult) {
if (aResult->GetShouldAllowContent()) {
callback->OnComplete(NS_OK);
} else {
transferable->ClearAllData();
callback->OnComplete(NS_ERROR_CONTENT_BLOCKED);
}
});
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);
if (NS_SUCCEEDED(clipboardCache->GetData(aTransferable))) {
CheckClipboardContentAnalysis(mRequestingWindowContext
? mRequestingWindowContext->Canonical()
: nullptr,
aTransferable, contentAnalysisCallback);
return NS_OK;
}
// 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.
}
// 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},
transferable = nsCOMPtr{aTransferable},
contentAnalysisCallback =
std::move(contentAnalysisCallback)](nsresult aResult) mutable {
if (NS_FAILED(aResult)) {
callback->OnComplete(aResult);
return;
}
// `IsValid()` checks the clipboard sequence number to ensure the data
// we are requesting is still valid.
if (!self->IsValid()) {
callback->OnComplete(NS_ERROR_FAILURE);
return;
}
CheckClipboardContentAnalysis(
self->mRequestingWindowContext
? self->mRequestingWindowContext->Canonical()
: nullptr,
transferable, contentAnalysisCallback);
});
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));
const mozilla::UniquePtr<ClipboardCache>& cache = mCaches[aClipboardType];
MOZ_ASSERT(cache);
if (!cache->GetTransferable()) {
MOZ_ASSERT(cache->GetSequenceNumber() == -1);
return nullptr;
}
auto changeCountOrError = GetNativeClipboardSequenceNumber(aClipboardType);
if (changeCountOrError.isErr()) {
return nullptr;
}
if (changeCountOrError.unwrap() != cache->GetSequenceNumber()) {
// Clipboard cache is invalid, clear it.
cache->Clear();
return nullptr;
}
return cache.get();
}
void nsBaseClipboard::ClipboardCache::Clear() {
if (mClipboardOwner) {
mClipboardOwner->LosingOwnership(mTransferable);
mClipboardOwner = nullptr;
}
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;
// XXX Maybe we need special check for image as we always put the image as
// "native" on the clipboard.
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;
}