diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /widget/nsBaseClipboard.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'widget/nsBaseClipboard.cpp')
-rw-r--r-- | widget/nsBaseClipboard.cpp | 1313 |
1 files changed, 1313 insertions, 0 deletions
diff --git a/widget/nsBaseClipboard.cpp b/widget/nsBaseClipboard.cpp new file mode 100644 index 0000000000..40af1de388 --- /dev/null +++ b/widget/nsBaseClipboard.cpp @@ -0,0 +1,1313 @@ +/* -*- 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; +} + +void nsBaseClipboard::AsyncGetDataInternal( + const nsTArray<nsCString>& aFlavorList, int32_t aClipboardType, + mozilla::dom::WindowContext* aRequestingWindowContext, + nsIAsyncClipboardGetCallback* aCallback) { + MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType)); + + 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 (auto* clipboardCache = GetClipboardCacheIfValid(aClipboardType)) { + 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); + } + } + } + + // XXX Do we need to check system clipboard for the flavors that cannot + // be found in cache? + auto asyncGetClipboardData = mozilla::MakeRefPtr<AsyncGetClipboardData>( + aClipboardType, clipboardCache->GetSequenceNumber(), + std::move(results), true, this, 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::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; +} |