Bug 1843499 - Part 5: Remove AsyncFunctionResolveKind. r=iain
[gecko.git] / widget / nsBaseClipboard.cpp
blobb4a9cd6d3dd6270ac3be4717870668c1936b8b4a
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"
23 #include "nsError.h"
24 #include "nsXPCOM.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;
36 namespace {
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 {
49 public:
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) {
61 MOZ_ASSERT(
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) {
98 MOZ_ASSERT(request);
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) {
107 MOZ_ASSERT(request);
108 MOZ_ASSERT(!request->mFlavorList.IsEmpty());
109 MOZ_ASSERT(request->mCallback);
110 mClipboard->AsyncGetDataInternal(request->mFlavorList, mClipboardType,
111 request->mCallback);
115 nsTArray<UniquePtr<ClipboardGetRequest>>& GetPendingClipboardGetRequests() {
116 return mPendingClipboardGetRequests;
119 private:
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)
134 NS_INTERFACE_MAP_END
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));
151 if (NS_FAILED(rv)) {
152 RejectPendingClipboardGetRequests(rv);
153 return;
156 bool result = false;
157 rv = propBag->GetPropertyAsBool(u"ok"_ns, &result);
158 if (NS_FAILED(rv)) {
159 RejectPendingClipboardGetRequests(rv);
160 return;
163 if (!result) {
164 RejectPendingClipboardGetRequests(NS_ERROR_DOM_NOT_ALLOWED_ERR);
165 return;
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);
179 } // namespace
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);
191 MOZ_ASSERT(
192 mClipboard->nsIClipboard::IsClipboardTypeSupported(mClipboardType));
195 NS_IMETHODIMP
196 nsBaseClipboard::AsyncSetClipboardData::SetData(nsITransferable* aTransferable,
197 nsIClipboardOwner* aOwner) {
198 MOZ_CLIPBOARD_LOG("AsyncSetClipboardData::SetData (%p): clipboard=%d", this,
199 mClipboardType);
201 if (!IsValid()) {
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);
215 MOZ_ASSERT(
216 mClipboard->nsIClipboard::IsClipboardTypeSupported(mClipboardType));
217 MOZ_DIAGNOSTIC_ASSERT(mClipboard->mPendingWriteRequests[mClipboardType] ==
218 this);
220 RefPtr<AsyncSetClipboardData> request =
221 std::move(mClipboard->mPendingWriteRequests[mClipboardType]);
222 nsresult rv = mClipboard->SetData(aTransferable, aOwner, mClipboardType);
223 MaybeNotifyCallback(rv);
225 return rv;
228 NS_IMETHODIMP
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);
238 return NS_OK;
241 void nsBaseClipboard::AsyncSetClipboardData::MaybeNotifyCallback(
242 nsresult aResult) {
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
252 // this request.
253 mClipboard = nullptr;
256 void nsBaseClipboard::RejectPendingAsyncSetDataRequestIfAny(
257 int32_t aClipboardType) {
258 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
259 auto& request = mPendingWriteRequests[aClipboardType];
260 if (request) {
261 request->Abort(NS_ERROR_ABORT);
262 request = nullptr;
266 NS_IMETHODIMP nsBaseClipboard::AsyncSetData(
267 int32_t aWhichClipboard, nsIAsyncClipboardRequestCallback* aCallback,
268 nsIAsyncSetClipboardData** _retval) {
269 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
271 *_retval = nullptr;
272 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
273 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
274 aWhichClipboard);
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,
284 aCallback);
285 mPendingWriteRequests[aWhichClipboard] = request;
286 request.forget(_retval);
287 return NS_OK;
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) {
308 if (request) {
309 request->Abort(NS_ERROR_ABORT);
310 request = nullptr;
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__,
330 aWhichClipboard);
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__);
348 return NS_OK;
351 clipboardCache->Clear();
353 nsresult rv = NS_ERROR_FAILURE;
354 if (aTransferable) {
355 mIgnoreEmptyNotification = true;
356 // Reject existing pending asyncSetData request if any.
357 RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard);
358 rv = SetNativeClipboardData(aTransferable, aWhichClipboard);
359 mIgnoreEmptyNotification = false;
361 if (NS_FAILED(rv)) {
362 MOZ_CLIPBOARD_LOG("%s: setting native clipboard data failed.",
363 __FUNCTION__);
364 return rv;
367 auto result = GetNativeClipboardSequenceNumber(aWhichClipboard);
368 if (result.isErr()) {
369 MOZ_CLIPBOARD_LOG("%s: getting native clipboard change count failed.",
370 __FUNCTION__);
371 return result.unwrapErr();
374 clipboardCache->Update(aTransferable, aOwner, result.unwrap());
375 return NS_OK;
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__,
406 aWhichClipboard);
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.
414 if (NS_SUCCEEDED(
415 GetDataFromClipboardCache(aTransferable, aWhichClipboard))) {
416 // maybe try to fill in more types? Is there a point?
417 return NS_OK;
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());
439 return;
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()) {
449 MOZ_CLIPBOARD_LOG(
450 "%s: unable to get available flavors for clipboard %d.",
451 __FUNCTION__, aWhichClipboard);
452 callback->OnError(aFlavorsOrError.unwrapErr());
453 return;
456 auto sequenceNumberOrError =
457 self->GetNativeClipboardSequenceNumber(aWhichClipboard);
458 if (sequenceNumberOrError.isErr()) {
459 MOZ_CLIPBOARD_LOG(
460 "%s: unable to get sequence number for clipboard %d.",
461 __FUNCTION__, aWhichClipboard);
462 callback->OnError(sequenceNumberOrError.unwrapErr());
463 return;
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);
472 return;
475 if (aRetryCount > 0) {
476 MOZ_CLIPBOARD_LOG(
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);
482 return;
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__,
503 aWhichClipboard);
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);
514 return NS_OK;
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();
521 MOZ_ASSERT(trans);
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.",
526 __FUNCTION__);
527 AsyncGetDataInternal(aFlavorList, aWhichClipboard, aCallback);
528 return NS_OK;
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,
541 aCallback);
542 return NS_OK;
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);
581 return;
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__,
598 aWhichClipboard);
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?");
612 return NS_OK;
615 clipboardCache->Clear();
617 return NS_OK;
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);
635 if (NS_FAILED(rv)) {
636 return mozilla::Err(rv);
639 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
640 MOZ_CLIPBOARD_LOG(" Cached transferable types (nums %zu)\n",
641 flavors.Length());
642 for (const auto& flavor : flavors) {
643 MOZ_CLIPBOARD_LOG(" MIME %s", flavor.get());
647 return std::move(flavors);
650 NS_IMETHODIMP
651 nsBaseClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
652 int32_t aWhichClipboard,
653 bool* aOutResult) {
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",
657 aWhichClipboard);
658 for (const auto& flavor : aFlavorList) {
659 MOZ_CLIPBOARD_LOG(" MIME %s", flavor.get());
663 *aOutResult = false;
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());
673 *aOutResult = true;
674 return NS_OK;
681 auto resultOrError =
682 HasNativeClipboardDataMatchingFlavors(aFlavorList, aWhichClipboard);
683 if (resultOrError.isErr()) {
684 MOZ_CLIPBOARD_LOG(
685 "%s: checking native clipboard data matching flavors falied.",
686 __FUNCTION__);
687 return resultOrError.unwrapErr();
690 *aOutResult = resultOrError.unwrap();
691 return NS_OK;
694 NS_IMETHODIMP
695 nsBaseClipboard::IsClipboardTypeSupported(int32_t aWhichClipboard,
696 bool* aRetval) {
697 NS_ENSURE_ARG_POINTER(aRetval);
698 switch (aWhichClipboard) {
699 case kGlobalClipboard:
700 // We always support the global clipboard.
701 *aRetval = true;
702 return NS_OK;
703 case kSelectionClipboard:
704 *aRetval = mClipboardCaps.supportsSelectionClipboard();
705 return NS_OK;
706 case kFindClipboard:
707 *aRetval = mClipboardCaps.supportsFindClipboard();
708 return NS_OK;
709 case kSelectionCache:
710 *aRetval = mClipboardCaps.supportsSelectionCache();
711 return NS_OK;
712 default:
713 *aRetval = false;
714 return NS_OK;
718 void nsBaseClipboard::AsyncHasNativeClipboardDataMatchingFlavors(
719 const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
720 HasMatchingFlavorsCallback&& aCallback) {
721 MOZ_DIAGNOSTIC_ASSERT(
722 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
724 MOZ_CLIPBOARD_LOG(
725 "nsBaseClipboard::AsyncHasNativeClipboardDataMatchingFlavors: "
726 "clipboard=%d",
727 aWhichClipboard);
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];
749 MOZ_ASSERT(cache);
750 cache->Clear();
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);
763 return;
766 CanonicalBrowsingContext* cbc =
767 CanonicalBrowsingContext::Cast(aWindowContext->GetBrowsingContext());
768 MOZ_ASSERT(
769 cbc->IsContent(),
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",
776 __FUNCTION__);
777 aCallback->OnError(NS_ERROR_FAILURE);
778 return;
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);
787 return;
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);
796 return;
799 aCallback->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR);
800 return;
803 nsresult rv = NS_ERROR_FAILURE;
804 nsCOMPtr<nsIPromptService> promptService =
805 do_GetService("@mozilla.org/prompter;1", &rv);
806 if (NS_FAILED(rv)) {
807 aCallback->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR);
808 return;
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);
815 return;
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);
837 MOZ_ASSERT(
838 mClipboard->nsIClipboard::IsClipboardTypeSupported(mClipboardType));
841 NS_IMETHODIMP nsBaseClipboard::AsyncGetClipboardData::GetValid(
842 bool* aOutResult) {
843 *aOutResult = IsValid();
844 return NS_OK;
847 NS_IMETHODIMP nsBaseClipboard::AsyncGetClipboardData::GetFlavorList(
848 nsTArray<nsCString>& aFlavors) {
849 aFlavors.AppendElements(mFlavors);
850 return NS_OK;
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);
864 if (NS_FAILED(rv)) {
865 return rv;
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;
875 if (!IsValid()) {
876 aCallback->OnComplete(NS_ERROR_FAILURE);
877 return NS_OK;
880 MOZ_ASSERT(mClipboard);
882 if (mFromCache) {
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() ==
889 mSequenceNumber);
890 if (NS_SUCCEEDED(clipboardCache->GetData(aTransferable))) {
891 aCallback->OnComplete(NS_OK);
892 return 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);
908 return NS_OK;
911 bool nsBaseClipboard::AsyncGetClipboardData::IsValid() {
912 if (!mClipboard) {
913 return false;
916 // If the data should from cache, check if cache is still valid or the
917 // sequence numbers are matched.
918 if (mFromCache) {
919 const auto* clipboardCache =
920 mClipboard->GetClipboardCacheIfValid(mClipboardType);
921 if (!clipboardCache) {
922 mClipboard = nullptr;
923 return false;
926 return mSequenceNumber == clipboardCache->GetSequenceNumber();
929 auto resultOrError =
930 mClipboard->GetNativeClipboardSequenceNumber(mClipboardType);
931 if (resultOrError.isErr()) {
932 mClipboard = nullptr;
933 return false;
936 if (mSequenceNumber != resultOrError.unwrap()) {
937 mClipboard = nullptr;
938 return false;
941 return true;
944 nsBaseClipboard::ClipboardCache* nsBaseClipboard::GetClipboardCacheIfValid(
945 int32_t aClipboardType) {
946 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
948 const mozilla::UniquePtr<ClipboardCache>& cache = mCaches[aClipboardType];
949 MOZ_ASSERT(cache);
951 if (!cache->GetTransferable()) {
952 MOZ_ASSERT(cache->GetSequenceNumber() == -1);
953 return nullptr;
956 auto changeCountOrError = GetNativeClipboardSequenceNumber(aClipboardType);
957 if (changeCountOrError.isErr()) {
958 return nullptr;
961 if (changeCountOrError.unwrap() != cache->GetSequenceNumber()) {
962 // Clipboard cache is invalid, clear it.
963 cache->Clear();
964 return nullptr;
967 return cache.get();
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__,
999 flavor.get());
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.
1003 return NS_OK;
1007 return NS_ERROR_FAILURE;