1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsBaseClipboard.h"
8 #include "ContentAnalysis.h"
9 #include "mozilla/Components.h"
10 #include "mozilla/contentanalysis/ContentAnalysisIPCTypes.h"
11 #include "mozilla/dom/BindingUtils.h"
12 #include "mozilla/dom/CanonicalBrowsingContext.h"
13 #include "mozilla/dom/Document.h"
14 #include "mozilla/dom/Promise.h"
15 #include "mozilla/dom/PromiseNativeHandler.h"
16 #include "mozilla/dom/WindowGlobalParent.h"
17 #include "mozilla/dom/WindowContext.h"
18 #include "mozilla/ErrorResult.h"
19 #include "mozilla/MoveOnlyFunction.h"
20 #include "mozilla/RefPtr.h"
21 #include "mozilla/Services.h"
22 #include "mozilla/SpinEventLoopUntil.h"
23 #include "mozilla/StaticPrefs_dom.h"
24 #include "mozilla/StaticPrefs_widget.h"
25 #include "nsContentUtils.h"
26 #include "nsFocusManager.h"
27 #include "nsIClipboardOwner.h"
28 #include "nsIPromptService.h"
29 #include "nsISupportsPrimitives.h"
33 using mozilla::GenericPromise
;
34 using mozilla::LogLevel
;
35 using mozilla::UniquePtr
;
36 using mozilla::dom::BrowsingContext
;
37 using mozilla::dom::CanonicalBrowsingContext
;
38 using mozilla::dom::ClipboardCapabilities
;
39 using mozilla::dom::Document
;
41 static const int32_t kGetAvailableFlavorsRetryCount
= 5;
45 struct ClipboardGetRequest
{
46 ClipboardGetRequest(const nsTArray
<nsCString
>& aFlavorList
,
47 nsIAsyncClipboardGetCallback
* aCallback
)
48 : mFlavorList(aFlavorList
.Clone()), mCallback(aCallback
) {}
50 const nsTArray
<nsCString
> mFlavorList
;
51 const nsCOMPtr
<nsIAsyncClipboardGetCallback
> mCallback
;
54 class UserConfirmationRequest final
55 : public mozilla::dom::PromiseNativeHandler
{
57 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
58 NS_DECL_CYCLE_COLLECTION_CLASS(UserConfirmationRequest
)
60 UserConfirmationRequest(int32_t aClipboardType
,
61 Document
* aRequestingChromeDocument
,
62 nsIPrincipal
* aRequestingPrincipal
,
63 nsBaseClipboard
* aClipboard
,
64 mozilla::dom::WindowContext
* aRequestingWindowContext
)
65 : mClipboardType(aClipboardType
),
66 mRequestingChromeDocument(aRequestingChromeDocument
),
67 mRequestingPrincipal(aRequestingPrincipal
),
68 mClipboard(aClipboard
),
69 mRequestingWindowContext(aRequestingWindowContext
) {
71 mClipboard
->nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
74 void ResolvedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
,
75 mozilla::ErrorResult
& aRv
) override
;
77 void RejectedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
,
78 mozilla::ErrorResult
& aRv
) override
;
80 bool IsEqual(int32_t aClipboardType
, Document
* aRequestingChromeDocument
,
81 nsIPrincipal
* aRequestingPrincipal
,
82 mozilla::dom::WindowContext
* aRequestingWindowContext
) const {
83 if (!(ClipboardType() == aClipboardType
&&
84 RequestingChromeDocument() == aRequestingChromeDocument
&&
85 RequestingPrincipal()->Equals(aRequestingPrincipal
) &&
86 (mRequestingWindowContext
&& aRequestingWindowContext
))) {
89 // Only check requesting window contexts if content analysis is active
90 nsCOMPtr
<nsIContentAnalysis
> contentAnalysis
=
91 mozilla::components::nsIContentAnalysis::Service();
92 if (!contentAnalysis
) {
96 bool contentAnalysisIsActive
;
97 nsresult rv
= contentAnalysis
->GetIsActive(&contentAnalysisIsActive
);
98 if (MOZ_LIKELY(NS_FAILED(rv
) || !contentAnalysisIsActive
)) {
101 return mRequestingWindowContext
->Id() == aRequestingWindowContext
->Id();
104 int32_t ClipboardType() const { return mClipboardType
; }
106 Document
* RequestingChromeDocument() const {
107 return mRequestingChromeDocument
;
110 nsIPrincipal
* RequestingPrincipal() const { return mRequestingPrincipal
; }
112 void AddClipboardGetRequest(const nsTArray
<nsCString
>& aFlavorList
,
113 nsIAsyncClipboardGetCallback
* aCallback
) {
114 MOZ_ASSERT(!aFlavorList
.IsEmpty());
115 MOZ_ASSERT(aCallback
);
116 mPendingClipboardGetRequests
.AppendElement(
117 mozilla::MakeUnique
<ClipboardGetRequest
>(aFlavorList
, aCallback
));
120 void RejectPendingClipboardGetRequests(nsresult aError
) {
121 MOZ_ASSERT(NS_FAILED(aError
));
122 auto requests
= std::move(mPendingClipboardGetRequests
);
123 for (const auto& request
: requests
) {
125 MOZ_ASSERT(request
->mCallback
);
126 request
->mCallback
->OnError(aError
);
130 void ProcessPendingClipboardGetRequests() {
131 auto requests
= std::move(mPendingClipboardGetRequests
);
132 for (const auto& request
: requests
) {
134 MOZ_ASSERT(!request
->mFlavorList
.IsEmpty());
135 MOZ_ASSERT(request
->mCallback
);
136 mClipboard
->AsyncGetDataInternal(request
->mFlavorList
, mClipboardType
,
137 mRequestingWindowContext
,
142 nsTArray
<UniquePtr
<ClipboardGetRequest
>>& GetPendingClipboardGetRequests() {
143 return mPendingClipboardGetRequests
;
147 ~UserConfirmationRequest() = default;
149 const int32_t mClipboardType
;
150 RefPtr
<Document
> mRequestingChromeDocument
;
151 const nsCOMPtr
<nsIPrincipal
> mRequestingPrincipal
;
152 const RefPtr
<nsBaseClipboard
> mClipboard
;
153 const RefPtr
<mozilla::dom::WindowContext
> mRequestingWindowContext
;
154 // Track the pending read requests that wait for user confirmation.
155 nsTArray
<UniquePtr
<ClipboardGetRequest
>> mPendingClipboardGetRequests
;
158 NS_IMPL_CYCLE_COLLECTION(UserConfirmationRequest
, mRequestingChromeDocument
)
160 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UserConfirmationRequest
)
161 NS_INTERFACE_MAP_ENTRY(nsISupports
)
164 NS_IMPL_CYCLE_COLLECTING_ADDREF(UserConfirmationRequest
)
165 NS_IMPL_CYCLE_COLLECTING_RELEASE(UserConfirmationRequest
)
167 static mozilla::StaticRefPtr
<UserConfirmationRequest
> sUserConfirmationRequest
;
169 void UserConfirmationRequest::ResolvedCallback(JSContext
* aCx
,
170 JS::Handle
<JS::Value
> aValue
,
171 mozilla::ErrorResult
& aRv
) {
172 MOZ_DIAGNOSTIC_ASSERT(sUserConfirmationRequest
== this);
173 sUserConfirmationRequest
= nullptr;
175 JS::Rooted
<JSObject
*> detailObj(aCx
, &aValue
.toObject());
176 nsCOMPtr
<nsIPropertyBag2
> propBag
;
177 nsresult rv
= mozilla::dom::UnwrapArg
<nsIPropertyBag2
>(
178 aCx
, detailObj
, getter_AddRefs(propBag
));
180 RejectPendingClipboardGetRequests(rv
);
185 rv
= propBag
->GetPropertyAsBool(u
"ok"_ns
, &result
);
187 RejectPendingClipboardGetRequests(rv
);
192 RejectPendingClipboardGetRequests(NS_ERROR_DOM_NOT_ALLOWED_ERR
);
196 ProcessPendingClipboardGetRequests();
199 void UserConfirmationRequest::RejectedCallback(JSContext
* aCx
,
200 JS::Handle
<JS::Value
> aValue
,
201 mozilla::ErrorResult
& aRv
) {
202 MOZ_DIAGNOSTIC_ASSERT(sUserConfirmationRequest
== this);
203 sUserConfirmationRequest
= nullptr;
204 RejectPendingClipboardGetRequests(NS_ERROR_FAILURE
);
209 NS_IMPL_ISUPPORTS(nsBaseClipboard::AsyncSetClipboardData
,
210 nsIAsyncSetClipboardData
)
212 nsBaseClipboard::AsyncSetClipboardData::AsyncSetClipboardData(
213 int32_t aClipboardType
, nsBaseClipboard
* aClipboard
,
214 nsIAsyncClipboardRequestCallback
* aCallback
)
215 : mClipboardType(aClipboardType
),
216 mClipboard(aClipboard
),
217 mCallback(aCallback
) {
218 MOZ_ASSERT(mClipboard
);
220 mClipboard
->nsIClipboard::IsClipboardTypeSupported(mClipboardType
));
224 nsBaseClipboard::AsyncSetClipboardData::SetData(nsITransferable
* aTransferable
,
225 nsIClipboardOwner
* aOwner
) {
226 MOZ_CLIPBOARD_LOG("AsyncSetClipboardData::SetData (%p): clipboard=%d", this,
230 return NS_ERROR_FAILURE
;
233 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
234 nsTArray
<nsCString
> flavors
;
235 if (NS_SUCCEEDED(aTransferable
->FlavorsTransferableCanImport(flavors
))) {
236 for (const auto& flavor
: flavors
) {
237 MOZ_CLIPBOARD_LOG(" MIME %s", flavor
.get());
242 MOZ_ASSERT(mClipboard
);
244 mClipboard
->nsIClipboard::IsClipboardTypeSupported(mClipboardType
));
245 MOZ_DIAGNOSTIC_ASSERT(mClipboard
->mPendingWriteRequests
[mClipboardType
] ==
248 RefPtr
<AsyncSetClipboardData
> request
=
249 std::move(mClipboard
->mPendingWriteRequests
[mClipboardType
]);
250 nsresult rv
= mClipboard
->SetData(aTransferable
, aOwner
, mClipboardType
);
251 MaybeNotifyCallback(rv
);
257 nsBaseClipboard::AsyncSetClipboardData::Abort(nsresult aReason
) {
258 // Note: This may be called during destructor, so it should not attempt to
259 // take a reference to mClipboard.
261 if (!IsValid() || !NS_FAILED(aReason
)) {
262 return NS_ERROR_FAILURE
;
265 MaybeNotifyCallback(aReason
);
269 void nsBaseClipboard::AsyncSetClipboardData::MaybeNotifyCallback(
271 // Note: This may be called during destructor, so it should not attempt to
272 // take a reference to mClipboard.
274 MOZ_ASSERT(IsValid());
275 if (nsCOMPtr
<nsIAsyncClipboardRequestCallback
> callback
=
276 mCallback
.forget()) {
277 callback
->OnComplete(aResult
);
279 // Once the callback is notified, setData should not be allowed, so invalidate
281 mClipboard
= nullptr;
284 void nsBaseClipboard::RejectPendingAsyncSetDataRequestIfAny(
285 int32_t aClipboardType
) {
286 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
287 auto& request
= mPendingWriteRequests
[aClipboardType
];
289 request
->Abort(NS_ERROR_ABORT
);
294 NS_IMETHODIMP
nsBaseClipboard::AsyncSetData(
295 int32_t aWhichClipboard
, nsIAsyncClipboardRequestCallback
* aCallback
,
296 nsIAsyncSetClipboardData
** _retval
) {
297 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
300 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
301 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
303 return NS_ERROR_DOM_NOT_SUPPORTED_ERR
;
306 // Reject existing pending AsyncSetData request if any.
307 RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard
);
309 // Create a new AsyncSetClipboardData.
310 RefPtr
<AsyncSetClipboardData
> request
=
311 mozilla::MakeRefPtr
<AsyncSetClipboardData
>(aWhichClipboard
, this,
313 mPendingWriteRequests
[aWhichClipboard
] = request
;
314 request
.forget(_retval
);
319 class SafeContentAnalysisResultCallback final
320 : public nsIContentAnalysisCallback
{
322 explicit SafeContentAnalysisResultCallback(
323 std::function
<void(RefPtr
<nsIContentAnalysisResult
>&&)> aResolver
)
324 : mResolver(std::move(aResolver
)) {}
325 void Callback(RefPtr
<nsIContentAnalysisResult
>&& aResult
) {
326 MOZ_ASSERT(mResolver
, "Called SafeContentAnalysisResultCallback twice!");
327 if (auto resolver
= std::move(mResolver
)) {
328 resolver(std::move(aResult
));
332 NS_IMETHODIMP
ContentResult(nsIContentAnalysisResponse
* aResponse
) override
{
333 using namespace mozilla::contentanalysis
;
334 RefPtr
<ContentAnalysisResult
> result
=
335 ContentAnalysisResult::FromContentAnalysisResponse(aResponse
);
340 NS_IMETHODIMP
Error(nsresult aError
) override
{
341 using namespace mozilla::contentanalysis
;
342 Callback(ContentAnalysisResult::FromNoResult(
343 NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR
));
347 NS_DECL_THREADSAFE_ISUPPORTS
349 // Private destructor to force this to be allocated in a RefPtr, which is
350 // necessary for safe usage.
351 ~SafeContentAnalysisResultCallback() {
352 MOZ_ASSERT(!mResolver
, "SafeContentAnalysisResultCallback never called!");
354 mozilla::MoveOnlyFunction
<void(RefPtr
<nsIContentAnalysisResult
>&&)> mResolver
;
356 NS_IMPL_ISUPPORTS(SafeContentAnalysisResultCallback
,
357 nsIContentAnalysisCallback
);
361 // - true means a content analysis request was fired
362 // - false means there is no text data in the transferable
363 // - NoContentAnalysisResult means there was an error
364 static mozilla::Result
<bool, mozilla::contentanalysis::NoContentAnalysisResult
>
365 CheckClipboardContentAnalysisAsText(
366 uint64_t aInnerWindowId
, SafeContentAnalysisResultCallback
* aResolver
,
367 nsIURI
* aDocumentURI
, nsIContentAnalysis
* aContentAnalysis
,
368 nsITransferable
* aTextTrans
) {
369 using namespace mozilla::contentanalysis
;
371 nsCOMPtr
<nsISupports
> transferData
;
372 if (NS_FAILED(aTextTrans
->GetTransferData(kTextMime
,
373 getter_AddRefs(transferData
)))) {
376 nsCOMPtr
<nsISupportsString
> textData
= do_QueryInterface(transferData
);
377 if (MOZ_UNLIKELY(!textData
)) {
381 if (NS_FAILED(textData
->GetData(text
))) {
382 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR
);
384 if (text
.IsEmpty()) {
385 // Content Analysis doesn't expect to analyze an empty string.
389 RefPtr
<mozilla::dom::WindowGlobalParent
> window
=
390 mozilla::dom::WindowGlobalParent::GetByInnerWindowId(aInnerWindowId
);
392 // The window has gone away in the meantime
393 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR
);
395 nsCOMPtr
<nsIContentAnalysisRequest
> contentAnalysisRequest
=
396 new ContentAnalysisRequest(
397 nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry
,
398 std::move(text
), false, EmptyCString(), aDocumentURI
,
399 nsIContentAnalysisRequest::OperationType::eClipboard
, window
);
400 nsresult rv
= aContentAnalysis
->AnalyzeContentRequestCallback(
401 contentAnalysisRequest
, /* aAutoAcknowledge */ true, aResolver
);
403 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR
);
409 // - true means a content analysis request was fired
410 // - false means there is no file data in the transferable
411 // - NoContentAnalysisResult means there was an error
412 static mozilla::Result
<bool, mozilla::contentanalysis::NoContentAnalysisResult
>
413 CheckClipboardContentAnalysisAsFile(
414 uint64_t aInnerWindowId
, SafeContentAnalysisResultCallback
* aResolver
,
415 nsIURI
* aDocumentURI
, nsIContentAnalysis
* aContentAnalysis
,
416 nsITransferable
* aFileTrans
) {
417 using namespace mozilla::contentanalysis
;
419 nsCOMPtr
<nsISupports
> transferData
;
421 aFileTrans
->GetTransferData(kFileMime
, getter_AddRefs(transferData
));
423 if (NS_SUCCEEDED(rv
)) {
424 if (nsCOMPtr
<nsIFile
> file
= do_QueryInterface(transferData
)) {
425 rv
= file
->GetPath(filePath
);
427 MOZ_ASSERT_UNREACHABLE("clipboard data had kFileMime but no nsIFile!");
428 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR
);
431 if (NS_FAILED(rv
) || filePath
.IsEmpty()) {
434 RefPtr
<mozilla::dom::WindowGlobalParent
> window
=
435 mozilla::dom::WindowGlobalParent::GetByInnerWindowId(aInnerWindowId
);
437 // The window has gone away in the meantime
438 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR
);
440 // Let the content analysis code calculate the digest
441 nsCOMPtr
<nsIContentAnalysisRequest
> contentAnalysisRequest
=
442 new ContentAnalysisRequest(
443 nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry
,
444 std::move(filePath
), true, EmptyCString(), aDocumentURI
,
445 nsIContentAnalysisRequest::OperationType::eCustomDisplayString
,
447 rv
= aContentAnalysis
->AnalyzeContentRequestCallback(
448 contentAnalysisRequest
,
449 /* aAutoAcknowledge */ true, aResolver
);
451 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR
);
456 static void CheckClipboardContentAnalysis(
457 mozilla::dom::WindowGlobalParent
* aWindow
, nsITransferable
* aTransferable
,
458 SafeContentAnalysisResultCallback
* aResolver
) {
459 using namespace mozilla::contentanalysis
;
461 // Content analysis is only needed if an outside webpage has access to
462 // the data. So, skip content analysis if there is:
463 // - no associated window (for example, scripted clipboard read by system
465 // - the window is a chrome docshell
466 // - the window is being rendered in the parent process (for example,
467 // about:support and the like)
468 if (!aWindow
|| aWindow
->GetBrowsingContext()->IsChrome() ||
469 aWindow
->IsInProcess()) {
470 aResolver
->Callback(ContentAnalysisResult::FromNoResult(
471 NoContentAnalysisResult::
472 ALLOW_DUE_TO_CONTEXT_EXEMPT_FROM_CONTENT_ANALYSIS
));
475 nsCOMPtr
<nsIContentAnalysis
> contentAnalysis
=
476 mozilla::components::nsIContentAnalysis::Service();
477 if (!contentAnalysis
) {
478 aResolver
->Callback(ContentAnalysisResult::FromNoResult(
479 NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR
));
483 bool contentAnalysisIsActive
;
484 nsresult rv
= contentAnalysis
->GetIsActive(&contentAnalysisIsActive
);
485 if (MOZ_LIKELY(NS_FAILED(rv
) || !contentAnalysisIsActive
)) {
486 aResolver
->Callback(ContentAnalysisResult::FromNoResult(
487 NoContentAnalysisResult::ALLOW_DUE_TO_CONTENT_ANALYSIS_NOT_ACTIVE
));
491 nsCOMPtr
<nsIURI
> currentURI
= aWindow
->Canonical()->GetDocumentURI();
492 uint64_t innerWindowId
= aWindow
->InnerWindowId();
493 nsTArray
<nsCString
> flavors
;
494 rv
= aTransferable
->FlavorsTransferableCanExport(flavors
);
495 if (NS_WARN_IF(NS_FAILED(rv
))) {
496 aResolver
->Callback(ContentAnalysisResult::FromNoResult(
497 NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR
));
500 bool keepChecking
= true;
501 if (flavors
.Contains(kFileMime
)) {
502 auto fileResult
= CheckClipboardContentAnalysisAsFile(
503 innerWindowId
, aResolver
, currentURI
, contentAnalysis
, aTransferable
);
505 if (fileResult
.isErr()) {
507 ContentAnalysisResult::FromNoResult(fileResult
.unwrapErr()));
510 keepChecking
= !fileResult
.unwrap();
513 // Failed to get the clipboard data as a file, so try as text
514 auto textResult
= CheckClipboardContentAnalysisAsText(
515 innerWindowId
, aResolver
, currentURI
, contentAnalysis
, aTransferable
);
516 if (textResult
.isErr()) {
518 ContentAnalysisResult::FromNoResult(textResult
.unwrapErr()));
521 if (!textResult
.unwrap()) {
522 // Couldn't get file or text data from this
523 aResolver
->Callback(ContentAnalysisResult::FromNoResult(
524 NoContentAnalysisResult::ALLOW_DUE_TO_COULD_NOT_GET_DATA
));
530 static bool CheckClipboardContentAnalysisSync(
531 mozilla::dom::WindowGlobalParent
* aWindow
,
532 const nsCOMPtr
<nsITransferable
>& trans
) {
533 bool requestDone
= false;
534 RefPtr
<nsIContentAnalysisResult
> result
;
535 auto callback
= mozilla::MakeRefPtr
<SafeContentAnalysisResultCallback
>(
536 [&requestDone
, &result
](RefPtr
<nsIContentAnalysisResult
>&& aResult
) {
537 result
= std::move(aResult
);
540 CheckClipboardContentAnalysis(aWindow
, trans
, callback
);
541 mozilla::SpinEventLoopUntil("CheckClipboardContentAnalysisSync"_ns
,
542 [&requestDone
]() -> bool { return requestDone
; });
543 return result
->GetShouldAllowContent();
546 nsBaseClipboard::nsBaseClipboard(const ClipboardCapabilities
& aClipboardCaps
)
547 : mClipboardCaps(aClipboardCaps
) {
548 using mozilla::MakeUnique
;
549 // Initialize clipboard cache.
550 mCaches
[kGlobalClipboard
] = MakeUnique
<ClipboardCache
>();
551 if (mClipboardCaps
.supportsSelectionClipboard()) {
552 mCaches
[kSelectionClipboard
] = MakeUnique
<ClipboardCache
>();
554 if (mClipboardCaps
.supportsFindClipboard()) {
555 mCaches
[kFindClipboard
] = MakeUnique
<ClipboardCache
>();
557 if (mClipboardCaps
.supportsSelectionCache()) {
558 mCaches
[kSelectionCache
] = MakeUnique
<ClipboardCache
>();
562 nsBaseClipboard::~nsBaseClipboard() {
563 for (auto& request
: mPendingWriteRequests
) {
565 request
->Abort(NS_ERROR_ABORT
);
571 NS_IMPL_ISUPPORTS(nsBaseClipboard
, nsIClipboard
)
574 * Sets the transferable object
577 NS_IMETHODIMP
nsBaseClipboard::SetData(nsITransferable
* aTransferable
,
578 nsIClipboardOwner
* aOwner
,
579 int32_t aWhichClipboard
) {
580 NS_ASSERTION(aTransferable
, "clipboard given a null transferable");
582 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
584 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
585 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
587 return NS_ERROR_FAILURE
;
590 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
591 nsTArray
<nsCString
> flavors
;
592 if (NS_SUCCEEDED(aTransferable
->FlavorsTransferableCanImport(flavors
))) {
593 for (const auto& flavor
: flavors
) {
594 MOZ_CLIPBOARD_LOG(" MIME %s", flavor
.get());
599 const auto& clipboardCache
= mCaches
[aWhichClipboard
];
600 MOZ_ASSERT(clipboardCache
);
601 if (aTransferable
== clipboardCache
->GetTransferable() &&
602 aOwner
== clipboardCache
->GetClipboardOwner()) {
603 MOZ_CLIPBOARD_LOG("%s: skipping update.", __FUNCTION__
);
607 clipboardCache
->Clear();
609 nsresult rv
= NS_ERROR_FAILURE
;
611 mIgnoreEmptyNotification
= true;
612 // Reject existing pending asyncSetData request if any.
613 RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard
);
614 rv
= SetNativeClipboardData(aTransferable
, aWhichClipboard
);
615 mIgnoreEmptyNotification
= false;
618 MOZ_CLIPBOARD_LOG("%s: setting native clipboard data failed.",
623 auto result
= GetNativeClipboardSequenceNumber(aWhichClipboard
);
624 if (result
.isErr()) {
625 MOZ_CLIPBOARD_LOG("%s: getting native clipboard change count failed.",
627 return result
.unwrapErr();
630 clipboardCache
->Update(aTransferable
, aOwner
, result
.unwrap());
634 nsresult
nsBaseClipboard::GetDataFromClipboardCache(
635 nsITransferable
* aTransferable
, int32_t aClipboardType
) {
636 MOZ_ASSERT(aTransferable
);
637 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
638 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
640 const auto* clipboardCache
= GetClipboardCacheIfValid(aClipboardType
);
641 if (!clipboardCache
) {
642 return NS_ERROR_FAILURE
;
645 return clipboardCache
->GetData(aTransferable
);
649 * Gets the transferable object from system clipboard.
651 NS_IMETHODIMP
nsBaseClipboard::GetData(
652 nsITransferable
* aTransferable
, int32_t aWhichClipboard
,
653 mozilla::dom::WindowContext
* aWindowContext
) {
654 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
656 if (!aTransferable
) {
657 NS_ASSERTION(false, "clipboard given a null transferable");
658 return NS_ERROR_FAILURE
;
661 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
662 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
664 return NS_ERROR_FAILURE
;
667 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
668 // If we were the last ones to put something on the native clipboard, then
669 // just use the cached transferable. Otherwise clear it because it isn't
670 // relevant any more.
672 GetDataFromClipboardCache(aTransferable
, aWhichClipboard
))) {
673 // maybe try to fill in more types? Is there a point?
674 if (!CheckClipboardContentAnalysisSync(aWindowContext
->Canonical(),
676 aTransferable
->ClearAllData();
677 return NS_ERROR_CONTENT_BLOCKED
;
682 // at this point we can't satisfy the request from cache data so let's look
683 // for things other people put on the system clipboard
685 nsresult rv
= GetNativeClipboardData(aTransferable
, aWhichClipboard
);
689 if (!CheckClipboardContentAnalysisSync(aWindowContext
->Canonical(),
691 aTransferable
->ClearAllData();
692 return NS_ERROR_CONTENT_BLOCKED
;
697 void nsBaseClipboard::MaybeRetryGetAvailableFlavors(
698 const nsTArray
<nsCString
>& aFlavorList
, int32_t aWhichClipboard
,
699 nsIAsyncClipboardGetCallback
* aCallback
, int32_t aRetryCount
,
700 mozilla::dom::WindowContext
* aRequestingWindowContext
) {
701 // Note we have to get the clipboard sequence number first before the actual
702 // read. This is to use it to verify the clipboard data is still the one we
703 // try to read, instead of the later state.
704 auto sequenceNumberOrError
=
705 GetNativeClipboardSequenceNumber(aWhichClipboard
);
706 if (sequenceNumberOrError
.isErr()) {
707 MOZ_CLIPBOARD_LOG("%s: unable to get sequence number for clipboard %d.",
708 __FUNCTION__
, aWhichClipboard
);
709 aCallback
->OnError(sequenceNumberOrError
.unwrapErr());
713 int32_t sequenceNumber
= sequenceNumberOrError
.unwrap();
714 AsyncHasNativeClipboardDataMatchingFlavors(
715 aFlavorList
, aWhichClipboard
,
716 [self
= RefPtr
{this}, callback
= nsCOMPtr
{aCallback
}, aWhichClipboard
,
717 aRetryCount
, flavorList
= aFlavorList
.Clone(), sequenceNumber
,
718 requestingWindowContext
=
719 RefPtr
{aRequestingWindowContext
}](auto aFlavorsOrError
) {
720 if (aFlavorsOrError
.isErr()) {
722 "%s: unable to get available flavors for clipboard %d.",
723 __FUNCTION__
, aWhichClipboard
);
724 callback
->OnError(aFlavorsOrError
.unwrapErr());
728 auto sequenceNumberOrError
=
729 self
->GetNativeClipboardSequenceNumber(aWhichClipboard
);
730 if (sequenceNumberOrError
.isErr()) {
732 "%s: unable to get sequence number for clipboard %d.",
733 __FUNCTION__
, aWhichClipboard
);
734 callback
->OnError(sequenceNumberOrError
.unwrapErr());
738 if (sequenceNumber
== sequenceNumberOrError
.unwrap()) {
739 auto asyncGetClipboardData
=
740 mozilla::MakeRefPtr
<AsyncGetClipboardData
>(
741 aWhichClipboard
, sequenceNumber
,
742 std::move(aFlavorsOrError
.unwrap()), false, self
,
743 requestingWindowContext
);
744 callback
->OnSuccess(asyncGetClipboardData
);
748 if (aRetryCount
> 0) {
750 "%s: clipboard=%d, ignore the data due to the sequence number "
751 "doesn't match, retry (%d) ..",
752 __FUNCTION__
, aWhichClipboard
, aRetryCount
);
753 self
->MaybeRetryGetAvailableFlavors(flavorList
, aWhichClipboard
,
754 callback
, aRetryCount
- 1,
755 requestingWindowContext
);
759 MOZ_DIAGNOSTIC_ASSERT(false, "How can this happen?!?");
760 callback
->OnError(NS_ERROR_FAILURE
);
764 NS_IMETHODIMP
nsBaseClipboard::AsyncGetData(
765 const nsTArray
<nsCString
>& aFlavorList
, int32_t aWhichClipboard
,
766 mozilla::dom::WindowContext
* aRequestingWindowContext
,
767 nsIPrincipal
* aRequestingPrincipal
,
768 nsIAsyncClipboardGetCallback
* aCallback
) {
769 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
771 if (!aCallback
|| !aRequestingPrincipal
|| aFlavorList
.IsEmpty()) {
772 return NS_ERROR_INVALID_ARG
;
775 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
776 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
778 return NS_ERROR_FAILURE
;
781 // We want to disable security check for automated tests that have the pref
782 // set to true, or extension that have clipboard read permission.
783 if (mozilla::StaticPrefs::
784 dom_events_testing_asyncClipboard_DoNotUseDirectly() ||
785 nsContentUtils::PrincipalHasPermission(*aRequestingPrincipal
,
786 nsGkAtoms::clipboardRead
)) {
787 AsyncGetDataInternal(aFlavorList
, aWhichClipboard
, aRequestingWindowContext
,
792 // If cache data is valid, we are the last ones to put something on the native
793 // clipboard, then check if the data is from the same-origin page,
794 if (auto* clipboardCache
= GetClipboardCacheIfValid(aWhichClipboard
)) {
795 nsCOMPtr
<nsITransferable
> trans
= clipboardCache
->GetTransferable();
798 if (nsCOMPtr
<nsIPrincipal
> principal
= trans
->GetRequestingPrincipal()) {
799 if (aRequestingPrincipal
->Subsumes(principal
)) {
800 MOZ_CLIPBOARD_LOG("%s: native clipboard data is from same-origin page.",
802 AsyncGetDataInternal(aFlavorList
, aWhichClipboard
,
803 aRequestingWindowContext
, aCallback
);
809 // TODO: enable showing the "Paste" button in this case; see bug 1773681.
810 if (aRequestingPrincipal
->GetIsAddonOrExpandedAddonPrincipal()) {
811 MOZ_CLIPBOARD_LOG("%s: Addon without read permission.", __FUNCTION__
);
812 return aCallback
->OnError(NS_ERROR_FAILURE
);
815 RequestUserConfirmation(aWhichClipboard
, aFlavorList
,
816 aRequestingWindowContext
, aRequestingPrincipal
,
821 already_AddRefed
<nsIAsyncGetClipboardData
>
822 nsBaseClipboard::MaybeCreateGetRequestFromClipboardCache(
823 const nsTArray
<nsCString
>& aFlavorList
, int32_t aClipboardType
,
824 mozilla::dom::WindowContext
* aRequestingWindowContext
) {
825 MOZ_DIAGNOSTIC_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
827 if (!mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
831 // If we were the last ones to put something on the native clipboard, then
832 // just use the cached transferable. Otherwise clear it because it isn't
833 // relevant any more.
834 ClipboardCache
* clipboardCache
= GetClipboardCacheIfValid(aClipboardType
);
835 if (!clipboardCache
) {
839 nsITransferable
* cachedTransferable
= clipboardCache
->GetTransferable();
840 MOZ_ASSERT(cachedTransferable
);
842 nsTArray
<nsCString
> transferableFlavors
;
843 if (NS_FAILED(cachedTransferable
->FlavorsTransferableCanExport(
844 transferableFlavors
))) {
848 nsTArray
<nsCString
> results
;
849 for (const auto& flavor
: aFlavorList
) {
850 for (const auto& transferableFlavor
: transferableFlavors
) {
851 // XXX We need special check for image as we always put the
852 // image as "native" on the clipboard.
853 if (transferableFlavor
.Equals(flavor
) ||
854 (transferableFlavor
.Equals(kNativeImageMime
) &&
855 nsContentUtils::IsFlavorImage(flavor
))) {
856 MOZ_CLIPBOARD_LOG(" has %s", flavor
.get());
857 results
.AppendElement(flavor
);
862 // XXX Do we need to check system clipboard for the flavors that cannot
863 // be found in cache?
864 return mozilla::MakeAndAddRef
<AsyncGetClipboardData
>(
865 aClipboardType
, clipboardCache
->GetSequenceNumber(), std::move(results
),
866 true /* aFromCache */, this, aRequestingWindowContext
);
869 void nsBaseClipboard::AsyncGetDataInternal(
870 const nsTArray
<nsCString
>& aFlavorList
, int32_t aClipboardType
,
871 mozilla::dom::WindowContext
* aRequestingWindowContext
,
872 nsIAsyncClipboardGetCallback
* aCallback
) {
873 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
875 if (nsCOMPtr
<nsIAsyncGetClipboardData
> asyncGetClipboardData
=
876 MaybeCreateGetRequestFromClipboardCache(aFlavorList
, aClipboardType
,
877 aRequestingWindowContext
)) {
878 aCallback
->OnSuccess(asyncGetClipboardData
);
882 // At this point we can't satisfy the request from cache data so let's
883 // look for things other people put on the system clipboard.
884 MaybeRetryGetAvailableFlavors(aFlavorList
, aClipboardType
, aCallback
,
885 kGetAvailableFlavorsRetryCount
,
886 aRequestingWindowContext
);
889 NS_IMETHODIMP
nsBaseClipboard::GetDataSnapshotSync(
890 const nsTArray
<nsCString
>& aFlavorList
, int32_t aWhichClipboard
,
891 mozilla::dom::WindowContext
* aRequestingWindowContext
,
892 nsIAsyncGetClipboardData
** _retval
) {
893 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
897 if (aFlavorList
.IsEmpty()) {
898 return NS_ERROR_INVALID_ARG
;
901 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
902 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
904 return NS_ERROR_FAILURE
;
907 if (nsCOMPtr
<nsIAsyncGetClipboardData
> asyncGetClipboardData
=
908 MaybeCreateGetRequestFromClipboardCache(aFlavorList
, aWhichClipboard
,
909 aRequestingWindowContext
)) {
910 asyncGetClipboardData
.forget(_retval
);
914 auto sequenceNumberOrError
=
915 GetNativeClipboardSequenceNumber(aWhichClipboard
);
916 if (sequenceNumberOrError
.isErr()) {
917 MOZ_CLIPBOARD_LOG("%s: unable to get sequence number for clipboard %d.",
918 __FUNCTION__
, aWhichClipboard
);
919 return sequenceNumberOrError
.unwrapErr();
922 nsTArray
<nsCString
> results
;
923 for (const auto& flavor
: aFlavorList
) {
924 auto resultOrError
= HasNativeClipboardDataMatchingFlavors(
925 AutoTArray
<nsCString
, 1>{flavor
}, aWhichClipboard
);
926 if (resultOrError
.isOk() && resultOrError
.unwrap()) {
927 results
.AppendElement(flavor
);
932 mozilla::MakeAndAddRef
<AsyncGetClipboardData
>(
933 aWhichClipboard
, sequenceNumberOrError
.unwrap(), std::move(results
),
934 false /* aFromCache */, this, aRequestingWindowContext
)
939 NS_IMETHODIMP
nsBaseClipboard::EmptyClipboard(int32_t aWhichClipboard
) {
940 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
942 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
943 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
945 return NS_ERROR_FAILURE
;
948 EmptyNativeClipboardData(aWhichClipboard
);
950 const auto& clipboardCache
= mCaches
[aWhichClipboard
];
951 MOZ_ASSERT(clipboardCache
);
953 if (mIgnoreEmptyNotification
) {
954 MOZ_DIAGNOSTIC_ASSERT(!clipboardCache
->GetTransferable() &&
955 !clipboardCache
->GetClipboardOwner() &&
956 clipboardCache
->GetSequenceNumber() == -1,
957 "How did we have data in clipboard cache here?");
961 clipboardCache
->Clear();
966 mozilla::Result
<nsTArray
<nsCString
>, nsresult
>
967 nsBaseClipboard::GetFlavorsFromClipboardCache(int32_t aClipboardType
) {
968 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
969 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
971 const auto* clipboardCache
= GetClipboardCacheIfValid(aClipboardType
);
972 if (!clipboardCache
) {
973 return mozilla::Err(NS_ERROR_FAILURE
);
976 nsITransferable
* cachedTransferable
= clipboardCache
->GetTransferable();
977 MOZ_ASSERT(cachedTransferable
);
979 nsTArray
<nsCString
> flavors
;
980 nsresult rv
= cachedTransferable
->FlavorsTransferableCanExport(flavors
);
982 return mozilla::Err(rv
);
985 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
986 MOZ_CLIPBOARD_LOG(" Cached transferable types (nums %zu)\n",
988 for (const auto& flavor
: flavors
) {
989 MOZ_CLIPBOARD_LOG(" MIME %s", flavor
.get());
993 return std::move(flavors
);
997 nsBaseClipboard::HasDataMatchingFlavors(const nsTArray
<nsCString
>& aFlavorList
,
998 int32_t aWhichClipboard
,
1000 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
1001 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
1002 MOZ_CLIPBOARD_LOG(" Asking for content clipboard=%i:\n",
1004 for (const auto& flavor
: aFlavorList
) {
1005 MOZ_CLIPBOARD_LOG(" MIME %s", flavor
.get());
1009 *aOutResult
= false;
1011 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
1012 // First, check if we have valid data in our cached transferable.
1013 auto flavorsOrError
= GetFlavorsFromClipboardCache(aWhichClipboard
);
1014 if (flavorsOrError
.isOk()) {
1015 for (const auto& transferableFlavor
: flavorsOrError
.unwrap()) {
1016 for (const auto& flavor
: aFlavorList
) {
1017 if (transferableFlavor
.Equals(flavor
)) {
1018 MOZ_CLIPBOARD_LOG(" has %s", flavor
.get());
1027 auto resultOrError
=
1028 HasNativeClipboardDataMatchingFlavors(aFlavorList
, aWhichClipboard
);
1029 if (resultOrError
.isErr()) {
1031 "%s: checking native clipboard data matching flavors falied.",
1033 return resultOrError
.unwrapErr();
1036 *aOutResult
= resultOrError
.unwrap();
1041 nsBaseClipboard::IsClipboardTypeSupported(int32_t aWhichClipboard
,
1043 NS_ENSURE_ARG_POINTER(aRetval
);
1044 switch (aWhichClipboard
) {
1045 case kGlobalClipboard
:
1046 // We always support the global clipboard.
1049 case kSelectionClipboard
:
1050 *aRetval
= mClipboardCaps
.supportsSelectionClipboard();
1052 case kFindClipboard
:
1053 *aRetval
= mClipboardCaps
.supportsFindClipboard();
1055 case kSelectionCache
:
1056 *aRetval
= mClipboardCaps
.supportsSelectionCache();
1064 void nsBaseClipboard::AsyncHasNativeClipboardDataMatchingFlavors(
1065 const nsTArray
<nsCString
>& aFlavorList
, int32_t aWhichClipboard
,
1066 HasMatchingFlavorsCallback
&& aCallback
) {
1067 MOZ_DIAGNOSTIC_ASSERT(
1068 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
));
1071 "nsBaseClipboard::AsyncHasNativeClipboardDataMatchingFlavors: "
1075 nsTArray
<nsCString
> results
;
1076 for (const auto& flavor
: aFlavorList
) {
1077 auto resultOrError
= HasNativeClipboardDataMatchingFlavors(
1078 AutoTArray
<nsCString
, 1>{flavor
}, aWhichClipboard
);
1079 if (resultOrError
.isOk() && resultOrError
.unwrap()) {
1080 results
.AppendElement(flavor
);
1083 aCallback(std::move(results
));
1086 void nsBaseClipboard::AsyncGetNativeClipboardData(
1087 nsITransferable
* aTransferable
, int32_t aWhichClipboard
,
1088 GetDataCallback
&& aCallback
) {
1089 aCallback(GetNativeClipboardData(aTransferable
, aWhichClipboard
));
1092 void nsBaseClipboard::ClearClipboardCache(int32_t aClipboardType
) {
1093 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
1094 const mozilla::UniquePtr
<ClipboardCache
>& cache
= mCaches
[aClipboardType
];
1099 void nsBaseClipboard::RequestUserConfirmation(
1100 int32_t aClipboardType
, const nsTArray
<nsCString
>& aFlavorList
,
1101 mozilla::dom::WindowContext
* aWindowContext
,
1102 nsIPrincipal
* aRequestingPrincipal
,
1103 nsIAsyncClipboardGetCallback
* aCallback
) {
1104 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
1105 MOZ_ASSERT(aCallback
);
1107 if (!aWindowContext
) {
1108 aCallback
->OnError(NS_ERROR_FAILURE
);
1112 CanonicalBrowsingContext
* cbc
=
1113 CanonicalBrowsingContext::Cast(aWindowContext
->GetBrowsingContext());
1116 "Should not require user confirmation when access from chrome window");
1118 RefPtr
<CanonicalBrowsingContext
> chromeTop
= cbc
->TopCrossChromeBoundary();
1119 Document
* chromeDoc
= chromeTop
? chromeTop
->GetDocument() : nullptr;
1120 if (!chromeDoc
|| !chromeDoc
->HasFocus(mozilla::IgnoreErrors())) {
1121 MOZ_CLIPBOARD_LOG("%s: reject due to not in the focused window",
1123 aCallback
->OnError(NS_ERROR_FAILURE
);
1127 mozilla::dom::Element
* activeElementInChromeDoc
=
1128 chromeDoc
->GetActiveElement();
1129 if (activeElementInChromeDoc
!= cbc
->Top()->GetEmbedderElement()) {
1130 // Reject if the request is not from web content that is in the focused tab.
1131 MOZ_CLIPBOARD_LOG("%s: reject due to not in the focused tab", __FUNCTION__
);
1132 aCallback
->OnError(NS_ERROR_FAILURE
);
1136 // If there is a pending user confirmation request, check if we could reuse
1137 // it. If not, reject the request.
1138 if (sUserConfirmationRequest
) {
1139 if (sUserConfirmationRequest
->IsEqual(
1140 aClipboardType
, chromeDoc
, aRequestingPrincipal
, aWindowContext
)) {
1141 sUserConfirmationRequest
->AddClipboardGetRequest(aFlavorList
, aCallback
);
1145 aCallback
->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR
);
1149 nsresult rv
= NS_ERROR_FAILURE
;
1150 nsCOMPtr
<nsIPromptService
> promptService
=
1151 do_GetService("@mozilla.org/prompter;1", &rv
);
1152 if (NS_FAILED(rv
)) {
1153 aCallback
->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR
);
1157 RefPtr
<mozilla::dom::Promise
> promise
;
1158 if (NS_FAILED(promptService
->ConfirmUserPaste(aWindowContext
->Canonical(),
1159 getter_AddRefs(promise
)))) {
1160 aCallback
->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR
);
1164 sUserConfirmationRequest
= new UserConfirmationRequest(
1165 aClipboardType
, chromeDoc
, aRequestingPrincipal
, this, aWindowContext
);
1166 sUserConfirmationRequest
->AddClipboardGetRequest(aFlavorList
, aCallback
);
1167 promise
->AppendNativeHandler(sUserConfirmationRequest
);
1170 NS_IMPL_ISUPPORTS(nsBaseClipboard::AsyncGetClipboardData
,
1171 nsIAsyncGetClipboardData
)
1173 nsBaseClipboard::AsyncGetClipboardData::AsyncGetClipboardData(
1174 int32_t aClipboardType
, int32_t aSequenceNumber
,
1175 nsTArray
<nsCString
>&& aFlavors
, bool aFromCache
,
1176 nsBaseClipboard
* aClipboard
,
1177 mozilla::dom::WindowContext
* aRequestingWindowContext
)
1178 : mClipboardType(aClipboardType
),
1179 mSequenceNumber(aSequenceNumber
),
1180 mFlavors(std::move(aFlavors
)),
1181 mFromCache(aFromCache
),
1182 mClipboard(aClipboard
),
1183 mRequestingWindowContext(aRequestingWindowContext
) {
1184 MOZ_ASSERT(mClipboard
);
1186 mClipboard
->nsIClipboard::IsClipboardTypeSupported(mClipboardType
));
1189 NS_IMETHODIMP
nsBaseClipboard::AsyncGetClipboardData::GetValid(
1191 *aOutResult
= IsValid();
1195 NS_IMETHODIMP
nsBaseClipboard::AsyncGetClipboardData::GetFlavorList(
1196 nsTArray
<nsCString
>& aFlavors
) {
1197 aFlavors
.AppendElements(mFlavors
);
1201 NS_IMETHODIMP
nsBaseClipboard::AsyncGetClipboardData::GetData(
1202 nsITransferable
* aTransferable
,
1203 nsIAsyncClipboardRequestCallback
* aCallback
) {
1204 MOZ_CLIPBOARD_LOG("AsyncGetClipboardData::GetData: %p", this);
1206 if (!aTransferable
|| !aCallback
) {
1207 return NS_ERROR_INVALID_ARG
;
1210 nsTArray
<nsCString
> flavors
;
1211 nsresult rv
= aTransferable
->FlavorsTransferableCanImport(flavors
);
1212 if (NS_FAILED(rv
)) {
1216 // If the requested flavor is not in the list, throw an error.
1217 for (const auto& flavor
: flavors
) {
1218 if (!mFlavors
.Contains(flavor
)) {
1219 return NS_ERROR_FAILURE
;
1224 aCallback
->OnComplete(NS_ERROR_FAILURE
);
1228 MOZ_ASSERT(mClipboard
);
1230 auto contentAnalysisCallback
=
1231 mozilla::MakeRefPtr
<SafeContentAnalysisResultCallback
>(
1232 [transferable
= nsCOMPtr
{aTransferable
},
1233 callback
= nsCOMPtr
{aCallback
}](
1234 RefPtr
<nsIContentAnalysisResult
>&& aResult
) {
1235 if (aResult
->GetShouldAllowContent()) {
1236 callback
->OnComplete(NS_OK
);
1238 transferable
->ClearAllData();
1239 callback
->OnComplete(NS_ERROR_CONTENT_BLOCKED
);
1244 const auto* clipboardCache
=
1245 mClipboard
->GetClipboardCacheIfValid(mClipboardType
);
1246 // `IsValid()` above ensures we should get a valid cache and matched
1247 // sequence number here.
1248 MOZ_DIAGNOSTIC_ASSERT(clipboardCache
);
1249 MOZ_DIAGNOSTIC_ASSERT(clipboardCache
->GetSequenceNumber() ==
1251 if (NS_SUCCEEDED(clipboardCache
->GetData(aTransferable
))) {
1252 CheckClipboardContentAnalysis(mRequestingWindowContext
1253 ? mRequestingWindowContext
->Canonical()
1255 aTransferable
, contentAnalysisCallback
);
1259 // At this point we can't satisfy the request from cache data so let's look
1260 // for things other people put on the system clipboard.
1263 // Since this is an async operation, we need to check if the data is still
1264 // valid after we get the result.
1265 mClipboard
->AsyncGetNativeClipboardData(
1266 aTransferable
, mClipboardType
,
1267 [callback
= nsCOMPtr
{aCallback
}, self
= RefPtr
{this},
1268 transferable
= nsCOMPtr
{aTransferable
},
1269 contentAnalysisCallback
=
1270 std::move(contentAnalysisCallback
)](nsresult aResult
) mutable {
1271 if (NS_FAILED(aResult
)) {
1272 callback
->OnComplete(aResult
);
1275 // `IsValid()` checks the clipboard sequence number to ensure the data
1276 // we are requesting is still valid.
1277 if (!self
->IsValid()) {
1278 callback
->OnComplete(NS_ERROR_FAILURE
);
1281 CheckClipboardContentAnalysis(
1282 self
->mRequestingWindowContext
1283 ? self
->mRequestingWindowContext
->Canonical()
1285 transferable
, contentAnalysisCallback
);
1290 bool nsBaseClipboard::AsyncGetClipboardData::IsValid() {
1295 // If the data should from cache, check if cache is still valid or the
1296 // sequence numbers are matched.
1298 const auto* clipboardCache
=
1299 mClipboard
->GetClipboardCacheIfValid(mClipboardType
);
1300 if (!clipboardCache
) {
1301 mClipboard
= nullptr;
1305 return mSequenceNumber
== clipboardCache
->GetSequenceNumber();
1308 auto resultOrError
=
1309 mClipboard
->GetNativeClipboardSequenceNumber(mClipboardType
);
1310 if (resultOrError
.isErr()) {
1311 mClipboard
= nullptr;
1315 if (mSequenceNumber
!= resultOrError
.unwrap()) {
1316 mClipboard
= nullptr;
1323 nsBaseClipboard::ClipboardCache
* nsBaseClipboard::GetClipboardCacheIfValid(
1324 int32_t aClipboardType
) {
1325 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
1327 const mozilla::UniquePtr
<ClipboardCache
>& cache
= mCaches
[aClipboardType
];
1330 if (!cache
->GetTransferable()) {
1331 MOZ_ASSERT(cache
->GetSequenceNumber() == -1);
1335 auto changeCountOrError
= GetNativeClipboardSequenceNumber(aClipboardType
);
1336 if (changeCountOrError
.isErr()) {
1340 if (changeCountOrError
.unwrap() != cache
->GetSequenceNumber()) {
1341 // Clipboard cache is invalid, clear it.
1349 void nsBaseClipboard::ClipboardCache::Clear() {
1350 if (mClipboardOwner
) {
1351 mClipboardOwner
->LosingOwnership(mTransferable
);
1352 mClipboardOwner
= nullptr;
1354 mTransferable
= nullptr;
1355 mSequenceNumber
= -1;
1358 nsresult
nsBaseClipboard::ClipboardCache::GetData(
1359 nsITransferable
* aTransferable
) const {
1360 MOZ_ASSERT(aTransferable
);
1361 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
1363 // get flavor list that includes all acceptable flavors (including ones
1364 // obtained through conversion)
1365 nsTArray
<nsCString
> flavors
;
1366 if (NS_FAILED(aTransferable
->FlavorsTransferableCanImport(flavors
))) {
1367 return NS_ERROR_FAILURE
;
1370 MOZ_ASSERT(mTransferable
);
1371 for (const auto& flavor
: flavors
) {
1372 nsCOMPtr
<nsISupports
> dataSupports
;
1373 // XXX Maybe we need special check for image as we always put the image as
1374 // "native" on the clipboard.
1375 if (NS_SUCCEEDED(mTransferable
->GetTransferData(
1376 flavor
.get(), getter_AddRefs(dataSupports
)))) {
1377 MOZ_CLIPBOARD_LOG("%s: getting %s from cache.", __FUNCTION__
,
1379 aTransferable
->SetTransferData(flavor
.get(), dataSupports
);
1380 // XXX we only read the first available type from native clipboard, so
1381 // make cache behave the same.
1386 return NS_ERROR_FAILURE
;