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/StaticPrefs_widget.h"
9 #include "nsIClipboardOwner.h"
13 using mozilla::GenericPromise
;
14 using mozilla::LogLevel
;
15 using mozilla::UniquePtr
;
16 using mozilla::dom::ClipboardCapabilities
;
18 NS_IMPL_ISUPPORTS(nsBaseClipboard::AsyncSetClipboardData
,
19 nsIAsyncSetClipboardData
)
21 nsBaseClipboard::AsyncSetClipboardData::AsyncSetClipboardData(
22 int32_t aClipboardType
, nsBaseClipboard
* aClipboard
,
23 nsIAsyncSetClipboardDataCallback
* aCallback
)
24 : mClipboardType(aClipboardType
),
25 mClipboard(aClipboard
),
26 mCallback(aCallback
) {
27 MOZ_ASSERT(mClipboard
);
29 mClipboard
->nsIClipboard::IsClipboardTypeSupported(mClipboardType
));
33 nsBaseClipboard::AsyncSetClipboardData::SetData(nsITransferable
* aTransferable
,
34 nsIClipboardOwner
* aOwner
) {
35 MOZ_CLIPBOARD_LOG("AsyncSetClipboardData::SetData (%p): clipboard=%d", this,
39 return NS_ERROR_FAILURE
;
42 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
43 nsTArray
<nsCString
> flavors
;
44 if (NS_SUCCEEDED(aTransferable
->FlavorsTransferableCanImport(flavors
))) {
45 for (const auto& flavor
: flavors
) {
46 MOZ_CLIPBOARD_LOG(" MIME %s", flavor
.get());
51 MOZ_ASSERT(mClipboard
);
53 mClipboard
->nsIClipboard::IsClipboardTypeSupported(mClipboardType
));
54 MOZ_DIAGNOSTIC_ASSERT(mClipboard
->mPendingWriteRequests
[mClipboardType
] ==
57 RefPtr
<AsyncSetClipboardData
> request
=
58 std::move(mClipboard
->mPendingWriteRequests
[mClipboardType
]);
59 nsresult rv
= mClipboard
->SetData(aTransferable
, aOwner
, mClipboardType
);
60 MaybeNotifyCallback(rv
);
66 nsBaseClipboard::AsyncSetClipboardData::Abort(nsresult aReason
) {
67 // Note: This may be called during destructor, so it should not attempt to
68 // take a reference to mClipboard.
70 if (!IsValid() || !NS_FAILED(aReason
)) {
71 return NS_ERROR_FAILURE
;
74 MaybeNotifyCallback(aReason
);
78 void nsBaseClipboard::AsyncSetClipboardData::MaybeNotifyCallback(
80 // Note: This may be called during destructor, so it should not attempt to
81 // take a reference to mClipboard.
83 MOZ_ASSERT(IsValid());
84 if (nsCOMPtr
<nsIAsyncSetClipboardDataCallback
> callback
=
86 callback
->OnComplete(aResult
);
88 // Once the callback is notified, setData should not be allowed, so invalidate
93 void nsBaseClipboard::RejectPendingAsyncSetDataRequestIfAny(
94 int32_t aClipboardType
) {
95 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
96 auto& request
= mPendingWriteRequests
[aClipboardType
];
98 request
->Abort(NS_ERROR_ABORT
);
103 NS_IMETHODIMP
nsBaseClipboard::AsyncSetData(
104 int32_t aWhichClipboard
, nsIAsyncSetClipboardDataCallback
* aCallback
,
105 nsIAsyncSetClipboardData
** _retval
) {
106 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
109 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
110 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
112 return NS_ERROR_DOM_NOT_SUPPORTED_ERR
;
115 // Reject existing pending AsyncSetData request if any.
116 RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard
);
118 // Create a new AsyncSetClipboardData.
119 RefPtr
<AsyncSetClipboardData
> request
=
120 mozilla::MakeRefPtr
<AsyncSetClipboardData
>(aWhichClipboard
, this,
122 mPendingWriteRequests
[aWhichClipboard
] = request
;
123 request
.forget(_retval
);
127 nsBaseClipboard::nsBaseClipboard(const ClipboardCapabilities
& aClipboardCaps
)
128 : mClipboardCaps(aClipboardCaps
) {
129 using mozilla::MakeUnique
;
130 // Initialize clipboard cache.
131 mCaches
[kGlobalClipboard
] = MakeUnique
<ClipboardCache
>();
132 if (mClipboardCaps
.supportsSelectionClipboard()) {
133 mCaches
[kSelectionClipboard
] = MakeUnique
<ClipboardCache
>();
135 if (mClipboardCaps
.supportsFindClipboard()) {
136 mCaches
[kFindClipboard
] = MakeUnique
<ClipboardCache
>();
138 if (mClipboardCaps
.supportsSelectionCache()) {
139 mCaches
[kSelectionCache
] = MakeUnique
<ClipboardCache
>();
143 nsBaseClipboard::~nsBaseClipboard() {
144 for (auto& request
: mPendingWriteRequests
) {
146 request
->Abort(NS_ERROR_ABORT
);
152 NS_IMPL_ISUPPORTS(nsBaseClipboard
, nsIClipboard
)
155 * Sets the transferable object
158 NS_IMETHODIMP
nsBaseClipboard::SetData(nsITransferable
* aTransferable
,
159 nsIClipboardOwner
* aOwner
,
160 int32_t aWhichClipboard
) {
161 NS_ASSERTION(aTransferable
, "clipboard given a null transferable");
163 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
165 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
166 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
168 return NS_ERROR_FAILURE
;
171 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
172 nsTArray
<nsCString
> flavors
;
173 if (NS_SUCCEEDED(aTransferable
->FlavorsTransferableCanImport(flavors
))) {
174 for (const auto& flavor
: flavors
) {
175 MOZ_CLIPBOARD_LOG(" MIME %s", flavor
.get());
180 const auto& clipboardCache
= mCaches
[aWhichClipboard
];
181 MOZ_ASSERT(clipboardCache
);
182 if (aTransferable
== clipboardCache
->GetTransferable() &&
183 aOwner
== clipboardCache
->GetClipboardOwner()) {
184 MOZ_CLIPBOARD_LOG("%s: skipping update.", __FUNCTION__
);
188 clipboardCache
->Clear();
190 nsresult rv
= NS_ERROR_FAILURE
;
192 mIgnoreEmptyNotification
= true;
193 // Reject existing pending asyncSetData request if any.
194 RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard
);
195 rv
= SetNativeClipboardData(aTransferable
, aWhichClipboard
);
196 mIgnoreEmptyNotification
= false;
199 MOZ_CLIPBOARD_LOG("%s: setting native clipboard data failed.",
204 auto result
= GetNativeClipboardSequenceNumber(aWhichClipboard
);
205 if (result
.isErr()) {
206 MOZ_CLIPBOARD_LOG("%s: getting native clipboard change count failed.",
208 return result
.unwrapErr();
211 clipboardCache
->Update(aTransferable
, aOwner
, result
.unwrap());
215 nsresult
nsBaseClipboard::GetDataFromClipboardCache(
216 nsITransferable
* aTransferable
, int32_t aClipboardType
) {
217 MOZ_ASSERT(aTransferable
);
218 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
219 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
221 const auto* clipboardCache
= GetClipboardCacheIfValid(aClipboardType
);
222 if (!clipboardCache
) {
223 return NS_ERROR_FAILURE
;
226 nsITransferable
* cachedTransferable
= clipboardCache
->GetTransferable();
227 MOZ_ASSERT(cachedTransferable
);
229 // get flavor list that includes all acceptable flavors (including ones
230 // obtained through conversion)
231 nsTArray
<nsCString
> flavors
;
232 if (NS_FAILED(aTransferable
->FlavorsTransferableCanImport(flavors
))) {
233 return NS_ERROR_FAILURE
;
236 for (const auto& flavor
: flavors
) {
237 nsCOMPtr
<nsISupports
> dataSupports
;
238 if (NS_SUCCEEDED(cachedTransferable
->GetTransferData(
239 flavor
.get(), getter_AddRefs(dataSupports
)))) {
240 MOZ_CLIPBOARD_LOG("%s: getting %s from cache.", __FUNCTION__
,
242 aTransferable
->SetTransferData(flavor
.get(), dataSupports
);
243 // maybe try to fill in more types? Is there a point?
248 return NS_ERROR_FAILURE
;
252 * Gets the transferable object from system clipboard.
254 NS_IMETHODIMP
nsBaseClipboard::GetData(nsITransferable
* aTransferable
,
255 int32_t aWhichClipboard
) {
256 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
258 if (!aTransferable
) {
259 NS_ASSERTION(false, "clipboard given a null transferable");
260 return NS_ERROR_FAILURE
;
263 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
264 // If we were the last ones to put something on the native clipboard, then
265 // just use the cached transferable. Otherwise clear it because it isn't
266 // relevant any more.
268 GetDataFromClipboardCache(aTransferable
, aWhichClipboard
))) {
269 // maybe try to fill in more types? Is there a point?
273 // at this point we can't satisfy the request from cache data so let's look
274 // for things other people put on the system clipboard
277 return GetNativeClipboardData(aTransferable
, aWhichClipboard
);
280 RefPtr
<GenericPromise
> nsBaseClipboard::AsyncGetData(
281 nsITransferable
* aTransferable
, int32_t aWhichClipboard
) {
282 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
284 if (!aTransferable
) {
285 NS_ASSERTION(false, "clipboard given a null transferable");
286 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE
, __func__
);
289 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
290 // If we were the last ones to put something on the native clipboard, then
291 // just use the cached transferable. Otherwise clear it because it isn't
292 // relevant any more.
294 GetDataFromClipboardCache(aTransferable
, aWhichClipboard
))) {
295 // maybe try to fill in more types? Is there a point?
296 return GenericPromise::CreateAndResolve(true, __func__
);
299 // at this point we can't satisfy the request from cache data so let's look
300 // for things other people put on the system clipboard
303 RefPtr
<GenericPromise::Private
> dataPromise
=
304 mozilla::MakeRefPtr
<GenericPromise::Private
>(__func__
);
305 AsyncGetNativeClipboardData(aTransferable
, aWhichClipboard
,
306 [dataPromise
](nsresult aResult
) {
307 if (NS_FAILED(aResult
)) {
308 dataPromise
->Reject(aResult
, __func__
);
312 dataPromise
->Resolve(true, __func__
);
314 return dataPromise
.forget();
317 NS_IMETHODIMP
nsBaseClipboard::EmptyClipboard(int32_t aWhichClipboard
) {
318 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
320 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
321 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
323 return NS_ERROR_FAILURE
;
326 EmptyNativeClipboardData(aWhichClipboard
);
328 const auto& clipboardCache
= mCaches
[aWhichClipboard
];
329 MOZ_ASSERT(clipboardCache
);
331 if (mIgnoreEmptyNotification
) {
332 MOZ_DIAGNOSTIC_ASSERT(!clipboardCache
->GetTransferable() &&
333 !clipboardCache
->GetClipboardOwner() &&
334 clipboardCache
->GetSequenceNumber() == -1,
335 "How did we have data in clipboard cache here?");
339 clipboardCache
->Clear();
344 mozilla::Result
<nsTArray
<nsCString
>, nsresult
>
345 nsBaseClipboard::GetFlavorsFromClipboardCache(int32_t aClipboardType
) {
346 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
347 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
349 const auto* clipboardCache
= GetClipboardCacheIfValid(aClipboardType
);
350 if (!clipboardCache
) {
351 return mozilla::Err(NS_ERROR_FAILURE
);
354 nsITransferable
* cachedTransferable
= clipboardCache
->GetTransferable();
355 MOZ_ASSERT(cachedTransferable
);
357 nsTArray
<nsCString
> flavors
;
358 nsresult rv
= cachedTransferable
->FlavorsTransferableCanImport(flavors
);
360 return mozilla::Err(rv
);
363 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
364 MOZ_CLIPBOARD_LOG(" Cached transferable types (nums %zu)\n",
366 for (const auto& flavor
: flavors
) {
367 MOZ_CLIPBOARD_LOG(" MIME %s", flavor
.get());
371 return std::move(flavors
);
375 nsBaseClipboard::HasDataMatchingFlavors(const nsTArray
<nsCString
>& aFlavorList
,
376 int32_t aWhichClipboard
,
378 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
379 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
380 MOZ_CLIPBOARD_LOG(" Asking for content clipboard=%i:\n",
382 for (const auto& flavor
: aFlavorList
) {
383 MOZ_CLIPBOARD_LOG(" MIME %s", flavor
.get());
389 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
390 // First, check if we have valid data in our cached transferable.
391 auto flavorsOrError
= GetFlavorsFromClipboardCache(aWhichClipboard
);
392 if (flavorsOrError
.isOk()) {
393 for (const auto& transferableFlavor
: flavorsOrError
.unwrap()) {
394 for (const auto& flavor
: aFlavorList
) {
395 if (transferableFlavor
.Equals(flavor
)) {
396 MOZ_CLIPBOARD_LOG(" has %s", flavor
.get());
406 HasNativeClipboardDataMatchingFlavors(aFlavorList
, aWhichClipboard
);
407 if (resultOrError
.isErr()) {
409 "%s: checking native clipboard data matching flavors falied.",
411 return resultOrError
.unwrapErr();
414 *aOutResult
= resultOrError
.unwrap();
418 RefPtr
<DataFlavorsPromise
> nsBaseClipboard::AsyncHasDataMatchingFlavors(
419 const nsTArray
<nsCString
>& aFlavorList
, int32_t aWhichClipboard
) {
420 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
421 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
422 MOZ_CLIPBOARD_LOG(" Asking for content clipboard=%i:\n",
424 for (const auto& flavor
: aFlavorList
) {
425 MOZ_CLIPBOARD_LOG(" MIME %s", flavor
.get());
429 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
430 // First, check if we have valid data in our cached transferable.
431 auto flavorsOrError
= GetFlavorsFromClipboardCache(aWhichClipboard
);
432 if (flavorsOrError
.isOk()) {
433 nsTArray
<nsCString
> results
;
434 for (const auto& transferableFlavor
: flavorsOrError
.unwrap()) {
435 for (const auto& flavor
: aFlavorList
) {
436 // XXX We need special check for image as we always put the image as
437 // "native" on the clipboard.
438 if (transferableFlavor
.Equals(flavor
) ||
439 (transferableFlavor
.Equals(kNativeImageMime
) &&
440 nsContentUtils::IsFlavorImage(flavor
))) {
441 MOZ_CLIPBOARD_LOG(" has %s", flavor
.get());
442 results
.AppendElement(flavor
);
446 return DataFlavorsPromise::CreateAndResolve(std::move(results
), __func__
);
450 RefPtr
<DataFlavorsPromise::Private
> flavorPromise
=
451 mozilla::MakeRefPtr
<DataFlavorsPromise::Private
>(__func__
);
452 AsyncHasNativeClipboardDataMatchingFlavors(
453 aFlavorList
, aWhichClipboard
, [flavorPromise
](auto aResultOrError
) {
454 if (aResultOrError
.isErr()) {
455 flavorPromise
->Reject(aResultOrError
.unwrapErr(), __func__
);
459 flavorPromise
->Resolve(std::move(aResultOrError
.unwrap()), __func__
);
461 return flavorPromise
.forget();
465 nsBaseClipboard::IsClipboardTypeSupported(int32_t aWhichClipboard
,
467 NS_ENSURE_ARG_POINTER(aRetval
);
468 switch (aWhichClipboard
) {
469 case kGlobalClipboard
:
470 // We always support the global clipboard.
473 case kSelectionClipboard
:
474 *aRetval
= mClipboardCaps
.supportsSelectionClipboard();
477 *aRetval
= mClipboardCaps
.supportsFindClipboard();
479 case kSelectionCache
:
480 *aRetval
= mClipboardCaps
.supportsSelectionCache();
488 void nsBaseClipboard::AsyncHasNativeClipboardDataMatchingFlavors(
489 const nsTArray
<nsCString
>& aFlavorList
, int32_t aWhichClipboard
,
490 HasMatchingFlavorsCallback
&& aCallback
) {
491 MOZ_DIAGNOSTIC_ASSERT(
492 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
));
495 "nsBaseClipboard::AsyncHasNativeClipboardDataMatchingFlavors: "
499 nsTArray
<nsCString
> results
;
500 for (const auto& flavor
: aFlavorList
) {
501 auto resultOrError
= HasNativeClipboardDataMatchingFlavors(
502 AutoTArray
<nsCString
, 1>{flavor
}, aWhichClipboard
);
503 if (resultOrError
.isOk() && resultOrError
.unwrap()) {
504 results
.AppendElement(flavor
);
507 aCallback(std::move(results
));
510 void nsBaseClipboard::AsyncGetNativeClipboardData(
511 nsITransferable
* aTransferable
, int32_t aWhichClipboard
,
512 GetDataCallback
&& aCallback
) {
513 aCallback(GetNativeClipboardData(aTransferable
, aWhichClipboard
));
516 void nsBaseClipboard::ClearClipboardCache(int32_t aClipboardType
) {
517 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
518 const mozilla::UniquePtr
<ClipboardCache
>& cache
= mCaches
[aClipboardType
];
523 nsBaseClipboard::ClipboardCache
* nsBaseClipboard::GetClipboardCacheIfValid(
524 int32_t aClipboardType
) {
525 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
527 const mozilla::UniquePtr
<ClipboardCache
>& cache
= mCaches
[aClipboardType
];
530 if (!cache
->GetTransferable()) {
531 MOZ_ASSERT(cache
->GetSequenceNumber() == -1);
535 auto changeCountOrError
= GetNativeClipboardSequenceNumber(aClipboardType
);
536 if (changeCountOrError
.isErr()) {
540 if (changeCountOrError
.unwrap() != cache
->GetSequenceNumber()) {
541 // Clipboard cache is invalid, clear it.
549 void nsBaseClipboard::ClipboardCache::Clear() {
550 if (mClipboardOwner
) {
551 mClipboardOwner
->LosingOwnership(mTransferable
);
552 mClipboardOwner
= nullptr;
554 mTransferable
= nullptr;
555 mSequenceNumber
= -1;