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 mozilla::dom::WindowContext
* aSettingWindowContext
,
215 nsIAsyncClipboardRequestCallback
* aCallback
)
216 : mClipboardType(aClipboardType
),
217 mClipboard(aClipboard
),
218 mWindowContext(aSettingWindowContext
),
219 mCallback(aCallback
) {
220 MOZ_ASSERT(mClipboard
);
222 mClipboard
->nsIClipboard::IsClipboardTypeSupported(mClipboardType
));
226 nsBaseClipboard::AsyncSetClipboardData::SetData(nsITransferable
* aTransferable
,
227 nsIClipboardOwner
* aOwner
) {
228 MOZ_CLIPBOARD_LOG("AsyncSetClipboardData::SetData (%p): clipboard=%d", this,
232 return NS_ERROR_FAILURE
;
235 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
236 nsTArray
<nsCString
> flavors
;
237 if (NS_SUCCEEDED(aTransferable
->FlavorsTransferableCanImport(flavors
))) {
238 for (const auto& flavor
: flavors
) {
239 MOZ_CLIPBOARD_LOG(" MIME %s", flavor
.get());
244 MOZ_ASSERT(mClipboard
);
246 mClipboard
->nsIClipboard::IsClipboardTypeSupported(mClipboardType
));
247 MOZ_DIAGNOSTIC_ASSERT(mClipboard
->mPendingWriteRequests
[mClipboardType
] ==
250 RefPtr
<AsyncSetClipboardData
> request
=
251 std::move(mClipboard
->mPendingWriteRequests
[mClipboardType
]);
252 nsresult rv
= mClipboard
->SetData(aTransferable
, aOwner
, mClipboardType
,
254 MaybeNotifyCallback(rv
);
260 nsBaseClipboard::AsyncSetClipboardData::Abort(nsresult aReason
) {
261 // Note: This may be called during destructor, so it should not attempt to
262 // take a reference to mClipboard.
264 if (!IsValid() || !NS_FAILED(aReason
)) {
265 return NS_ERROR_FAILURE
;
268 MaybeNotifyCallback(aReason
);
272 void nsBaseClipboard::AsyncSetClipboardData::MaybeNotifyCallback(
274 // Note: This may be called during destructor, so it should not attempt to
275 // take a reference to mClipboard.
277 MOZ_ASSERT(IsValid());
278 if (nsCOMPtr
<nsIAsyncClipboardRequestCallback
> callback
=
279 mCallback
.forget()) {
280 callback
->OnComplete(aResult
);
282 // Once the callback is notified, setData should not be allowed, so invalidate
284 mClipboard
= nullptr;
287 void nsBaseClipboard::RejectPendingAsyncSetDataRequestIfAny(
288 int32_t aClipboardType
) {
289 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
290 auto& request
= mPendingWriteRequests
[aClipboardType
];
292 request
->Abort(NS_ERROR_ABORT
);
297 NS_IMETHODIMP
nsBaseClipboard::AsyncSetData(
298 int32_t aWhichClipboard
, mozilla::dom::WindowContext
* aSettingWindowContext
,
299 nsIAsyncClipboardRequestCallback
* aCallback
,
300 nsIAsyncSetClipboardData
** _retval
) {
301 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
304 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
305 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
307 return NS_ERROR_DOM_NOT_SUPPORTED_ERR
;
310 // Reject existing pending AsyncSetData request if any.
311 RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard
);
313 // Create a new AsyncSetClipboardData.
314 RefPtr
<AsyncSetClipboardData
> request
=
315 mozilla::MakeRefPtr
<AsyncSetClipboardData
>(
316 aWhichClipboard
, this, aSettingWindowContext
, aCallback
);
317 mPendingWriteRequests
[aWhichClipboard
] = request
;
318 request
.forget(_retval
);
322 NS_IMPL_ISUPPORTS(nsBaseClipboard::SafeContentAnalysisResultCallback
,
323 nsIContentAnalysisCallback
);
325 nsBaseClipboard::ClipboardContentAnalysisResult
326 nsBaseClipboard::CheckClipboardContentAnalysisAsText(
327 uint64_t aInnerWindowId
, SafeContentAnalysisResultCallback
* aResolver
,
328 nsIURI
* aDocumentURI
, nsIContentAnalysis
* aContentAnalysis
,
329 nsITransferable
* aTextTrans
) {
330 using namespace mozilla::contentanalysis
;
332 nsCOMPtr
<nsISupports
> transferData
;
333 if (NS_FAILED(aTextTrans
->GetTransferData(kTextMime
,
334 getter_AddRefs(transferData
)))) {
337 nsCOMPtr
<nsISupportsString
> textData
= do_QueryInterface(transferData
);
338 if (MOZ_UNLIKELY(!textData
)) {
342 if (NS_FAILED(textData
->GetData(text
))) {
343 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR
);
345 if (text
.IsEmpty()) {
346 // Content Analysis doesn't expect to analyze an empty string.
350 RefPtr
<mozilla::dom::WindowGlobalParent
> window
=
351 mozilla::dom::WindowGlobalParent::GetByInnerWindowId(aInnerWindowId
);
353 // The window has gone away in the meantime
354 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR
);
356 nsCOMPtr
<nsIContentAnalysisRequest
> contentAnalysisRequest
=
357 new ContentAnalysisRequest(
358 nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry
,
359 std::move(text
), false, EmptyCString(), aDocumentURI
,
360 nsIContentAnalysisRequest::OperationType::eClipboard
, window
);
361 nsresult rv
= aContentAnalysis
->AnalyzeContentRequestCallback(
362 contentAnalysisRequest
, /* aAutoAcknowledge */ true, aResolver
);
364 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR
);
369 nsBaseClipboard::ClipboardContentAnalysisResult
370 nsBaseClipboard::CheckClipboardContentAnalysisAsFile(
371 uint64_t aInnerWindowId
, SafeContentAnalysisResultCallback
* aResolver
,
372 nsIURI
* aDocumentURI
, nsIContentAnalysis
* aContentAnalysis
,
373 nsITransferable
* aFileTrans
) {
374 using namespace mozilla::contentanalysis
;
376 nsCOMPtr
<nsISupports
> transferData
;
378 aFileTrans
->GetTransferData(kFileMime
, getter_AddRefs(transferData
));
380 if (NS_SUCCEEDED(rv
)) {
381 if (nsCOMPtr
<nsIFile
> file
= do_QueryInterface(transferData
)) {
382 rv
= file
->GetPath(filePath
);
384 MOZ_ASSERT_UNREACHABLE("clipboard data had kFileMime but no nsIFile!");
385 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR
);
388 if (NS_FAILED(rv
) || filePath
.IsEmpty()) {
391 RefPtr
<mozilla::dom::WindowGlobalParent
> window
=
392 mozilla::dom::WindowGlobalParent::GetByInnerWindowId(aInnerWindowId
);
394 // The window has gone away in the meantime
395 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR
);
397 // Let the content analysis code calculate the digest
398 nsCOMPtr
<nsIContentAnalysisRequest
> contentAnalysisRequest
=
399 new ContentAnalysisRequest(
400 nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry
,
401 std::move(filePath
), true, EmptyCString(), aDocumentURI
,
402 nsIContentAnalysisRequest::OperationType::eCustomDisplayString
,
404 rv
= aContentAnalysis
->AnalyzeContentRequestCallback(
405 contentAnalysisRequest
,
406 /* aAutoAcknowledge */ true, aResolver
);
408 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR
);
413 void nsBaseClipboard::CheckClipboardContentAnalysis(
414 mozilla::dom::WindowGlobalParent
* aWindow
, nsITransferable
* aTransferable
,
415 int32_t aClipboardType
, SafeContentAnalysisResultCallback
* aResolver
) {
416 using namespace mozilla::contentanalysis
;
418 // Content analysis is only needed if an outside webpage has access to
419 // the data. So, skip content analysis if there is:
420 // - no associated window (for example, scripted clipboard read by system
422 // - the window is a chrome docshell
423 // - the window is being rendered in the parent process (for example,
424 // about:support and the like)
425 if (!aWindow
|| aWindow
->GetBrowsingContext()->IsChrome() ||
426 aWindow
->IsInProcess()) {
427 aResolver
->Callback(ContentAnalysisResult::FromNoResult(
428 NoContentAnalysisResult::
429 ALLOW_DUE_TO_CONTEXT_EXEMPT_FROM_CONTENT_ANALYSIS
));
432 nsCOMPtr
<nsIContentAnalysis
> contentAnalysis
=
433 mozilla::components::nsIContentAnalysis::Service();
434 if (!contentAnalysis
) {
435 aResolver
->Callback(ContentAnalysisResult::FromNoResult(
436 NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR
));
440 bool contentAnalysisIsActive
;
441 nsresult rv
= contentAnalysis
->GetIsActive(&contentAnalysisIsActive
);
442 if (MOZ_LIKELY(NS_FAILED(rv
) || !contentAnalysisIsActive
)) {
443 aResolver
->Callback(ContentAnalysisResult::FromNoResult(
444 NoContentAnalysisResult::ALLOW_DUE_TO_CONTENT_ANALYSIS_NOT_ACTIVE
));
448 uint64_t innerWindowId
= aWindow
->InnerWindowId();
449 if (mozilla::StaticPrefs::
450 browser_contentanalysis_bypass_for_same_tab_operations()) {
451 const auto* clipboardCache
= GetClipboardCacheIfValid(aClipboardType
);
452 if (clipboardCache
) {
453 if (clipboardCache
->GetInnerWindowId().isSome() &&
454 *(clipboardCache
->GetInnerWindowId()) == innerWindowId
) {
455 // If the same page copied this data to the clipboard (and the above
456 // preference is set) we can skip content analysis and immediately allow
458 aResolver
->Callback(ContentAnalysisResult::FromNoResult(
459 NoContentAnalysisResult::ALLOW_DUE_TO_SAME_TAB_SOURCE
));
465 nsCOMPtr
<nsIURI
> currentURI
= aWindow
->Canonical()->GetDocumentURI();
466 nsTArray
<nsCString
> flavors
;
467 rv
= aTransferable
->FlavorsTransferableCanExport(flavors
);
468 if (NS_WARN_IF(NS_FAILED(rv
))) {
469 aResolver
->Callback(ContentAnalysisResult::FromNoResult(
470 NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR
));
473 bool keepChecking
= true;
474 if (flavors
.Contains(kFileMime
)) {
475 auto fileResult
= CheckClipboardContentAnalysisAsFile(
476 innerWindowId
, aResolver
, currentURI
, contentAnalysis
, aTransferable
);
478 if (fileResult
.isErr()) {
480 ContentAnalysisResult::FromNoResult(fileResult
.unwrapErr()));
483 keepChecking
= !fileResult
.unwrap();
486 // Failed to get the clipboard data as a file, so try as text
487 auto textResult
= CheckClipboardContentAnalysisAsText(
488 innerWindowId
, aResolver
, currentURI
, contentAnalysis
, aTransferable
);
489 if (textResult
.isErr()) {
491 ContentAnalysisResult::FromNoResult(textResult
.unwrapErr()));
494 if (!textResult
.unwrap()) {
495 // Couldn't get file or text data from this
496 aResolver
->Callback(ContentAnalysisResult::FromNoResult(
497 NoContentAnalysisResult::ALLOW_DUE_TO_COULD_NOT_GET_DATA
));
503 bool nsBaseClipboard::CheckClipboardContentAnalysisSync(
504 mozilla::dom::WindowGlobalParent
* aWindow
,
505 const nsCOMPtr
<nsITransferable
>& trans
, int32_t aClipboardType
) {
506 bool requestDone
= false;
507 RefPtr
<nsIContentAnalysisResult
> result
;
508 auto callback
= mozilla::MakeRefPtr
<SafeContentAnalysisResultCallback
>(
509 [&requestDone
, &result
](RefPtr
<nsIContentAnalysisResult
>&& aResult
) {
510 result
= std::move(aResult
);
513 CheckClipboardContentAnalysis(aWindow
, trans
, aClipboardType
, callback
);
514 mozilla::SpinEventLoopUntil("CheckClipboardContentAnalysisSync"_ns
,
515 [&requestDone
]() -> bool { return requestDone
; });
516 return result
->GetShouldAllowContent();
519 nsBaseClipboard::nsBaseClipboard(const ClipboardCapabilities
& aClipboardCaps
)
520 : mClipboardCaps(aClipboardCaps
) {
521 using mozilla::MakeUnique
;
522 // Initialize clipboard cache.
523 mCaches
[kGlobalClipboard
] = MakeUnique
<ClipboardCache
>();
524 if (mClipboardCaps
.supportsSelectionClipboard()) {
525 mCaches
[kSelectionClipboard
] = MakeUnique
<ClipboardCache
>();
527 if (mClipboardCaps
.supportsFindClipboard()) {
528 mCaches
[kFindClipboard
] = MakeUnique
<ClipboardCache
>();
530 if (mClipboardCaps
.supportsSelectionCache()) {
531 mCaches
[kSelectionCache
] = MakeUnique
<ClipboardCache
>();
535 nsBaseClipboard::~nsBaseClipboard() {
536 for (auto& request
: mPendingWriteRequests
) {
538 request
->Abort(NS_ERROR_ABORT
);
544 NS_IMPL_ISUPPORTS(nsBaseClipboard
, nsIClipboard
)
547 * Sets the transferable object
550 NS_IMETHODIMP
nsBaseClipboard::SetData(
551 nsITransferable
* aTransferable
, nsIClipboardOwner
* aOwner
,
552 int32_t aWhichClipboard
, mozilla::dom::WindowContext
* aWindowContext
) {
553 NS_ASSERTION(aTransferable
, "clipboard given a null transferable");
555 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
557 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
558 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
560 return NS_ERROR_FAILURE
;
563 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
564 nsTArray
<nsCString
> flavors
;
565 if (NS_SUCCEEDED(aTransferable
->FlavorsTransferableCanImport(flavors
))) {
566 for (const auto& flavor
: flavors
) {
567 MOZ_CLIPBOARD_LOG(" MIME %s", flavor
.get());
572 const auto& clipboardCache
= mCaches
[aWhichClipboard
];
573 MOZ_ASSERT(clipboardCache
);
574 if (aTransferable
== clipboardCache
->GetTransferable() &&
575 aOwner
== clipboardCache
->GetClipboardOwner()) {
576 MOZ_CLIPBOARD_LOG("%s: skipping update.", __FUNCTION__
);
580 clipboardCache
->Clear();
582 nsresult rv
= NS_ERROR_FAILURE
;
584 mIgnoreEmptyNotification
= true;
585 // Reject existing pending asyncSetData request if any.
586 RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard
);
587 rv
= SetNativeClipboardData(aTransferable
, aWhichClipboard
);
588 mIgnoreEmptyNotification
= false;
591 MOZ_CLIPBOARD_LOG("%s: setting native clipboard data failed.",
596 auto result
= GetNativeClipboardSequenceNumber(aWhichClipboard
);
597 if (result
.isErr()) {
598 MOZ_CLIPBOARD_LOG("%s: getting native clipboard change count failed.",
600 return result
.unwrapErr();
603 clipboardCache
->Update(aTransferable
, aOwner
, result
.unwrap(),
605 ? mozilla::Some(aWindowContext
->InnerWindowId())
606 : mozilla::Nothing());
610 nsresult
nsBaseClipboard::GetDataFromClipboardCache(
611 nsITransferable
* aTransferable
, int32_t aClipboardType
) {
612 MOZ_ASSERT(aTransferable
);
613 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
615 const auto* clipboardCache
= GetClipboardCacheIfValid(aClipboardType
);
616 if (!clipboardCache
) {
617 return NS_ERROR_FAILURE
;
619 return clipboardCache
->GetData(aTransferable
);
623 * Gets the transferable object from system clipboard.
625 NS_IMETHODIMP
nsBaseClipboard::GetData(
626 nsITransferable
* aTransferable
, int32_t aWhichClipboard
,
627 mozilla::dom::WindowContext
* aWindowContext
) {
628 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
630 if (!aTransferable
) {
631 NS_ASSERTION(false, "clipboard given a null transferable");
632 return NS_ERROR_FAILURE
;
635 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
636 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
638 return NS_ERROR_FAILURE
;
641 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
642 // If we were the last ones to put something on the native clipboard, then
643 // just use the cached transferable. Otherwise clear it because it isn't
644 // relevant any more.
646 GetDataFromClipboardCache(aTransferable
, aWhichClipboard
))) {
647 // maybe try to fill in more types? Is there a point?
648 if (!CheckClipboardContentAnalysisSync(aWindowContext
->Canonical(),
649 aTransferable
, aWhichClipboard
)) {
650 aTransferable
->ClearAllData();
651 return NS_ERROR_CONTENT_BLOCKED
;
656 // at this point we can't satisfy the request from cache data so let's look
657 // for things other people put on the system clipboard
659 nsresult rv
= GetNativeClipboardData(aTransferable
, aWhichClipboard
);
663 if (!CheckClipboardContentAnalysisSync(aWindowContext
->Canonical(),
664 aTransferable
, aWhichClipboard
)) {
665 aTransferable
->ClearAllData();
666 return NS_ERROR_CONTENT_BLOCKED
;
671 void nsBaseClipboard::MaybeRetryGetAvailableFlavors(
672 const nsTArray
<nsCString
>& aFlavorList
, int32_t aWhichClipboard
,
673 nsIAsyncClipboardGetCallback
* aCallback
, int32_t aRetryCount
,
674 mozilla::dom::WindowContext
* aRequestingWindowContext
) {
675 // Note we have to get the clipboard sequence number first before the actual
676 // read. This is to use it to verify the clipboard data is still the one we
677 // try to read, instead of the later state.
678 auto sequenceNumberOrError
=
679 GetNativeClipboardSequenceNumber(aWhichClipboard
);
680 if (sequenceNumberOrError
.isErr()) {
681 MOZ_CLIPBOARD_LOG("%s: unable to get sequence number for clipboard %d.",
682 __FUNCTION__
, aWhichClipboard
);
683 aCallback
->OnError(sequenceNumberOrError
.unwrapErr());
687 int32_t sequenceNumber
= sequenceNumberOrError
.unwrap();
688 AsyncHasNativeClipboardDataMatchingFlavors(
689 aFlavorList
, aWhichClipboard
,
690 [self
= RefPtr
{this}, callback
= nsCOMPtr
{aCallback
}, aWhichClipboard
,
691 aRetryCount
, flavorList
= aFlavorList
.Clone(), sequenceNumber
,
692 requestingWindowContext
=
693 RefPtr
{aRequestingWindowContext
}](auto aFlavorsOrError
) {
694 if (aFlavorsOrError
.isErr()) {
696 "%s: unable to get available flavors for clipboard %d.",
697 __FUNCTION__
, aWhichClipboard
);
698 callback
->OnError(aFlavorsOrError
.unwrapErr());
702 auto sequenceNumberOrError
=
703 self
->GetNativeClipboardSequenceNumber(aWhichClipboard
);
704 if (sequenceNumberOrError
.isErr()) {
706 "%s: unable to get sequence number for clipboard %d.",
707 __FUNCTION__
, aWhichClipboard
);
708 callback
->OnError(sequenceNumberOrError
.unwrapErr());
712 if (sequenceNumber
== sequenceNumberOrError
.unwrap()) {
713 auto asyncGetClipboardData
=
714 mozilla::MakeRefPtr
<AsyncGetClipboardData
>(
715 aWhichClipboard
, sequenceNumber
,
716 std::move(aFlavorsOrError
.unwrap()), false, self
,
717 requestingWindowContext
);
718 callback
->OnSuccess(asyncGetClipboardData
);
722 if (aRetryCount
> 0) {
724 "%s: clipboard=%d, ignore the data due to the sequence number "
725 "doesn't match, retry (%d) ..",
726 __FUNCTION__
, aWhichClipboard
, aRetryCount
);
727 self
->MaybeRetryGetAvailableFlavors(flavorList
, aWhichClipboard
,
728 callback
, aRetryCount
- 1,
729 requestingWindowContext
);
733 MOZ_DIAGNOSTIC_ASSERT(false, "How can this happen?!?");
734 callback
->OnError(NS_ERROR_FAILURE
);
738 NS_IMETHODIMP
nsBaseClipboard::AsyncGetData(
739 const nsTArray
<nsCString
>& aFlavorList
, int32_t aWhichClipboard
,
740 mozilla::dom::WindowContext
* aRequestingWindowContext
,
741 nsIPrincipal
* aRequestingPrincipal
,
742 nsIAsyncClipboardGetCallback
* aCallback
) {
743 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
745 if (!aCallback
|| !aRequestingPrincipal
|| aFlavorList
.IsEmpty()) {
746 return NS_ERROR_INVALID_ARG
;
749 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
750 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
752 return NS_ERROR_FAILURE
;
755 // We want to disable security check for automated tests that have the pref
756 // set to true, or extension that have clipboard read permission.
757 if (mozilla::StaticPrefs::
758 dom_events_testing_asyncClipboard_DoNotUseDirectly() ||
759 nsContentUtils::PrincipalHasPermission(*aRequestingPrincipal
,
760 nsGkAtoms::clipboardRead
)) {
761 AsyncGetDataInternal(aFlavorList
, aWhichClipboard
, aRequestingWindowContext
,
766 // If cache data is valid, we are the last ones to put something on the native
767 // clipboard, then check if the data is from the same-origin page,
768 if (auto* clipboardCache
= GetClipboardCacheIfValid(aWhichClipboard
)) {
769 nsCOMPtr
<nsITransferable
> trans
= clipboardCache
->GetTransferable();
772 if (nsCOMPtr
<nsIPrincipal
> principal
= trans
->GetRequestingPrincipal()) {
773 if (aRequestingPrincipal
->Subsumes(principal
)) {
774 MOZ_CLIPBOARD_LOG("%s: native clipboard data is from same-origin page.",
776 AsyncGetDataInternal(aFlavorList
, aWhichClipboard
,
777 aRequestingWindowContext
, aCallback
);
783 // TODO: enable showing the "Paste" button in this case; see bug 1773681.
784 if (aRequestingPrincipal
->GetIsAddonOrExpandedAddonPrincipal()) {
785 MOZ_CLIPBOARD_LOG("%s: Addon without read permission.", __FUNCTION__
);
786 return aCallback
->OnError(NS_ERROR_FAILURE
);
789 RequestUserConfirmation(aWhichClipboard
, aFlavorList
,
790 aRequestingWindowContext
, aRequestingPrincipal
,
795 already_AddRefed
<nsIAsyncGetClipboardData
>
796 nsBaseClipboard::MaybeCreateGetRequestFromClipboardCache(
797 const nsTArray
<nsCString
>& aFlavorList
, int32_t aClipboardType
,
798 mozilla::dom::WindowContext
* aRequestingWindowContext
) {
799 MOZ_DIAGNOSTIC_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
801 if (!mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
805 // If we were the last ones to put something on the native clipboard, then
806 // just use the cached transferable. Otherwise clear it because it isn't
807 // relevant any more.
808 ClipboardCache
* clipboardCache
= GetClipboardCacheIfValid(aClipboardType
);
809 if (!clipboardCache
) {
813 nsITransferable
* cachedTransferable
= clipboardCache
->GetTransferable();
814 MOZ_ASSERT(cachedTransferable
);
816 nsTArray
<nsCString
> transferableFlavors
;
817 if (NS_FAILED(cachedTransferable
->FlavorsTransferableCanExport(
818 transferableFlavors
))) {
822 nsTArray
<nsCString
> results
;
823 for (const auto& flavor
: aFlavorList
) {
824 for (const auto& transferableFlavor
: transferableFlavors
) {
825 // XXX We need special check for image as we always put the
826 // image as "native" on the clipboard.
827 if (transferableFlavor
.Equals(flavor
) ||
828 (transferableFlavor
.Equals(kNativeImageMime
) &&
829 nsContentUtils::IsFlavorImage(flavor
))) {
830 MOZ_CLIPBOARD_LOG(" has %s", flavor
.get());
831 results
.AppendElement(flavor
);
836 // XXX Do we need to check system clipboard for the flavors that cannot
837 // be found in cache?
838 return mozilla::MakeAndAddRef
<AsyncGetClipboardData
>(
839 aClipboardType
, clipboardCache
->GetSequenceNumber(), std::move(results
),
840 true /* aFromCache */, this, aRequestingWindowContext
);
843 void nsBaseClipboard::AsyncGetDataInternal(
844 const nsTArray
<nsCString
>& aFlavorList
, int32_t aClipboardType
,
845 mozilla::dom::WindowContext
* aRequestingWindowContext
,
846 nsIAsyncClipboardGetCallback
* aCallback
) {
847 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
849 if (nsCOMPtr
<nsIAsyncGetClipboardData
> asyncGetClipboardData
=
850 MaybeCreateGetRequestFromClipboardCache(aFlavorList
, aClipboardType
,
851 aRequestingWindowContext
)) {
852 aCallback
->OnSuccess(asyncGetClipboardData
);
856 // At this point we can't satisfy the request from cache data so let's
857 // look for things other people put on the system clipboard.
858 MaybeRetryGetAvailableFlavors(aFlavorList
, aClipboardType
, aCallback
,
859 kGetAvailableFlavorsRetryCount
,
860 aRequestingWindowContext
);
863 NS_IMETHODIMP
nsBaseClipboard::GetDataSnapshotSync(
864 const nsTArray
<nsCString
>& aFlavorList
, int32_t aWhichClipboard
,
865 mozilla::dom::WindowContext
* aRequestingWindowContext
,
866 nsIAsyncGetClipboardData
** _retval
) {
867 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
871 if (aFlavorList
.IsEmpty()) {
872 return NS_ERROR_INVALID_ARG
;
875 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
876 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
878 return NS_ERROR_FAILURE
;
881 if (nsCOMPtr
<nsIAsyncGetClipboardData
> asyncGetClipboardData
=
882 MaybeCreateGetRequestFromClipboardCache(aFlavorList
, aWhichClipboard
,
883 aRequestingWindowContext
)) {
884 asyncGetClipboardData
.forget(_retval
);
888 auto sequenceNumberOrError
=
889 GetNativeClipboardSequenceNumber(aWhichClipboard
);
890 if (sequenceNumberOrError
.isErr()) {
891 MOZ_CLIPBOARD_LOG("%s: unable to get sequence number for clipboard %d.",
892 __FUNCTION__
, aWhichClipboard
);
893 return sequenceNumberOrError
.unwrapErr();
896 nsTArray
<nsCString
> results
;
897 for (const auto& flavor
: aFlavorList
) {
898 auto resultOrError
= HasNativeClipboardDataMatchingFlavors(
899 AutoTArray
<nsCString
, 1>{flavor
}, aWhichClipboard
);
900 if (resultOrError
.isOk() && resultOrError
.unwrap()) {
901 results
.AppendElement(flavor
);
906 mozilla::MakeAndAddRef
<AsyncGetClipboardData
>(
907 aWhichClipboard
, sequenceNumberOrError
.unwrap(), std::move(results
),
908 false /* aFromCache */, this, aRequestingWindowContext
)
913 NS_IMETHODIMP
nsBaseClipboard::EmptyClipboard(int32_t aWhichClipboard
) {
914 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
916 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
917 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
919 return NS_ERROR_FAILURE
;
922 EmptyNativeClipboardData(aWhichClipboard
);
924 const auto& clipboardCache
= mCaches
[aWhichClipboard
];
925 MOZ_ASSERT(clipboardCache
);
927 if (mIgnoreEmptyNotification
) {
928 MOZ_DIAGNOSTIC_ASSERT(!clipboardCache
->GetTransferable() &&
929 !clipboardCache
->GetClipboardOwner() &&
930 clipboardCache
->GetSequenceNumber() == -1,
931 "How did we have data in clipboard cache here?");
935 clipboardCache
->Clear();
940 mozilla::Result
<nsTArray
<nsCString
>, nsresult
>
941 nsBaseClipboard::GetFlavorsFromClipboardCache(int32_t aClipboardType
) {
942 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
943 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
945 const auto* clipboardCache
= GetClipboardCacheIfValid(aClipboardType
);
946 if (!clipboardCache
) {
947 return mozilla::Err(NS_ERROR_FAILURE
);
950 nsITransferable
* cachedTransferable
= clipboardCache
->GetTransferable();
951 MOZ_ASSERT(cachedTransferable
);
953 nsTArray
<nsCString
> flavors
;
954 nsresult rv
= cachedTransferable
->FlavorsTransferableCanExport(flavors
);
956 return mozilla::Err(rv
);
959 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
960 MOZ_CLIPBOARD_LOG(" Cached transferable types (nums %zu)\n",
962 for (const auto& flavor
: flavors
) {
963 MOZ_CLIPBOARD_LOG(" MIME %s", flavor
.get());
967 return std::move(flavors
);
971 nsBaseClipboard::HasDataMatchingFlavors(const nsTArray
<nsCString
>& aFlavorList
,
972 int32_t aWhichClipboard
,
974 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
975 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
976 MOZ_CLIPBOARD_LOG(" Asking for content clipboard=%i:\n",
978 for (const auto& flavor
: aFlavorList
) {
979 MOZ_CLIPBOARD_LOG(" MIME %s", flavor
.get());
985 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
986 // First, check if we have valid data in our cached transferable.
987 auto flavorsOrError
= GetFlavorsFromClipboardCache(aWhichClipboard
);
988 if (flavorsOrError
.isOk()) {
989 for (const auto& transferableFlavor
: flavorsOrError
.unwrap()) {
990 for (const auto& flavor
: aFlavorList
) {
991 if (transferableFlavor
.Equals(flavor
)) {
992 MOZ_CLIPBOARD_LOG(" has %s", flavor
.get());
1001 auto resultOrError
=
1002 HasNativeClipboardDataMatchingFlavors(aFlavorList
, aWhichClipboard
);
1003 if (resultOrError
.isErr()) {
1005 "%s: checking native clipboard data matching flavors falied.",
1007 return resultOrError
.unwrapErr();
1010 *aOutResult
= resultOrError
.unwrap();
1015 nsBaseClipboard::IsClipboardTypeSupported(int32_t aWhichClipboard
,
1017 NS_ENSURE_ARG_POINTER(aRetval
);
1018 switch (aWhichClipboard
) {
1019 case kGlobalClipboard
:
1020 // We always support the global clipboard.
1023 case kSelectionClipboard
:
1024 *aRetval
= mClipboardCaps
.supportsSelectionClipboard();
1026 case kFindClipboard
:
1027 *aRetval
= mClipboardCaps
.supportsFindClipboard();
1029 case kSelectionCache
:
1030 *aRetval
= mClipboardCaps
.supportsSelectionCache();
1038 void nsBaseClipboard::AsyncHasNativeClipboardDataMatchingFlavors(
1039 const nsTArray
<nsCString
>& aFlavorList
, int32_t aWhichClipboard
,
1040 HasMatchingFlavorsCallback
&& aCallback
) {
1041 MOZ_DIAGNOSTIC_ASSERT(
1042 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
));
1045 "nsBaseClipboard::AsyncHasNativeClipboardDataMatchingFlavors: "
1049 nsTArray
<nsCString
> results
;
1050 for (const auto& flavor
: aFlavorList
) {
1051 auto resultOrError
= HasNativeClipboardDataMatchingFlavors(
1052 AutoTArray
<nsCString
, 1>{flavor
}, aWhichClipboard
);
1053 if (resultOrError
.isOk() && resultOrError
.unwrap()) {
1054 results
.AppendElement(flavor
);
1057 aCallback(std::move(results
));
1060 void nsBaseClipboard::AsyncGetNativeClipboardData(
1061 nsITransferable
* aTransferable
, int32_t aWhichClipboard
,
1062 GetDataCallback
&& aCallback
) {
1063 aCallback(GetNativeClipboardData(aTransferable
, aWhichClipboard
));
1066 void nsBaseClipboard::ClearClipboardCache(int32_t aClipboardType
) {
1067 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
1068 const mozilla::UniquePtr
<ClipboardCache
>& cache
= mCaches
[aClipboardType
];
1073 void nsBaseClipboard::RequestUserConfirmation(
1074 int32_t aClipboardType
, const nsTArray
<nsCString
>& aFlavorList
,
1075 mozilla::dom::WindowContext
* aWindowContext
,
1076 nsIPrincipal
* aRequestingPrincipal
,
1077 nsIAsyncClipboardGetCallback
* aCallback
) {
1078 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
1079 MOZ_ASSERT(aCallback
);
1081 if (!aWindowContext
) {
1082 aCallback
->OnError(NS_ERROR_FAILURE
);
1086 CanonicalBrowsingContext
* cbc
=
1087 CanonicalBrowsingContext::Cast(aWindowContext
->GetBrowsingContext());
1090 "Should not require user confirmation when access from chrome window");
1092 RefPtr
<CanonicalBrowsingContext
> chromeTop
= cbc
->TopCrossChromeBoundary();
1093 Document
* chromeDoc
= chromeTop
? chromeTop
->GetDocument() : nullptr;
1094 if (!chromeDoc
|| !chromeDoc
->HasFocus(mozilla::IgnoreErrors())) {
1095 MOZ_CLIPBOARD_LOG("%s: reject due to not in the focused window",
1097 aCallback
->OnError(NS_ERROR_FAILURE
);
1101 mozilla::dom::Element
* activeElementInChromeDoc
=
1102 chromeDoc
->GetActiveElement();
1103 if (activeElementInChromeDoc
!= cbc
->Top()->GetEmbedderElement()) {
1104 // Reject if the request is not from web content that is in the focused tab.
1105 MOZ_CLIPBOARD_LOG("%s: reject due to not in the focused tab", __FUNCTION__
);
1106 aCallback
->OnError(NS_ERROR_FAILURE
);
1110 // If there is a pending user confirmation request, check if we could reuse
1111 // it. If not, reject the request.
1112 if (sUserConfirmationRequest
) {
1113 if (sUserConfirmationRequest
->IsEqual(
1114 aClipboardType
, chromeDoc
, aRequestingPrincipal
, aWindowContext
)) {
1115 sUserConfirmationRequest
->AddClipboardGetRequest(aFlavorList
, aCallback
);
1119 aCallback
->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR
);
1123 nsresult rv
= NS_ERROR_FAILURE
;
1124 nsCOMPtr
<nsIPromptService
> promptService
=
1125 do_GetService("@mozilla.org/prompter;1", &rv
);
1126 if (NS_FAILED(rv
)) {
1127 aCallback
->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR
);
1131 RefPtr
<mozilla::dom::Promise
> promise
;
1132 if (NS_FAILED(promptService
->ConfirmUserPaste(aWindowContext
->Canonical(),
1133 getter_AddRefs(promise
)))) {
1134 aCallback
->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR
);
1138 sUserConfirmationRequest
= new UserConfirmationRequest(
1139 aClipboardType
, chromeDoc
, aRequestingPrincipal
, this, aWindowContext
);
1140 sUserConfirmationRequest
->AddClipboardGetRequest(aFlavorList
, aCallback
);
1141 promise
->AppendNativeHandler(sUserConfirmationRequest
);
1144 NS_IMPL_ISUPPORTS(nsBaseClipboard::AsyncGetClipboardData
,
1145 nsIAsyncGetClipboardData
)
1147 nsBaseClipboard::AsyncGetClipboardData::AsyncGetClipboardData(
1148 int32_t aClipboardType
, int32_t aSequenceNumber
,
1149 nsTArray
<nsCString
>&& aFlavors
, bool aFromCache
,
1150 nsBaseClipboard
* aClipboard
,
1151 mozilla::dom::WindowContext
* aRequestingWindowContext
)
1152 : mClipboardType(aClipboardType
),
1153 mSequenceNumber(aSequenceNumber
),
1154 mFlavors(std::move(aFlavors
)),
1155 mFromCache(aFromCache
),
1156 mClipboard(aClipboard
),
1157 mRequestingWindowContext(aRequestingWindowContext
) {
1158 MOZ_ASSERT(mClipboard
);
1160 mClipboard
->nsIClipboard::IsClipboardTypeSupported(mClipboardType
));
1163 NS_IMETHODIMP
nsBaseClipboard::AsyncGetClipboardData::GetValid(
1165 *aOutResult
= IsValid();
1169 NS_IMETHODIMP
nsBaseClipboard::AsyncGetClipboardData::GetFlavorList(
1170 nsTArray
<nsCString
>& aFlavors
) {
1171 aFlavors
.AppendElements(mFlavors
);
1175 NS_IMETHODIMP
nsBaseClipboard::AsyncGetClipboardData::GetData(
1176 nsITransferable
* aTransferable
,
1177 nsIAsyncClipboardRequestCallback
* aCallback
) {
1178 MOZ_CLIPBOARD_LOG("AsyncGetClipboardData::GetData: %p", this);
1180 if (!aTransferable
|| !aCallback
) {
1181 return NS_ERROR_INVALID_ARG
;
1184 nsTArray
<nsCString
> flavors
;
1185 nsresult rv
= aTransferable
->FlavorsTransferableCanImport(flavors
);
1186 if (NS_FAILED(rv
)) {
1190 // If the requested flavor is not in the list, throw an error.
1191 for (const auto& flavor
: flavors
) {
1192 if (!mFlavors
.Contains(flavor
)) {
1193 return NS_ERROR_FAILURE
;
1198 aCallback
->OnComplete(NS_ERROR_FAILURE
);
1202 MOZ_ASSERT(mClipboard
);
1204 auto contentAnalysisCallback
=
1205 mozilla::MakeRefPtr
<SafeContentAnalysisResultCallback
>(
1206 [transferable
= nsCOMPtr
{aTransferable
},
1207 callback
= nsCOMPtr
{aCallback
}](
1208 RefPtr
<nsIContentAnalysisResult
>&& aResult
) {
1209 if (aResult
->GetShouldAllowContent()) {
1210 callback
->OnComplete(NS_OK
);
1212 transferable
->ClearAllData();
1213 callback
->OnComplete(NS_ERROR_CONTENT_BLOCKED
);
1218 const auto* clipboardCache
=
1219 mClipboard
->GetClipboardCacheIfValid(mClipboardType
);
1220 // `IsValid()` above ensures we should get a valid cache and matched
1221 // sequence number here.
1222 MOZ_DIAGNOSTIC_ASSERT(clipboardCache
);
1223 MOZ_DIAGNOSTIC_ASSERT(clipboardCache
->GetSequenceNumber() ==
1225 if (NS_SUCCEEDED(clipboardCache
->GetData(aTransferable
))) {
1226 mClipboard
->CheckClipboardContentAnalysis(
1227 mRequestingWindowContext
? mRequestingWindowContext
->Canonical()
1229 aTransferable
, mClipboardType
, contentAnalysisCallback
);
1233 // At this point we can't satisfy the request from cache data so let's look
1234 // for things other people put on the system clipboard.
1237 // Since this is an async operation, we need to check if the data is still
1238 // valid after we get the result.
1239 mClipboard
->AsyncGetNativeClipboardData(
1240 aTransferable
, mClipboardType
,
1241 [callback
= nsCOMPtr
{aCallback
}, self
= RefPtr
{this},
1242 transferable
= nsCOMPtr
{aTransferable
},
1243 contentAnalysisCallback
=
1244 std::move(contentAnalysisCallback
)](nsresult aResult
) mutable {
1245 if (NS_FAILED(aResult
)) {
1246 callback
->OnComplete(aResult
);
1249 // `IsValid()` checks the clipboard sequence number to ensure the data
1250 // we are requesting is still valid.
1251 if (!self
->IsValid()) {
1252 callback
->OnComplete(NS_ERROR_FAILURE
);
1255 self
->mClipboard
->CheckClipboardContentAnalysis(
1256 self
->mRequestingWindowContext
1257 ? self
->mRequestingWindowContext
->Canonical()
1259 transferable
, self
->mClipboardType
, contentAnalysisCallback
);
1264 bool nsBaseClipboard::AsyncGetClipboardData::IsValid() {
1269 // If the data should from cache, check if cache is still valid or the
1270 // sequence numbers are matched.
1272 const auto* clipboardCache
=
1273 mClipboard
->GetClipboardCacheIfValid(mClipboardType
);
1274 if (!clipboardCache
) {
1275 mClipboard
= nullptr;
1279 return mSequenceNumber
== clipboardCache
->GetSequenceNumber();
1282 auto resultOrError
=
1283 mClipboard
->GetNativeClipboardSequenceNumber(mClipboardType
);
1284 if (resultOrError
.isErr()) {
1285 mClipboard
= nullptr;
1289 if (mSequenceNumber
!= resultOrError
.unwrap()) {
1290 mClipboard
= nullptr;
1297 nsBaseClipboard::ClipboardCache
* nsBaseClipboard::GetClipboardCacheIfValid(
1298 int32_t aClipboardType
) {
1299 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
1301 const mozilla::UniquePtr
<ClipboardCache
>& cache
= mCaches
[aClipboardType
];
1304 if (!cache
->GetTransferable()) {
1305 MOZ_ASSERT(cache
->GetSequenceNumber() == -1);
1309 auto changeCountOrError
= GetNativeClipboardSequenceNumber(aClipboardType
);
1310 if (changeCountOrError
.isErr()) {
1314 if (changeCountOrError
.unwrap() != cache
->GetSequenceNumber()) {
1315 // Clipboard cache is invalid, clear it.
1323 void nsBaseClipboard::ClipboardCache::Clear() {
1324 if (mClipboardOwner
) {
1325 mClipboardOwner
->LosingOwnership(mTransferable
);
1326 mClipboardOwner
= nullptr;
1328 mTransferable
= nullptr;
1329 mSequenceNumber
= -1;
1332 nsresult
nsBaseClipboard::ClipboardCache::GetData(
1333 nsITransferable
* aTransferable
) const {
1334 MOZ_ASSERT(aTransferable
);
1335 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
1337 // get flavor list that includes all acceptable flavors (including ones
1338 // obtained through conversion)
1339 nsTArray
<nsCString
> flavors
;
1340 if (NS_FAILED(aTransferable
->FlavorsTransferableCanImport(flavors
))) {
1341 return NS_ERROR_FAILURE
;
1344 MOZ_ASSERT(mTransferable
);
1345 for (const auto& flavor
: flavors
) {
1346 nsCOMPtr
<nsISupports
> dataSupports
;
1347 // XXX Maybe we need special check for image as we always put the image as
1348 // "native" on the clipboard.
1349 if (NS_SUCCEEDED(mTransferable
->GetTransferData(
1350 flavor
.get(), getter_AddRefs(dataSupports
)))) {
1351 MOZ_CLIPBOARD_LOG("%s: getting %s from cache.", __FUNCTION__
,
1353 aTransferable
->SetTransferData(flavor
.get(), dataSupports
);
1354 // XXX we only read the first available type from native clipboard, so
1355 // make cache behave the same.
1360 return NS_ERROR_FAILURE
;