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 "mozilla/dom/BindingUtils.h"
9 #include "mozilla/dom/CanonicalBrowsingContext.h"
10 #include "mozilla/dom/Document.h"
11 #include "mozilla/dom/Promise.h"
12 #include "mozilla/dom/PromiseNativeHandler.h"
13 #include "mozilla/dom/WindowGlobalParent.h"
14 #include "mozilla/ErrorResult.h"
15 #include "mozilla/RefPtr.h"
16 #include "mozilla/Services.h"
17 #include "mozilla/StaticPrefs_dom.h"
18 #include "mozilla/StaticPrefs_widget.h"
19 #include "nsContentUtils.h"
20 #include "nsFocusManager.h"
21 #include "nsIClipboardOwner.h"
22 #include "nsIPromptService.h"
26 using mozilla::GenericPromise
;
27 using mozilla::LogLevel
;
28 using mozilla::UniquePtr
;
29 using mozilla::dom::BrowsingContext
;
30 using mozilla::dom::CanonicalBrowsingContext
;
31 using mozilla::dom::ClipboardCapabilities
;
32 using mozilla::dom::Document
;
34 static const int32_t kGetAvailableFlavorsRetryCount
= 5;
38 struct ClipboardGetRequest
{
39 ClipboardGetRequest(const nsTArray
<nsCString
>& aFlavorList
,
40 nsIAsyncClipboardGetCallback
* aCallback
)
41 : mFlavorList(aFlavorList
.Clone()), mCallback(aCallback
) {}
43 const nsTArray
<nsCString
> mFlavorList
;
44 const nsCOMPtr
<nsIAsyncClipboardGetCallback
> mCallback
;
47 class UserConfirmationRequest final
48 : public mozilla::dom::PromiseNativeHandler
{
50 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
51 NS_DECL_CYCLE_COLLECTION_CLASS(UserConfirmationRequest
)
53 UserConfirmationRequest(int32_t aClipboardType
,
54 Document
* aRequestingChromeDocument
,
55 nsIPrincipal
* aRequestingPrincipal
,
56 nsBaseClipboard
* aClipboard
)
57 : mClipboardType(aClipboardType
),
58 mRequestingChromeDocument(aRequestingChromeDocument
),
59 mRequestingPrincipal(aRequestingPrincipal
),
60 mClipboard(aClipboard
) {
62 mClipboard
->nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
65 void ResolvedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
,
66 mozilla::ErrorResult
& aRv
) override
;
68 void RejectedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
,
69 mozilla::ErrorResult
& aRv
) override
;
71 bool IsEqual(int32_t aClipboardType
, Document
* aRequestingChromeDocument
,
72 nsIPrincipal
* aRequestingPrincipal
) const {
73 return ClipboardType() == aClipboardType
&&
74 RequestingChromeDocument() == aRequestingChromeDocument
&&
75 RequestingPrincipal()->Equals(aRequestingPrincipal
);
78 int32_t ClipboardType() const { return mClipboardType
; }
80 Document
* RequestingChromeDocument() const {
81 return mRequestingChromeDocument
;
84 nsIPrincipal
* RequestingPrincipal() const { return mRequestingPrincipal
; }
86 void AddClipboardGetRequest(const nsTArray
<nsCString
>& aFlavorList
,
87 nsIAsyncClipboardGetCallback
* aCallback
) {
88 MOZ_ASSERT(!aFlavorList
.IsEmpty());
89 MOZ_ASSERT(aCallback
);
90 mPendingClipboardGetRequests
.AppendElement(
91 mozilla::MakeUnique
<ClipboardGetRequest
>(aFlavorList
, aCallback
));
94 void RejectPendingClipboardGetRequests(nsresult aError
) {
95 MOZ_ASSERT(NS_FAILED(aError
));
96 auto requests
= std::move(mPendingClipboardGetRequests
);
97 for (const auto& request
: requests
) {
99 MOZ_ASSERT(request
->mCallback
);
100 request
->mCallback
->OnError(aError
);
104 void ProcessPendingClipboardGetRequests() {
105 auto requests
= std::move(mPendingClipboardGetRequests
);
106 for (const auto& request
: requests
) {
108 MOZ_ASSERT(!request
->mFlavorList
.IsEmpty());
109 MOZ_ASSERT(request
->mCallback
);
110 mClipboard
->AsyncGetDataInternal(request
->mFlavorList
, mClipboardType
,
115 nsTArray
<UniquePtr
<ClipboardGetRequest
>>& GetPendingClipboardGetRequests() {
116 return mPendingClipboardGetRequests
;
120 ~UserConfirmationRequest() = default;
122 const int32_t mClipboardType
;
123 RefPtr
<Document
> mRequestingChromeDocument
;
124 const nsCOMPtr
<nsIPrincipal
> mRequestingPrincipal
;
125 const RefPtr
<nsBaseClipboard
> mClipboard
;
126 // Track the pending read requests that wait for user confirmation.
127 nsTArray
<UniquePtr
<ClipboardGetRequest
>> mPendingClipboardGetRequests
;
130 NS_IMPL_CYCLE_COLLECTION(UserConfirmationRequest
, mRequestingChromeDocument
)
132 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UserConfirmationRequest
)
133 NS_INTERFACE_MAP_ENTRY(nsISupports
)
136 NS_IMPL_CYCLE_COLLECTING_ADDREF(UserConfirmationRequest
)
137 NS_IMPL_CYCLE_COLLECTING_RELEASE(UserConfirmationRequest
)
139 static mozilla::StaticRefPtr
<UserConfirmationRequest
> sUserConfirmationRequest
;
141 void UserConfirmationRequest::ResolvedCallback(JSContext
* aCx
,
142 JS::Handle
<JS::Value
> aValue
,
143 mozilla::ErrorResult
& aRv
) {
144 MOZ_DIAGNOSTIC_ASSERT(sUserConfirmationRequest
== this);
145 sUserConfirmationRequest
= nullptr;
147 JS::Rooted
<JSObject
*> detailObj(aCx
, &aValue
.toObject());
148 nsCOMPtr
<nsIPropertyBag2
> propBag
;
149 nsresult rv
= mozilla::dom::UnwrapArg
<nsIPropertyBag2
>(
150 aCx
, detailObj
, getter_AddRefs(propBag
));
152 RejectPendingClipboardGetRequests(rv
);
157 rv
= propBag
->GetPropertyAsBool(u
"ok"_ns
, &result
);
159 RejectPendingClipboardGetRequests(rv
);
164 RejectPendingClipboardGetRequests(NS_ERROR_DOM_NOT_ALLOWED_ERR
);
168 ProcessPendingClipboardGetRequests();
171 void UserConfirmationRequest::RejectedCallback(JSContext
* aCx
,
172 JS::Handle
<JS::Value
> aValue
,
173 mozilla::ErrorResult
& aRv
) {
174 MOZ_DIAGNOSTIC_ASSERT(sUserConfirmationRequest
== this);
175 sUserConfirmationRequest
= nullptr;
176 RejectPendingClipboardGetRequests(NS_ERROR_FAILURE
);
181 NS_IMPL_ISUPPORTS(nsBaseClipboard::AsyncSetClipboardData
,
182 nsIAsyncSetClipboardData
)
184 nsBaseClipboard::AsyncSetClipboardData::AsyncSetClipboardData(
185 int32_t aClipboardType
, nsBaseClipboard
* aClipboard
,
186 nsIAsyncClipboardRequestCallback
* aCallback
)
187 : mClipboardType(aClipboardType
),
188 mClipboard(aClipboard
),
189 mCallback(aCallback
) {
190 MOZ_ASSERT(mClipboard
);
192 mClipboard
->nsIClipboard::IsClipboardTypeSupported(mClipboardType
));
196 nsBaseClipboard::AsyncSetClipboardData::SetData(nsITransferable
* aTransferable
,
197 nsIClipboardOwner
* aOwner
) {
198 MOZ_CLIPBOARD_LOG("AsyncSetClipboardData::SetData (%p): clipboard=%d", this,
202 return NS_ERROR_FAILURE
;
205 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
206 nsTArray
<nsCString
> flavors
;
207 if (NS_SUCCEEDED(aTransferable
->FlavorsTransferableCanImport(flavors
))) {
208 for (const auto& flavor
: flavors
) {
209 MOZ_CLIPBOARD_LOG(" MIME %s", flavor
.get());
214 MOZ_ASSERT(mClipboard
);
216 mClipboard
->nsIClipboard::IsClipboardTypeSupported(mClipboardType
));
217 MOZ_DIAGNOSTIC_ASSERT(mClipboard
->mPendingWriteRequests
[mClipboardType
] ==
220 RefPtr
<AsyncSetClipboardData
> request
=
221 std::move(mClipboard
->mPendingWriteRequests
[mClipboardType
]);
222 nsresult rv
= mClipboard
->SetData(aTransferable
, aOwner
, mClipboardType
);
223 MaybeNotifyCallback(rv
);
229 nsBaseClipboard::AsyncSetClipboardData::Abort(nsresult aReason
) {
230 // Note: This may be called during destructor, so it should not attempt to
231 // take a reference to mClipboard.
233 if (!IsValid() || !NS_FAILED(aReason
)) {
234 return NS_ERROR_FAILURE
;
237 MaybeNotifyCallback(aReason
);
241 void nsBaseClipboard::AsyncSetClipboardData::MaybeNotifyCallback(
243 // Note: This may be called during destructor, so it should not attempt to
244 // take a reference to mClipboard.
246 MOZ_ASSERT(IsValid());
247 if (nsCOMPtr
<nsIAsyncClipboardRequestCallback
> callback
=
248 mCallback
.forget()) {
249 callback
->OnComplete(aResult
);
251 // Once the callback is notified, setData should not be allowed, so invalidate
253 mClipboard
= nullptr;
256 void nsBaseClipboard::RejectPendingAsyncSetDataRequestIfAny(
257 int32_t aClipboardType
) {
258 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
259 auto& request
= mPendingWriteRequests
[aClipboardType
];
261 request
->Abort(NS_ERROR_ABORT
);
266 NS_IMETHODIMP
nsBaseClipboard::AsyncSetData(
267 int32_t aWhichClipboard
, nsIAsyncClipboardRequestCallback
* aCallback
,
268 nsIAsyncSetClipboardData
** _retval
) {
269 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
272 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
273 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
275 return NS_ERROR_DOM_NOT_SUPPORTED_ERR
;
278 // Reject existing pending AsyncSetData request if any.
279 RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard
);
281 // Create a new AsyncSetClipboardData.
282 RefPtr
<AsyncSetClipboardData
> request
=
283 mozilla::MakeRefPtr
<AsyncSetClipboardData
>(aWhichClipboard
, this,
285 mPendingWriteRequests
[aWhichClipboard
] = request
;
286 request
.forget(_retval
);
290 nsBaseClipboard::nsBaseClipboard(const ClipboardCapabilities
& aClipboardCaps
)
291 : mClipboardCaps(aClipboardCaps
) {
292 using mozilla::MakeUnique
;
293 // Initialize clipboard cache.
294 mCaches
[kGlobalClipboard
] = MakeUnique
<ClipboardCache
>();
295 if (mClipboardCaps
.supportsSelectionClipboard()) {
296 mCaches
[kSelectionClipboard
] = MakeUnique
<ClipboardCache
>();
298 if (mClipboardCaps
.supportsFindClipboard()) {
299 mCaches
[kFindClipboard
] = MakeUnique
<ClipboardCache
>();
301 if (mClipboardCaps
.supportsSelectionCache()) {
302 mCaches
[kSelectionCache
] = MakeUnique
<ClipboardCache
>();
306 nsBaseClipboard::~nsBaseClipboard() {
307 for (auto& request
: mPendingWriteRequests
) {
309 request
->Abort(NS_ERROR_ABORT
);
315 NS_IMPL_ISUPPORTS(nsBaseClipboard
, nsIClipboard
)
318 * Sets the transferable object
321 NS_IMETHODIMP
nsBaseClipboard::SetData(nsITransferable
* aTransferable
,
322 nsIClipboardOwner
* aOwner
,
323 int32_t aWhichClipboard
) {
324 NS_ASSERTION(aTransferable
, "clipboard given a null transferable");
326 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
328 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
329 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
331 return NS_ERROR_FAILURE
;
334 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
335 nsTArray
<nsCString
> flavors
;
336 if (NS_SUCCEEDED(aTransferable
->FlavorsTransferableCanImport(flavors
))) {
337 for (const auto& flavor
: flavors
) {
338 MOZ_CLIPBOARD_LOG(" MIME %s", flavor
.get());
343 const auto& clipboardCache
= mCaches
[aWhichClipboard
];
344 MOZ_ASSERT(clipboardCache
);
345 if (aTransferable
== clipboardCache
->GetTransferable() &&
346 aOwner
== clipboardCache
->GetClipboardOwner()) {
347 MOZ_CLIPBOARD_LOG("%s: skipping update.", __FUNCTION__
);
351 clipboardCache
->Clear();
353 nsresult rv
= NS_ERROR_FAILURE
;
355 mIgnoreEmptyNotification
= true;
356 // Reject existing pending asyncSetData request if any.
357 RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard
);
358 rv
= SetNativeClipboardData(aTransferable
, aWhichClipboard
);
359 mIgnoreEmptyNotification
= false;
362 MOZ_CLIPBOARD_LOG("%s: setting native clipboard data failed.",
367 auto result
= GetNativeClipboardSequenceNumber(aWhichClipboard
);
368 if (result
.isErr()) {
369 MOZ_CLIPBOARD_LOG("%s: getting native clipboard change count failed.",
371 return result
.unwrapErr();
374 clipboardCache
->Update(aTransferable
, aOwner
, result
.unwrap());
378 nsresult
nsBaseClipboard::GetDataFromClipboardCache(
379 nsITransferable
* aTransferable
, int32_t aClipboardType
) {
380 MOZ_ASSERT(aTransferable
);
381 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
382 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
384 const auto* clipboardCache
= GetClipboardCacheIfValid(aClipboardType
);
385 if (!clipboardCache
) {
386 return NS_ERROR_FAILURE
;
389 return clipboardCache
->GetData(aTransferable
);
393 * Gets the transferable object from system clipboard.
395 NS_IMETHODIMP
nsBaseClipboard::GetData(nsITransferable
* aTransferable
,
396 int32_t aWhichClipboard
) {
397 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
399 if (!aTransferable
) {
400 NS_ASSERTION(false, "clipboard given a null transferable");
401 return NS_ERROR_FAILURE
;
404 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
405 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
407 return NS_ERROR_FAILURE
;
410 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
411 // If we were the last ones to put something on the native clipboard, then
412 // just use the cached transferable. Otherwise clear it because it isn't
413 // relevant any more.
415 GetDataFromClipboardCache(aTransferable
, aWhichClipboard
))) {
416 // maybe try to fill in more types? Is there a point?
420 // at this point we can't satisfy the request from cache data so let's look
421 // for things other people put on the system clipboard
424 return GetNativeClipboardData(aTransferable
, aWhichClipboard
);
427 void nsBaseClipboard::MaybeRetryGetAvailableFlavors(
428 const nsTArray
<nsCString
>& aFlavorList
, int32_t aWhichClipboard
,
429 nsIAsyncClipboardGetCallback
* aCallback
, int32_t aRetryCount
) {
430 // Note we have to get the clipboard sequence number first before the actual
431 // read. This is to use it to verify the clipboard data is still the one we
432 // try to read, instead of the later state.
433 auto sequenceNumberOrError
=
434 GetNativeClipboardSequenceNumber(aWhichClipboard
);
435 if (sequenceNumberOrError
.isErr()) {
436 MOZ_CLIPBOARD_LOG("%s: unable to get sequence number for clipboard %d.",
437 __FUNCTION__
, aWhichClipboard
);
438 aCallback
->OnError(sequenceNumberOrError
.unwrapErr());
442 int32_t sequenceNumber
= sequenceNumberOrError
.unwrap();
443 AsyncHasNativeClipboardDataMatchingFlavors(
444 aFlavorList
, aWhichClipboard
,
445 [self
= RefPtr
{this}, callback
= nsCOMPtr
{aCallback
}, aWhichClipboard
,
446 aRetryCount
, flavorList
= aFlavorList
.Clone(),
447 sequenceNumber
](auto aFlavorsOrError
) {
448 if (aFlavorsOrError
.isErr()) {
450 "%s: unable to get available flavors for clipboard %d.",
451 __FUNCTION__
, aWhichClipboard
);
452 callback
->OnError(aFlavorsOrError
.unwrapErr());
456 auto sequenceNumberOrError
=
457 self
->GetNativeClipboardSequenceNumber(aWhichClipboard
);
458 if (sequenceNumberOrError
.isErr()) {
460 "%s: unable to get sequence number for clipboard %d.",
461 __FUNCTION__
, aWhichClipboard
);
462 callback
->OnError(sequenceNumberOrError
.unwrapErr());
466 if (sequenceNumber
== sequenceNumberOrError
.unwrap()) {
467 auto asyncGetClipboardData
=
468 mozilla::MakeRefPtr
<AsyncGetClipboardData
>(
469 aWhichClipboard
, sequenceNumber
,
470 std::move(aFlavorsOrError
.unwrap()), false, self
);
471 callback
->OnSuccess(asyncGetClipboardData
);
475 if (aRetryCount
> 0) {
477 "%s: clipboard=%d, ignore the data due to the sequence number "
478 "doesn't match, retry (%d) ..",
479 __FUNCTION__
, aWhichClipboard
, aRetryCount
);
480 self
->MaybeRetryGetAvailableFlavors(flavorList
, aWhichClipboard
,
481 callback
, aRetryCount
- 1);
485 MOZ_DIAGNOSTIC_ASSERT(false, "How can this happen?!?");
486 callback
->OnError(NS_ERROR_FAILURE
);
490 NS_IMETHODIMP
nsBaseClipboard::AsyncGetData(
491 const nsTArray
<nsCString
>& aFlavorList
, int32_t aWhichClipboard
,
492 mozilla::dom::WindowContext
* aRequestingWindowContext
,
493 nsIPrincipal
* aRequestingPrincipal
,
494 nsIAsyncClipboardGetCallback
* aCallback
) {
495 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
497 if (!aCallback
|| !aRequestingPrincipal
|| aFlavorList
.IsEmpty()) {
498 return NS_ERROR_INVALID_ARG
;
501 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
502 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
504 return NS_ERROR_FAILURE
;
507 // We want to disable security check for automated tests that have the pref
508 // set to true, or extension that have clipboard read permission.
509 if (mozilla::StaticPrefs::
510 dom_events_testing_asyncClipboard_DoNotUseDirectly() ||
511 nsContentUtils::PrincipalHasPermission(*aRequestingPrincipal
,
512 nsGkAtoms::clipboardRead
)) {
513 AsyncGetDataInternal(aFlavorList
, aWhichClipboard
, aCallback
);
517 // If cache data is valid, we are the last ones to put something on the native
518 // clipboard, then check if the data is from the same-origin page,
519 if (auto* clipboardCache
= GetClipboardCacheIfValid(aWhichClipboard
)) {
520 nsCOMPtr
<nsITransferable
> trans
= clipboardCache
->GetTransferable();
523 if (nsCOMPtr
<nsIPrincipal
> principal
= trans
->GetRequestingPrincipal()) {
524 if (aRequestingPrincipal
->Subsumes(principal
)) {
525 MOZ_CLIPBOARD_LOG("%s: native clipboard data is from same-origin page.",
527 AsyncGetDataInternal(aFlavorList
, aWhichClipboard
, aCallback
);
533 // TODO: enable showing the "Paste" button in this case; see bug 1773681.
534 if (aRequestingPrincipal
->GetIsAddonOrExpandedAddonPrincipal()) {
535 MOZ_CLIPBOARD_LOG("%s: Addon without read permission.", __FUNCTION__
);
536 return aCallback
->OnError(NS_ERROR_FAILURE
);
539 RequestUserConfirmation(aWhichClipboard
, aFlavorList
,
540 aRequestingWindowContext
, aRequestingPrincipal
,
545 void nsBaseClipboard::AsyncGetDataInternal(
546 const nsTArray
<nsCString
>& aFlavorList
, int32_t aClipboardType
,
547 nsIAsyncClipboardGetCallback
* aCallback
) {
548 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
550 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
551 // If we were the last ones to put something on the native clipboard, then
552 // just use the cached transferable. Otherwise clear it because it isn't
553 // relevant any more.
554 if (auto* clipboardCache
= GetClipboardCacheIfValid(aClipboardType
)) {
555 nsITransferable
* cachedTransferable
= clipboardCache
->GetTransferable();
556 MOZ_ASSERT(cachedTransferable
);
558 nsTArray
<nsCString
> transferableFlavors
;
559 if (NS_SUCCEEDED(cachedTransferable
->FlavorsTransferableCanExport(
560 transferableFlavors
))) {
561 nsTArray
<nsCString
> results
;
562 for (const auto& transferableFlavor
: transferableFlavors
) {
563 for (const auto& flavor
: aFlavorList
) {
564 // XXX We need special check for image as we always put the
565 // image as "native" on the clipboard.
566 if (transferableFlavor
.Equals(flavor
) ||
567 (transferableFlavor
.Equals(kNativeImageMime
) &&
568 nsContentUtils::IsFlavorImage(flavor
))) {
569 MOZ_CLIPBOARD_LOG(" has %s", flavor
.get());
570 results
.AppendElement(flavor
);
575 // XXX Do we need to check system clipboard for the flavors that cannot
576 // be found in cache?
577 auto asyncGetClipboardData
= mozilla::MakeRefPtr
<AsyncGetClipboardData
>(
578 aClipboardType
, clipboardCache
->GetSequenceNumber(),
579 std::move(results
), true, this);
580 aCallback
->OnSuccess(asyncGetClipboardData
);
585 // At this point we can't satisfy the request from cache data so let's look
586 // for things other people put on the system clipboard.
589 MaybeRetryGetAvailableFlavors(aFlavorList
, aClipboardType
, aCallback
,
590 kGetAvailableFlavorsRetryCount
);
593 NS_IMETHODIMP
nsBaseClipboard::EmptyClipboard(int32_t aWhichClipboard
) {
594 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
596 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
597 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
599 return NS_ERROR_FAILURE
;
602 EmptyNativeClipboardData(aWhichClipboard
);
604 const auto& clipboardCache
= mCaches
[aWhichClipboard
];
605 MOZ_ASSERT(clipboardCache
);
607 if (mIgnoreEmptyNotification
) {
608 MOZ_DIAGNOSTIC_ASSERT(!clipboardCache
->GetTransferable() &&
609 !clipboardCache
->GetClipboardOwner() &&
610 clipboardCache
->GetSequenceNumber() == -1,
611 "How did we have data in clipboard cache here?");
615 clipboardCache
->Clear();
620 mozilla::Result
<nsTArray
<nsCString
>, nsresult
>
621 nsBaseClipboard::GetFlavorsFromClipboardCache(int32_t aClipboardType
) {
622 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
623 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
625 const auto* clipboardCache
= GetClipboardCacheIfValid(aClipboardType
);
626 if (!clipboardCache
) {
627 return mozilla::Err(NS_ERROR_FAILURE
);
630 nsITransferable
* cachedTransferable
= clipboardCache
->GetTransferable();
631 MOZ_ASSERT(cachedTransferable
);
633 nsTArray
<nsCString
> flavors
;
634 nsresult rv
= cachedTransferable
->FlavorsTransferableCanExport(flavors
);
636 return mozilla::Err(rv
);
639 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
640 MOZ_CLIPBOARD_LOG(" Cached transferable types (nums %zu)\n",
642 for (const auto& flavor
: flavors
) {
643 MOZ_CLIPBOARD_LOG(" MIME %s", flavor
.get());
647 return std::move(flavors
);
651 nsBaseClipboard::HasDataMatchingFlavors(const nsTArray
<nsCString
>& aFlavorList
,
652 int32_t aWhichClipboard
,
654 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
655 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
656 MOZ_CLIPBOARD_LOG(" Asking for content clipboard=%i:\n",
658 for (const auto& flavor
: aFlavorList
) {
659 MOZ_CLIPBOARD_LOG(" MIME %s", flavor
.get());
665 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
666 // First, check if we have valid data in our cached transferable.
667 auto flavorsOrError
= GetFlavorsFromClipboardCache(aWhichClipboard
);
668 if (flavorsOrError
.isOk()) {
669 for (const auto& transferableFlavor
: flavorsOrError
.unwrap()) {
670 for (const auto& flavor
: aFlavorList
) {
671 if (transferableFlavor
.Equals(flavor
)) {
672 MOZ_CLIPBOARD_LOG(" has %s", flavor
.get());
682 HasNativeClipboardDataMatchingFlavors(aFlavorList
, aWhichClipboard
);
683 if (resultOrError
.isErr()) {
685 "%s: checking native clipboard data matching flavors falied.",
687 return resultOrError
.unwrapErr();
690 *aOutResult
= resultOrError
.unwrap();
695 nsBaseClipboard::IsClipboardTypeSupported(int32_t aWhichClipboard
,
697 NS_ENSURE_ARG_POINTER(aRetval
);
698 switch (aWhichClipboard
) {
699 case kGlobalClipboard
:
700 // We always support the global clipboard.
703 case kSelectionClipboard
:
704 *aRetval
= mClipboardCaps
.supportsSelectionClipboard();
707 *aRetval
= mClipboardCaps
.supportsFindClipboard();
709 case kSelectionCache
:
710 *aRetval
= mClipboardCaps
.supportsSelectionCache();
718 void nsBaseClipboard::AsyncHasNativeClipboardDataMatchingFlavors(
719 const nsTArray
<nsCString
>& aFlavorList
, int32_t aWhichClipboard
,
720 HasMatchingFlavorsCallback
&& aCallback
) {
721 MOZ_DIAGNOSTIC_ASSERT(
722 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
));
725 "nsBaseClipboard::AsyncHasNativeClipboardDataMatchingFlavors: "
729 nsTArray
<nsCString
> results
;
730 for (const auto& flavor
: aFlavorList
) {
731 auto resultOrError
= HasNativeClipboardDataMatchingFlavors(
732 AutoTArray
<nsCString
, 1>{flavor
}, aWhichClipboard
);
733 if (resultOrError
.isOk() && resultOrError
.unwrap()) {
734 results
.AppendElement(flavor
);
737 aCallback(std::move(results
));
740 void nsBaseClipboard::AsyncGetNativeClipboardData(
741 nsITransferable
* aTransferable
, int32_t aWhichClipboard
,
742 GetDataCallback
&& aCallback
) {
743 aCallback(GetNativeClipboardData(aTransferable
, aWhichClipboard
));
746 void nsBaseClipboard::ClearClipboardCache(int32_t aClipboardType
) {
747 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
748 const mozilla::UniquePtr
<ClipboardCache
>& cache
= mCaches
[aClipboardType
];
753 void nsBaseClipboard::RequestUserConfirmation(
754 int32_t aClipboardType
, const nsTArray
<nsCString
>& aFlavorList
,
755 mozilla::dom::WindowContext
* aWindowContext
,
756 nsIPrincipal
* aRequestingPrincipal
,
757 nsIAsyncClipboardGetCallback
* aCallback
) {
758 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
759 MOZ_ASSERT(aCallback
);
761 if (!aWindowContext
) {
762 aCallback
->OnError(NS_ERROR_FAILURE
);
766 CanonicalBrowsingContext
* cbc
=
767 CanonicalBrowsingContext::Cast(aWindowContext
->GetBrowsingContext());
770 "Should not require user confirmation when access from chrome window");
772 RefPtr
<CanonicalBrowsingContext
> chromeTop
= cbc
->TopCrossChromeBoundary();
773 Document
* chromeDoc
= chromeTop
? chromeTop
->GetDocument() : nullptr;
774 if (!chromeDoc
|| !chromeDoc
->HasFocus(mozilla::IgnoreErrors())) {
775 MOZ_CLIPBOARD_LOG("%s: reject due to not in the focused window",
777 aCallback
->OnError(NS_ERROR_FAILURE
);
781 mozilla::dom::Element
* activeElementInChromeDoc
=
782 chromeDoc
->GetActiveElement();
783 if (activeElementInChromeDoc
!= cbc
->Top()->GetEmbedderElement()) {
784 // Reject if the request is not from web content that is in the focused tab.
785 MOZ_CLIPBOARD_LOG("%s: reject due to not in the focused tab", __FUNCTION__
);
786 aCallback
->OnError(NS_ERROR_FAILURE
);
790 // If there is a pending user confirmation request, check if we could reuse
791 // it. If not, reject the request.
792 if (sUserConfirmationRequest
) {
793 if (sUserConfirmationRequest
->IsEqual(aClipboardType
, chromeDoc
,
794 aRequestingPrincipal
)) {
795 sUserConfirmationRequest
->AddClipboardGetRequest(aFlavorList
, aCallback
);
799 aCallback
->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR
);
803 nsresult rv
= NS_ERROR_FAILURE
;
804 nsCOMPtr
<nsIPromptService
> promptService
=
805 do_GetService("@mozilla.org/prompter;1", &rv
);
807 aCallback
->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR
);
811 RefPtr
<mozilla::dom::Promise
> promise
;
812 if (NS_FAILED(promptService
->ConfirmUserPaste(aWindowContext
->Canonical(),
813 getter_AddRefs(promise
)))) {
814 aCallback
->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR
);
818 sUserConfirmationRequest
= new UserConfirmationRequest(
819 aClipboardType
, chromeDoc
, aRequestingPrincipal
, this);
820 sUserConfirmationRequest
->AddClipboardGetRequest(aFlavorList
, aCallback
);
821 promise
->AppendNativeHandler(sUserConfirmationRequest
);
824 NS_IMPL_ISUPPORTS(nsBaseClipboard::AsyncGetClipboardData
,
825 nsIAsyncGetClipboardData
)
827 nsBaseClipboard::AsyncGetClipboardData::AsyncGetClipboardData(
828 int32_t aClipboardType
, int32_t aSequenceNumber
,
829 nsTArray
<nsCString
>&& aFlavors
, bool aFromCache
,
830 nsBaseClipboard
* aClipboard
)
831 : mClipboardType(aClipboardType
),
832 mSequenceNumber(aSequenceNumber
),
833 mFlavors(std::move(aFlavors
)),
834 mFromCache(aFromCache
),
835 mClipboard(aClipboard
) {
836 MOZ_ASSERT(mClipboard
);
838 mClipboard
->nsIClipboard::IsClipboardTypeSupported(mClipboardType
));
841 NS_IMETHODIMP
nsBaseClipboard::AsyncGetClipboardData::GetValid(
843 *aOutResult
= IsValid();
847 NS_IMETHODIMP
nsBaseClipboard::AsyncGetClipboardData::GetFlavorList(
848 nsTArray
<nsCString
>& aFlavors
) {
849 aFlavors
.AppendElements(mFlavors
);
853 NS_IMETHODIMP
nsBaseClipboard::AsyncGetClipboardData::GetData(
854 nsITransferable
* aTransferable
,
855 nsIAsyncClipboardRequestCallback
* aCallback
) {
856 MOZ_CLIPBOARD_LOG("AsyncGetClipboardData::GetData: %p", this);
858 if (!aTransferable
|| !aCallback
) {
859 return NS_ERROR_INVALID_ARG
;
862 nsTArray
<nsCString
> flavors
;
863 nsresult rv
= aTransferable
->FlavorsTransferableCanImport(flavors
);
868 // If the requested flavor is not in the list, throw an error.
869 for (const auto& flavor
: flavors
) {
870 if (!mFlavors
.Contains(flavor
)) {
871 return NS_ERROR_FAILURE
;
876 aCallback
->OnComplete(NS_ERROR_FAILURE
);
880 MOZ_ASSERT(mClipboard
);
883 const auto* clipboardCache
=
884 mClipboard
->GetClipboardCacheIfValid(mClipboardType
);
885 // `IsValid()` above ensures we should get a valid cache and matched
886 // sequence number here.
887 MOZ_DIAGNOSTIC_ASSERT(clipboardCache
);
888 MOZ_DIAGNOSTIC_ASSERT(clipboardCache
->GetSequenceNumber() ==
890 if (NS_SUCCEEDED(clipboardCache
->GetData(aTransferable
))) {
891 aCallback
->OnComplete(NS_OK
);
895 // At this point we can't satisfy the request from cache data so let's look
896 // for things other people put on the system clipboard.
899 // Since this is an async operation, we need to check if the data is still
900 // valid after we get the result.
901 mClipboard
->AsyncGetNativeClipboardData(
902 aTransferable
, mClipboardType
,
903 [callback
= nsCOMPtr
{aCallback
}, self
= RefPtr
{this}](nsresult aResult
) {
904 // `IsValid()` checks the clipboard sequence number to ensure the data
905 // we are requesting is still valid.
906 callback
->OnComplete(self
->IsValid() ? aResult
: NS_ERROR_FAILURE
);
911 bool nsBaseClipboard::AsyncGetClipboardData::IsValid() {
916 // If the data should from cache, check if cache is still valid or the
917 // sequence numbers are matched.
919 const auto* clipboardCache
=
920 mClipboard
->GetClipboardCacheIfValid(mClipboardType
);
921 if (!clipboardCache
) {
922 mClipboard
= nullptr;
926 return mSequenceNumber
== clipboardCache
->GetSequenceNumber();
930 mClipboard
->GetNativeClipboardSequenceNumber(mClipboardType
);
931 if (resultOrError
.isErr()) {
932 mClipboard
= nullptr;
936 if (mSequenceNumber
!= resultOrError
.unwrap()) {
937 mClipboard
= nullptr;
944 nsBaseClipboard::ClipboardCache
* nsBaseClipboard::GetClipboardCacheIfValid(
945 int32_t aClipboardType
) {
946 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
948 const mozilla::UniquePtr
<ClipboardCache
>& cache
= mCaches
[aClipboardType
];
951 if (!cache
->GetTransferable()) {
952 MOZ_ASSERT(cache
->GetSequenceNumber() == -1);
956 auto changeCountOrError
= GetNativeClipboardSequenceNumber(aClipboardType
);
957 if (changeCountOrError
.isErr()) {
961 if (changeCountOrError
.unwrap() != cache
->GetSequenceNumber()) {
962 // Clipboard cache is invalid, clear it.
970 void nsBaseClipboard::ClipboardCache::Clear() {
971 if (mClipboardOwner
) {
972 mClipboardOwner
->LosingOwnership(mTransferable
);
973 mClipboardOwner
= nullptr;
975 mTransferable
= nullptr;
976 mSequenceNumber
= -1;
979 nsresult
nsBaseClipboard::ClipboardCache::GetData(
980 nsITransferable
* aTransferable
) const {
981 MOZ_ASSERT(aTransferable
);
982 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
984 // get flavor list that includes all acceptable flavors (including ones
985 // obtained through conversion)
986 nsTArray
<nsCString
> flavors
;
987 if (NS_FAILED(aTransferable
->FlavorsTransferableCanImport(flavors
))) {
988 return NS_ERROR_FAILURE
;
991 MOZ_ASSERT(mTransferable
);
992 for (const auto& flavor
: flavors
) {
993 nsCOMPtr
<nsISupports
> dataSupports
;
994 // XXX Maybe we need special check for image as we always put the image as
995 // "native" on the clipboard.
996 if (NS_SUCCEEDED(mTransferable
->GetTransferData(
997 flavor
.get(), getter_AddRefs(dataSupports
)))) {
998 MOZ_CLIPBOARD_LOG("%s: getting %s from cache.", __FUNCTION__
,
1000 aTransferable
->SetTransferData(flavor
.get(), dataSupports
);
1001 // XXX we only read the first available type from native clipboard, so
1002 // make cache behave the same.
1007 return NS_ERROR_FAILURE
;