Bug 1942006 - Upstream a variety of Servo-specific code from Servo's downstream fork...
[gecko.git] / widget / nsBaseClipboard.cpp
blob0c5485558f1acca12edd06d38bec04fc9554ed19
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/dom/BindingUtils.h"
11 #include "mozilla/dom/CanonicalBrowsingContext.h"
12 #include "mozilla/dom/Document.h"
13 #include "mozilla/dom/Promise.h"
14 #include "mozilla/dom/PromiseNativeHandler.h"
15 #include "mozilla/dom/WindowGlobalParent.h"
16 #include "mozilla/dom/WindowContext.h"
17 #include "mozilla/ErrorResult.h"
18 #include "mozilla/MoveOnlyFunction.h"
19 #include "mozilla/RefPtr.h"
20 #include "mozilla/Services.h"
21 #include "mozilla/StaticPrefs_dom.h"
22 #include "mozilla/StaticPrefs_widget.h"
23 #include "nsContentUtils.h"
24 #include "nsFocusManager.h"
25 #include "nsIClipboardOwner.h"
26 #include "nsIPromptService.h"
27 #include "nsError.h"
28 #include "nsXPCOM.h"
30 using mozilla::GenericPromise;
31 using mozilla::LogLevel;
32 using mozilla::UniquePtr;
33 using mozilla::dom::BrowsingContext;
34 using mozilla::dom::CanonicalBrowsingContext;
35 using mozilla::dom::ClipboardCapabilities;
36 using mozilla::dom::Document;
38 mozilla::LazyLogModule gWidgetClipboardLog("WidgetClipboard");
40 static const int32_t kGetAvailableFlavorsRetryCount = 5;
42 namespace {
44 struct ClipboardGetRequest {
45 ClipboardGetRequest(const nsTArray<nsCString>& aFlavorList,
46 nsIClipboardGetDataSnapshotCallback* aCallback)
47 : mFlavorList(aFlavorList.Clone()), mCallback(aCallback) {}
49 const nsTArray<nsCString> mFlavorList;
50 const nsCOMPtr<nsIClipboardGetDataSnapshotCallback> mCallback;
53 class UserConfirmationRequest final
54 : public mozilla::dom::PromiseNativeHandler {
55 public:
56 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
57 NS_DECL_CYCLE_COLLECTION_CLASS(UserConfirmationRequest)
59 UserConfirmationRequest(nsIClipboard::ClipboardType aClipboardType,
60 Document* aRequestingChromeDocument,
61 nsIPrincipal* aRequestingPrincipal,
62 nsBaseClipboard* aClipboard,
63 mozilla::dom::WindowContext* aRequestingWindowContext)
64 : mClipboardType(aClipboardType),
65 mRequestingChromeDocument(aRequestingChromeDocument),
66 mRequestingPrincipal(aRequestingPrincipal),
67 mClipboard(aClipboard),
68 mRequestingWindowContext(aRequestingWindowContext) {
69 MOZ_ASSERT(
70 mClipboard->nsIClipboard::IsClipboardTypeSupported(aClipboardType));
73 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
74 mozilla::ErrorResult& aRv) override;
76 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
77 mozilla::ErrorResult& aRv) override;
79 bool IsEqual(nsIClipboard::ClipboardType aClipboardType,
80 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))) {
87 return false;
89 // Only check requesting window contexts if content analysis is active
90 nsCOMPtr<nsIContentAnalysis> contentAnalysis =
91 mozilla::components::nsIContentAnalysis::Service();
92 if (!contentAnalysis) {
93 return false;
96 bool contentAnalysisIsActive;
97 nsresult rv = contentAnalysis->GetIsActive(&contentAnalysisIsActive);
98 if (MOZ_LIKELY(NS_FAILED(rv) || !contentAnalysisIsActive)) {
99 return true;
101 return mRequestingWindowContext->Id() == aRequestingWindowContext->Id();
104 nsIClipboard::ClipboardType 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 nsIClipboardGetDataSnapshotCallback* 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) {
124 MOZ_ASSERT(request);
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) {
133 MOZ_ASSERT(request);
134 MOZ_ASSERT(!request->mFlavorList.IsEmpty());
135 MOZ_ASSERT(request->mCallback);
136 mClipboard->GetDataSnapshotInternal(request->mFlavorList, mClipboardType,
137 mRequestingWindowContext,
138 request->mCallback);
142 nsTArray<UniquePtr<ClipboardGetRequest>>& GetPendingClipboardGetRequests() {
143 return mPendingClipboardGetRequests;
146 private:
147 ~UserConfirmationRequest() = default;
149 const nsIClipboard::ClipboardType 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)
162 NS_INTERFACE_MAP_END
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));
179 if (NS_FAILED(rv)) {
180 RejectPendingClipboardGetRequests(rv);
181 return;
184 bool result = false;
185 rv = propBag->GetPropertyAsBool(u"ok"_ns, &result);
186 if (NS_FAILED(rv)) {
187 RejectPendingClipboardGetRequests(rv);
188 return;
191 if (!result) {
192 RejectPendingClipboardGetRequests(NS_ERROR_DOM_NOT_ALLOWED_ERR);
193 return;
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);
207 } // namespace
209 NS_IMPL_ISUPPORTS(nsBaseClipboard::AsyncSetClipboardData,
210 nsIAsyncSetClipboardData)
212 nsBaseClipboard::AsyncSetClipboardData::AsyncSetClipboardData(
213 nsIClipboard::ClipboardType 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);
221 MOZ_ASSERT(
222 mClipboard->nsIClipboard::IsClipboardTypeSupported(mClipboardType));
225 NS_IMETHODIMP
226 nsBaseClipboard::AsyncSetClipboardData::SetData(nsITransferable* aTransferable,
227 nsIClipboardOwner* aOwner) {
228 MOZ_CLIPBOARD_LOG("AsyncSetClipboardData::SetData (%p): clipboard=%d", this,
229 mClipboardType);
231 if (!IsValid()) {
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);
245 MOZ_ASSERT(
246 mClipboard->nsIClipboard::IsClipboardTypeSupported(mClipboardType));
247 MOZ_DIAGNOSTIC_ASSERT(mClipboard->mPendingWriteRequests[mClipboardType] ==
248 this);
250 RefPtr<AsyncSetClipboardData> request =
251 std::move(mClipboard->mPendingWriteRequests[mClipboardType]);
252 nsresult rv = mClipboard->SetData(aTransferable, aOwner, mClipboardType,
253 mWindowContext);
254 MaybeNotifyCallback(rv);
256 return rv;
259 NS_IMETHODIMP
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);
269 return NS_OK;
272 void nsBaseClipboard::AsyncSetClipboardData::MaybeNotifyCallback(
273 nsresult aResult) {
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
283 // this request.
284 mClipboard = nullptr;
287 void nsBaseClipboard::RejectPendingAsyncSetDataRequestIfAny(
288 ClipboardType aClipboardType) {
289 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
290 auto& request = mPendingWriteRequests[aClipboardType];
291 if (request) {
292 request->Abort(NS_ERROR_ABORT);
293 request = nullptr;
297 NS_IMETHODIMP nsBaseClipboard::AsyncSetData(
298 ClipboardType aWhichClipboard,
299 mozilla::dom::WindowContext* aSettingWindowContext,
300 nsIAsyncClipboardRequestCallback* aCallback,
301 nsIAsyncSetClipboardData** _retval) {
302 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
304 *_retval = nullptr;
305 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
306 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
307 aWhichClipboard);
308 return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
311 // Reject existing pending AsyncSetData request if any.
312 RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard);
314 // Create a new AsyncSetClipboardData.
315 RefPtr<AsyncSetClipboardData> request =
316 mozilla::MakeRefPtr<AsyncSetClipboardData>(
317 aWhichClipboard, this, aSettingWindowContext, aCallback);
318 mPendingWriteRequests[aWhichClipboard] = request;
319 request.forget(_retval);
320 return NS_OK;
323 nsBaseClipboard::nsBaseClipboard(const ClipboardCapabilities& aClipboardCaps)
324 : mClipboardCaps(aClipboardCaps) {
325 using mozilla::MakeUnique;
326 // Initialize clipboard cache.
327 mCaches[kGlobalClipboard] = MakeUnique<ClipboardCache>();
328 if (mClipboardCaps.supportsSelectionClipboard()) {
329 mCaches[kSelectionClipboard] = MakeUnique<ClipboardCache>();
331 if (mClipboardCaps.supportsFindClipboard()) {
332 mCaches[kFindClipboard] = MakeUnique<ClipboardCache>();
334 if (mClipboardCaps.supportsSelectionCache()) {
335 mCaches[kSelectionCache] = MakeUnique<ClipboardCache>();
339 nsBaseClipboard::~nsBaseClipboard() {
340 for (auto& request : mPendingWriteRequests) {
341 if (request) {
342 request->Abort(NS_ERROR_ABORT);
343 request = nullptr;
348 NS_IMPL_ISUPPORTS(nsBaseClipboard, nsIClipboard)
351 * Sets the transferable object
354 NS_IMETHODIMP nsBaseClipboard::SetData(
355 nsITransferable* aTransferable, nsIClipboardOwner* aOwner,
356 ClipboardType aWhichClipboard,
357 mozilla::dom::WindowContext* aWindowContext) {
358 NS_ASSERTION(aTransferable, "clipboard given a null transferable");
360 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
362 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
363 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
364 aWhichClipboard);
365 return NS_ERROR_FAILURE;
368 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
369 nsTArray<nsCString> flavors;
370 if (NS_SUCCEEDED(aTransferable->FlavorsTransferableCanImport(flavors))) {
371 for (const auto& flavor : flavors) {
372 MOZ_CLIPBOARD_LOG(" MIME %s", flavor.get());
377 const auto& clipboardCache = mCaches[aWhichClipboard];
378 MOZ_ASSERT(clipboardCache);
379 if (aTransferable == clipboardCache->GetTransferable() &&
380 aOwner == clipboardCache->GetClipboardOwner()) {
381 MOZ_CLIPBOARD_LOG("%s: skipping update.", __FUNCTION__);
382 return NS_OK;
385 clipboardCache->Clear();
387 nsresult rv = NS_ERROR_FAILURE;
388 if (aTransferable) {
389 mIgnoreEmptyNotification = true;
390 // Reject existing pending asyncSetData request if any.
391 RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard);
392 rv = SetNativeClipboardData(aTransferable, aWhichClipboard);
393 mIgnoreEmptyNotification = false;
395 if (NS_FAILED(rv)) {
396 MOZ_CLIPBOARD_LOG("%s: setting native clipboard data failed.",
397 __FUNCTION__);
398 return rv;
401 auto result = GetNativeClipboardSequenceNumber(aWhichClipboard);
402 if (result.isErr()) {
403 MOZ_CLIPBOARD_LOG("%s: getting native clipboard change count failed.",
404 __FUNCTION__);
405 return result.unwrapErr();
408 clipboardCache->Update(aTransferable, aOwner, result.unwrap(),
409 aWindowContext
410 ? mozilla::Some(aWindowContext->InnerWindowId())
411 : mozilla::Nothing());
412 return NS_OK;
415 nsresult nsBaseClipboard::GetDataFromClipboardCache(
416 nsITransferable* aTransferable, ClipboardType aClipboardType) {
417 MOZ_ASSERT(aTransferable);
418 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
420 const auto* clipboardCache = GetClipboardCacheIfValid(aClipboardType);
421 if (!clipboardCache) {
422 return NS_ERROR_FAILURE;
424 return clipboardCache->GetData(aTransferable);
428 * Gets the transferable object from system clipboard.
430 NS_IMETHODIMP nsBaseClipboard::GetData(
431 nsITransferable* aTransferable, ClipboardType aWhichClipboard,
432 mozilla::dom::WindowContext* aWindowContext) {
433 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
435 if (!aTransferable) {
436 NS_ASSERTION(false, "clipboard given a null transferable");
437 return NS_ERROR_FAILURE;
440 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
441 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
442 aWhichClipboard);
443 return NS_ERROR_FAILURE;
446 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
447 // If we were the last ones to put something on the native clipboard, then
448 // just use the cached transferable. Otherwise clear it because it isn't
449 // relevant any more.
450 if (NS_SUCCEEDED(
451 GetDataFromClipboardCache(aTransferable, aWhichClipboard))) {
452 // maybe try to fill in more types? Is there a point?
453 if (!mozilla::contentanalysis::ContentAnalysis::
454 CheckClipboardContentAnalysisSync(
455 this, aWindowContext->Canonical(), aTransferable,
456 aWhichClipboard)) {
457 aTransferable->ClearAllData();
458 return NS_ERROR_CONTENT_BLOCKED;
460 return NS_OK;
463 // at this point we can't satisfy the request from cache data so let's look
464 // for things other people put on the system clipboard
466 nsresult rv = GetNativeClipboardData(aTransferable, aWhichClipboard);
467 if (NS_FAILED(rv)) {
468 return rv;
470 if (!mozilla::contentanalysis::ContentAnalysis::
471 CheckClipboardContentAnalysisSync(this, aWindowContext->Canonical(),
472 aTransferable, aWhichClipboard)) {
473 aTransferable->ClearAllData();
474 return NS_ERROR_CONTENT_BLOCKED;
476 return NS_OK;
479 void nsBaseClipboard::MaybeRetryGetAvailableFlavors(
480 const nsTArray<nsCString>& aFlavorList, ClipboardType aWhichClipboard,
481 nsIClipboardGetDataSnapshotCallback* aCallback, int32_t aRetryCount,
482 mozilla::dom::WindowContext* aRequestingWindowContext) {
483 // Note we have to get the clipboard sequence number first before the actual
484 // read. This is to use it to verify the clipboard data is still the one we
485 // try to read, instead of the later state.
486 auto sequenceNumberOrError =
487 GetNativeClipboardSequenceNumber(aWhichClipboard);
488 if (sequenceNumberOrError.isErr()) {
489 MOZ_CLIPBOARD_LOG("%s: unable to get sequence number for clipboard %d.",
490 __FUNCTION__, aWhichClipboard);
491 aCallback->OnError(sequenceNumberOrError.unwrapErr());
492 return;
495 int32_t sequenceNumber = sequenceNumberOrError.unwrap();
496 AsyncHasNativeClipboardDataMatchingFlavors(
497 aFlavorList, aWhichClipboard,
498 [self = RefPtr{this}, callback = nsCOMPtr{aCallback}, aWhichClipboard,
499 aRetryCount, flavorList = aFlavorList.Clone(), sequenceNumber,
500 requestingWindowContext =
501 RefPtr{aRequestingWindowContext}](auto aFlavorsOrError) {
502 if (aFlavorsOrError.isErr()) {
503 MOZ_CLIPBOARD_LOG(
504 "%s: unable to get available flavors for clipboard %d.",
505 __FUNCTION__, aWhichClipboard);
506 callback->OnError(aFlavorsOrError.unwrapErr());
507 return;
510 auto sequenceNumberOrError =
511 self->GetNativeClipboardSequenceNumber(aWhichClipboard);
512 if (sequenceNumberOrError.isErr()) {
513 MOZ_CLIPBOARD_LOG(
514 "%s: unable to get sequence number for clipboard %d.",
515 __FUNCTION__, aWhichClipboard);
516 callback->OnError(sequenceNumberOrError.unwrapErr());
517 return;
520 if (sequenceNumber == sequenceNumberOrError.unwrap()) {
521 auto clipboardDataSnapshot =
522 mozilla::MakeRefPtr<ClipboardDataSnapshot>(
523 aWhichClipboard, sequenceNumber,
524 std::move(aFlavorsOrError.unwrap()), false, self,
525 requestingWindowContext);
526 callback->OnSuccess(clipboardDataSnapshot);
527 return;
530 if (aRetryCount > 0) {
531 MOZ_CLIPBOARD_LOG(
532 "%s: clipboard=%d, ignore the data due to the sequence number "
533 "doesn't match, retry (%d) ..",
534 __FUNCTION__, aWhichClipboard, aRetryCount);
535 self->MaybeRetryGetAvailableFlavors(flavorList, aWhichClipboard,
536 callback, aRetryCount - 1,
537 requestingWindowContext);
538 return;
541 MOZ_DIAGNOSTIC_CRASH("How can this happen?!?");
542 callback->OnError(NS_ERROR_FAILURE);
546 NS_IMETHODIMP nsBaseClipboard::GetDataSnapshot(
547 const nsTArray<nsCString>& aFlavorList, ClipboardType aWhichClipboard,
548 mozilla::dom::WindowContext* aRequestingWindowContext,
549 nsIPrincipal* aRequestingPrincipal,
550 nsIClipboardGetDataSnapshotCallback* aCallback) {
551 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
553 if (!aCallback || !aRequestingPrincipal || aFlavorList.IsEmpty()) {
554 return NS_ERROR_INVALID_ARG;
557 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
558 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
559 aWhichClipboard);
560 return NS_ERROR_FAILURE;
563 // We want to disable security check for automated tests that have the pref
564 // set to true, or extension that have clipboard read permission.
565 if (mozilla::StaticPrefs::
566 dom_events_testing_asyncClipboard_DoNotUseDirectly() ||
567 nsContentUtils::PrincipalHasPermission(*aRequestingPrincipal,
568 nsGkAtoms::clipboardRead)) {
569 GetDataSnapshotInternal(aFlavorList, aWhichClipboard,
570 aRequestingWindowContext, aCallback);
571 return NS_OK;
574 // If cache data is valid, we are the last ones to put something on the native
575 // clipboard, then check if the data is from the same-origin page,
576 if (auto* clipboardCache = GetClipboardCacheIfValid(aWhichClipboard)) {
577 nsCOMPtr<nsITransferable> trans = clipboardCache->GetTransferable();
578 MOZ_ASSERT(trans);
580 if (nsCOMPtr<nsIPrincipal> principal = trans->GetDataPrincipal()) {
581 if (aRequestingPrincipal->Subsumes(principal)) {
582 MOZ_CLIPBOARD_LOG("%s: native clipboard data is from same-origin page.",
583 __FUNCTION__);
584 GetDataSnapshotInternal(aFlavorList, aWhichClipboard,
585 aRequestingWindowContext, aCallback);
586 return NS_OK;
591 // TODO: enable showing the "Paste" button in this case; see bug 1773681.
592 if (aRequestingPrincipal->GetIsAddonOrExpandedAddonPrincipal()) {
593 MOZ_CLIPBOARD_LOG("%s: Addon without read permission.", __FUNCTION__);
594 return aCallback->OnError(NS_ERROR_FAILURE);
597 RequestUserConfirmation(aWhichClipboard, aFlavorList,
598 aRequestingWindowContext, aRequestingPrincipal,
599 aCallback);
600 return NS_OK;
603 already_AddRefed<nsIClipboardDataSnapshot>
604 nsBaseClipboard::MaybeCreateGetRequestFromClipboardCache(
605 const nsTArray<nsCString>& aFlavorList, ClipboardType aClipboardType,
606 mozilla::dom::WindowContext* aRequestingWindowContext) {
607 MOZ_DIAGNOSTIC_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
609 if (!mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
610 return nullptr;
613 // If we were the last ones to put something on the native clipboard, then
614 // just use the cached transferable. Otherwise clear it because it isn't
615 // relevant any more.
616 ClipboardCache* clipboardCache = GetClipboardCacheIfValid(aClipboardType);
617 if (!clipboardCache) {
618 return nullptr;
621 nsITransferable* cachedTransferable = clipboardCache->GetTransferable();
622 MOZ_ASSERT(cachedTransferable);
624 nsTArray<nsCString> transferableFlavors;
625 if (NS_FAILED(cachedTransferable->FlavorsTransferableCanExport(
626 transferableFlavors))) {
627 return nullptr;
630 nsTArray<nsCString> results;
631 for (const auto& flavor : aFlavorList) {
632 for (const auto& transferableFlavor : transferableFlavors) {
633 // XXX We need special check for image as we always put the
634 // image as "native" on the clipboard.
635 if (transferableFlavor.Equals(flavor) ||
636 (transferableFlavor.Equals(kNativeImageMime) &&
637 nsContentUtils::IsFlavorImage(flavor))) {
638 MOZ_CLIPBOARD_LOG(" has %s", flavor.get());
639 results.AppendElement(flavor);
644 // XXX Do we need to check system clipboard for the flavors that cannot
645 // be found in cache?
646 return mozilla::MakeAndAddRef<ClipboardDataSnapshot>(
647 aClipboardType, clipboardCache->GetSequenceNumber(), std::move(results),
648 true /* aFromCache */, this, aRequestingWindowContext);
651 void nsBaseClipboard::GetDataSnapshotInternal(
652 const nsTArray<nsCString>& aFlavorList, ClipboardType aClipboardType,
653 mozilla::dom::WindowContext* aRequestingWindowContext,
654 nsIClipboardGetDataSnapshotCallback* aCallback) {
655 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
657 if (nsCOMPtr<nsIClipboardDataSnapshot> clipboardDataSnapshot =
658 MaybeCreateGetRequestFromClipboardCache(aFlavorList, aClipboardType,
659 aRequestingWindowContext)) {
660 aCallback->OnSuccess(clipboardDataSnapshot);
661 return;
664 // At this point we can't satisfy the request from cache data so let's
665 // look for things other people put on the system clipboard.
666 MaybeRetryGetAvailableFlavors(aFlavorList, aClipboardType, aCallback,
667 kGetAvailableFlavorsRetryCount,
668 aRequestingWindowContext);
671 NS_IMETHODIMP nsBaseClipboard::GetDataSnapshotSync(
672 const nsTArray<nsCString>& aFlavorList, ClipboardType aWhichClipboard,
673 mozilla::dom::WindowContext* aRequestingWindowContext,
674 nsIClipboardDataSnapshot** _retval) {
675 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
677 *_retval = nullptr;
679 if (aFlavorList.IsEmpty()) {
680 return NS_ERROR_INVALID_ARG;
683 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
684 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
685 aWhichClipboard);
686 return NS_ERROR_FAILURE;
689 if (nsCOMPtr<nsIClipboardDataSnapshot> clipboardDataSnapshot =
690 MaybeCreateGetRequestFromClipboardCache(aFlavorList, aWhichClipboard,
691 aRequestingWindowContext)) {
692 clipboardDataSnapshot.forget(_retval);
693 return NS_OK;
696 auto sequenceNumberOrError =
697 GetNativeClipboardSequenceNumber(aWhichClipboard);
698 if (sequenceNumberOrError.isErr()) {
699 MOZ_CLIPBOARD_LOG("%s: unable to get sequence number for clipboard %d.",
700 __FUNCTION__, aWhichClipboard);
701 return sequenceNumberOrError.unwrapErr();
704 nsTArray<nsCString> results;
705 for (const auto& flavor : aFlavorList) {
706 auto resultOrError = HasNativeClipboardDataMatchingFlavors(
707 AutoTArray<nsCString, 1>{flavor}, aWhichClipboard);
708 if (resultOrError.isOk() && resultOrError.unwrap()) {
709 results.AppendElement(flavor);
713 *_retval =
714 mozilla::MakeAndAddRef<ClipboardDataSnapshot>(
715 aWhichClipboard, sequenceNumberOrError.unwrap(), std::move(results),
716 false /* aFromCache */, this, aRequestingWindowContext)
717 .take();
718 return NS_OK;
721 NS_IMETHODIMP nsBaseClipboard::EmptyClipboard(ClipboardType aWhichClipboard) {
722 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
724 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
725 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
726 aWhichClipboard);
727 return NS_ERROR_FAILURE;
730 EmptyNativeClipboardData(aWhichClipboard);
732 const auto& clipboardCache = mCaches[aWhichClipboard];
733 MOZ_ASSERT(clipboardCache);
735 if (mIgnoreEmptyNotification) {
736 MOZ_DIAGNOSTIC_ASSERT(!clipboardCache->GetTransferable() &&
737 !clipboardCache->GetClipboardOwner() &&
738 clipboardCache->GetSequenceNumber() == -1,
739 "How did we have data in clipboard cache here?");
740 return NS_OK;
743 clipboardCache->Clear();
745 return NS_OK;
748 mozilla::Result<nsTArray<nsCString>, nsresult>
749 nsBaseClipboard::GetFlavorsFromClipboardCache(ClipboardType aClipboardType) {
750 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
751 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
753 const auto* clipboardCache = GetClipboardCacheIfValid(aClipboardType);
754 if (!clipboardCache) {
755 return mozilla::Err(NS_ERROR_FAILURE);
758 nsITransferable* cachedTransferable = clipboardCache->GetTransferable();
759 MOZ_ASSERT(cachedTransferable);
761 nsTArray<nsCString> flavors;
762 nsresult rv = cachedTransferable->FlavorsTransferableCanExport(flavors);
763 if (NS_FAILED(rv)) {
764 return mozilla::Err(rv);
767 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
768 MOZ_CLIPBOARD_LOG(" Cached transferable types (nums %zu)\n",
769 flavors.Length());
770 for (const auto& flavor : flavors) {
771 MOZ_CLIPBOARD_LOG(" MIME %s", flavor.get());
775 return std::move(flavors);
778 NS_IMETHODIMP
779 nsBaseClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
780 ClipboardType aWhichClipboard,
781 bool* aOutResult) {
782 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
784 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
785 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
786 aWhichClipboard);
787 return NS_ERROR_FAILURE;
790 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
791 MOZ_CLIPBOARD_LOG(" Asking for content clipboard=%i:\n",
792 aWhichClipboard);
793 for (const auto& flavor : aFlavorList) {
794 MOZ_CLIPBOARD_LOG(" MIME %s", flavor.get());
798 *aOutResult = false;
800 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
801 // First, check if we have valid data in our cached transferable.
802 auto flavorsOrError = GetFlavorsFromClipboardCache(aWhichClipboard);
803 if (flavorsOrError.isOk()) {
804 for (const auto& transferableFlavor : flavorsOrError.unwrap()) {
805 for (const auto& flavor : aFlavorList) {
806 if (transferableFlavor.Equals(flavor)) {
807 MOZ_CLIPBOARD_LOG(" has %s", flavor.get());
808 *aOutResult = true;
809 return NS_OK;
816 auto resultOrError =
817 HasNativeClipboardDataMatchingFlavors(aFlavorList, aWhichClipboard);
818 if (resultOrError.isErr()) {
819 MOZ_CLIPBOARD_LOG(
820 "%s: checking native clipboard data matching flavors falied.",
821 __FUNCTION__);
822 return resultOrError.unwrapErr();
825 *aOutResult = resultOrError.unwrap();
826 return NS_OK;
829 NS_IMETHODIMP
830 nsBaseClipboard::IsClipboardTypeSupported(ClipboardType aWhichClipboard,
831 bool* aRetval) {
832 NS_ENSURE_ARG_POINTER(aRetval);
833 switch (aWhichClipboard) {
834 case kGlobalClipboard:
835 // We always support the global clipboard.
836 *aRetval = true;
837 return NS_OK;
838 case kSelectionClipboard:
839 *aRetval = mClipboardCaps.supportsSelectionClipboard();
840 return NS_OK;
841 case kFindClipboard:
842 *aRetval = mClipboardCaps.supportsFindClipboard();
843 return NS_OK;
844 case kSelectionCache:
845 *aRetval = mClipboardCaps.supportsSelectionCache();
846 return NS_OK;
847 default:
848 *aRetval = false;
849 return NS_OK;
853 void nsBaseClipboard::AsyncHasNativeClipboardDataMatchingFlavors(
854 const nsTArray<nsCString>& aFlavorList, ClipboardType aWhichClipboard,
855 HasMatchingFlavorsCallback&& aCallback) {
856 MOZ_DIAGNOSTIC_ASSERT(
857 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
859 MOZ_CLIPBOARD_LOG(
860 "nsBaseClipboard::AsyncHasNativeClipboardDataMatchingFlavors: "
861 "clipboard=%d",
862 aWhichClipboard);
864 nsTArray<nsCString> results;
865 for (const auto& flavor : aFlavorList) {
866 auto resultOrError = HasNativeClipboardDataMatchingFlavors(
867 AutoTArray<nsCString, 1>{flavor}, aWhichClipboard);
868 if (resultOrError.isOk() && resultOrError.unwrap()) {
869 results.AppendElement(flavor);
872 aCallback(std::move(results));
875 void nsBaseClipboard::AsyncGetNativeClipboardData(
876 nsITransferable* aTransferable, ClipboardType aWhichClipboard,
877 GetDataCallback&& aCallback) {
878 aCallback(GetNativeClipboardData(aTransferable, aWhichClipboard));
881 void nsBaseClipboard::ClearClipboardCache(ClipboardType aClipboardType) {
882 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
883 const mozilla::UniquePtr<ClipboardCache>& cache = mCaches[aClipboardType];
884 MOZ_ASSERT(cache);
885 cache->Clear();
888 void nsBaseClipboard::RequestUserConfirmation(
889 ClipboardType aClipboardType, const nsTArray<nsCString>& aFlavorList,
890 mozilla::dom::WindowContext* aWindowContext,
891 nsIPrincipal* aRequestingPrincipal,
892 nsIClipboardGetDataSnapshotCallback* aCallback) {
893 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
894 MOZ_ASSERT(aCallback);
896 if (!aWindowContext) {
897 aCallback->OnError(NS_ERROR_FAILURE);
898 return;
901 CanonicalBrowsingContext* cbc =
902 CanonicalBrowsingContext::Cast(aWindowContext->GetBrowsingContext());
903 MOZ_ASSERT(
904 cbc->IsContent(),
905 "Should not require user confirmation when access from chrome window");
907 RefPtr<CanonicalBrowsingContext> chromeTop = cbc->TopCrossChromeBoundary();
908 Document* chromeDoc = chromeTop ? chromeTop->GetDocument() : nullptr;
909 if (!chromeDoc || !chromeDoc->HasFocus(mozilla::IgnoreErrors())) {
910 MOZ_CLIPBOARD_LOG("%s: reject due to not in the focused window",
911 __FUNCTION__);
912 aCallback->OnError(NS_ERROR_FAILURE);
913 return;
916 mozilla::dom::Element* activeElementInChromeDoc =
917 chromeDoc->GetActiveElement();
918 if (activeElementInChromeDoc != cbc->Top()->GetEmbedderElement()) {
919 // Reject if the request is not from web content that is in the focused tab.
920 MOZ_CLIPBOARD_LOG("%s: reject due to not in the focused tab", __FUNCTION__);
921 aCallback->OnError(NS_ERROR_FAILURE);
922 return;
925 // If there is a pending user confirmation request, check if we could reuse
926 // it. If not, reject the request.
927 if (sUserConfirmationRequest) {
928 if (sUserConfirmationRequest->IsEqual(
929 aClipboardType, chromeDoc, aRequestingPrincipal, aWindowContext)) {
930 sUserConfirmationRequest->AddClipboardGetRequest(aFlavorList, aCallback);
931 return;
934 aCallback->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR);
935 return;
938 nsresult rv = NS_ERROR_FAILURE;
939 nsCOMPtr<nsIPromptService> promptService =
940 do_GetService("@mozilla.org/prompter;1", &rv);
941 if (NS_FAILED(rv)) {
942 aCallback->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR);
943 return;
946 RefPtr<mozilla::dom::Promise> promise;
947 if (NS_FAILED(promptService->ConfirmUserPaste(aWindowContext->Canonical(),
948 getter_AddRefs(promise)))) {
949 aCallback->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR);
950 return;
953 sUserConfirmationRequest = new UserConfirmationRequest(
954 aClipboardType, chromeDoc, aRequestingPrincipal, this, aWindowContext);
955 sUserConfirmationRequest->AddClipboardGetRequest(aFlavorList, aCallback);
956 promise->AppendNativeHandler(sUserConfirmationRequest);
959 NS_IMPL_ISUPPORTS(nsBaseClipboard::ClipboardDataSnapshot,
960 nsIClipboardDataSnapshot)
962 nsBaseClipboard::ClipboardDataSnapshot::ClipboardDataSnapshot(
963 nsIClipboard::ClipboardType aClipboardType, int32_t aSequenceNumber,
964 nsTArray<nsCString>&& aFlavors, bool aFromCache,
965 nsBaseClipboard* aClipboard,
966 mozilla::dom::WindowContext* aRequestingWindowContext)
967 : mClipboardType(aClipboardType),
968 mSequenceNumber(aSequenceNumber),
969 mFlavors(std::move(aFlavors)),
970 mFromCache(aFromCache),
971 mClipboard(aClipboard),
972 mRequestingWindowContext(aRequestingWindowContext) {
973 MOZ_ASSERT(mClipboard);
974 MOZ_ASSERT(
975 mClipboard->nsIClipboard::IsClipboardTypeSupported(mClipboardType));
978 NS_IMETHODIMP nsBaseClipboard::ClipboardDataSnapshot::GetValid(
979 bool* aOutResult) {
980 *aOutResult = IsValid();
981 return NS_OK;
984 NS_IMETHODIMP nsBaseClipboard::ClipboardDataSnapshot::GetFlavorList(
985 nsTArray<nsCString>& aFlavors) {
986 aFlavors.AppendElements(mFlavors);
987 return NS_OK;
990 NS_IMETHODIMP nsBaseClipboard::ClipboardDataSnapshot::GetData(
991 nsITransferable* aTransferable,
992 nsIAsyncClipboardRequestCallback* aCallback) {
993 MOZ_CLIPBOARD_LOG("ClipboardDataSnapshot::GetData: %p", this);
995 if (!aTransferable || !aCallback) {
996 return NS_ERROR_INVALID_ARG;
999 nsTArray<nsCString> flavors;
1000 nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
1001 if (NS_FAILED(rv)) {
1002 return rv;
1005 // If the requested flavor is not in the list, throw an error.
1006 for (const auto& flavor : flavors) {
1007 if (!mFlavors.Contains(flavor)) {
1008 return NS_ERROR_FAILURE;
1012 if (!IsValid()) {
1013 aCallback->OnComplete(NS_ERROR_NOT_AVAILABLE);
1014 return NS_OK;
1017 MOZ_ASSERT(mClipboard);
1019 auto contentAnalysisCallback =
1020 mozilla::MakeRefPtr<mozilla::contentanalysis::ContentAnalysis::
1021 SafeContentAnalysisResultCallback>(
1022 [transferable = nsCOMPtr{aTransferable},
1023 callback = nsCOMPtr{aCallback}](
1024 RefPtr<nsIContentAnalysisResult>&& aResult) {
1025 if (aResult->GetShouldAllowContent()) {
1026 callback->OnComplete(NS_OK);
1027 } else {
1028 transferable->ClearAllData();
1029 callback->OnComplete(NS_ERROR_CONTENT_BLOCKED);
1033 if (mFromCache) {
1034 const auto* clipboardCache =
1035 mClipboard->GetClipboardCacheIfValid(mClipboardType);
1036 // `IsValid()` above ensures we should get a valid cache and matched
1037 // sequence number here.
1038 MOZ_DIAGNOSTIC_ASSERT(clipboardCache);
1039 MOZ_DIAGNOSTIC_ASSERT(clipboardCache->GetSequenceNumber() ==
1040 mSequenceNumber);
1041 if (NS_SUCCEEDED(clipboardCache->GetData(aTransferable))) {
1042 mozilla::contentanalysis::ContentAnalysis::CheckClipboardContentAnalysis(
1043 mClipboard,
1044 mRequestingWindowContext ? mRequestingWindowContext->Canonical()
1045 : nullptr,
1046 aTransferable, mClipboardType, contentAnalysisCallback);
1047 return NS_OK;
1050 // At this point we can't satisfy the request from cache data so let's look
1051 // for things other people put on the system clipboard.
1054 // Since this is an async operation, we need to check if the data is still
1055 // valid after we get the result.
1056 mClipboard->AsyncGetNativeClipboardData(
1057 aTransferable, mClipboardType,
1058 [callback = nsCOMPtr{aCallback}, self = RefPtr{this},
1059 transferable = nsCOMPtr{aTransferable},
1060 contentAnalysisCallback =
1061 std::move(contentAnalysisCallback)](nsresult aResult) mutable {
1062 if (NS_FAILED(aResult)) {
1063 callback->OnComplete(aResult);
1064 return;
1066 // `IsValid()` checks the clipboard sequence number to ensure the data
1067 // we are requesting is still valid.
1068 if (!self->IsValid()) {
1069 callback->OnComplete(NS_ERROR_NOT_AVAILABLE);
1070 return;
1072 mozilla::contentanalysis::ContentAnalysis::
1073 CheckClipboardContentAnalysis(
1074 self->mClipboard,
1075 self->mRequestingWindowContext
1076 ? self->mRequestingWindowContext->Canonical()
1077 : nullptr,
1078 transferable, self->mClipboardType, contentAnalysisCallback);
1080 return NS_OK;
1083 NS_IMETHODIMP nsBaseClipboard::ClipboardDataSnapshot::GetDataSync(
1084 nsITransferable* aTransferable) {
1085 MOZ_CLIPBOARD_LOG("ClipboardDataSnapshot::GetDataSync: %p", this);
1087 if (!aTransferable) {
1088 return NS_ERROR_INVALID_ARG;
1091 nsTArray<nsCString> flavors;
1092 nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
1093 if (NS_FAILED(rv)) {
1094 return rv;
1097 // If the requested flavor is not in the list, throw an error.
1098 for (const auto& flavor : flavors) {
1099 if (!mFlavors.Contains(flavor)) {
1100 return NS_ERROR_FAILURE;
1104 if (!IsValid()) {
1105 return NS_ERROR_NOT_AVAILABLE;
1108 MOZ_ASSERT(mClipboard);
1110 if (mFromCache) {
1111 const auto* clipboardCache =
1112 mClipboard->GetClipboardCacheIfValid(mClipboardType);
1113 // `IsValid()` above ensures we should get a valid cache and matched
1114 // sequence number here.
1115 MOZ_DIAGNOSTIC_ASSERT(clipboardCache);
1116 MOZ_DIAGNOSTIC_ASSERT(clipboardCache->GetSequenceNumber() ==
1117 mSequenceNumber);
1118 if (NS_SUCCEEDED(clipboardCache->GetData(aTransferable))) {
1119 bool shouldAllowContent = mozilla::contentanalysis::ContentAnalysis::
1120 CheckClipboardContentAnalysisSync(
1121 mClipboard,
1122 mRequestingWindowContext ? mRequestingWindowContext->Canonical()
1123 : nullptr,
1124 aTransferable, mClipboardType);
1125 if (shouldAllowContent) {
1126 return NS_OK;
1128 aTransferable->ClearAllData();
1129 return NS_ERROR_CONTENT_BLOCKED;
1132 // At this point we can't satisfy the request from cache data so let's look
1133 // for things other people put on the system clipboard.
1136 rv = mClipboard->GetNativeClipboardData(aTransferable, mClipboardType);
1137 if (NS_FAILED(rv)) {
1138 return rv;
1141 bool shouldAllowContent = mozilla::contentanalysis::ContentAnalysis::
1142 CheckClipboardContentAnalysisSync(
1143 mClipboard,
1144 mRequestingWindowContext ? mRequestingWindowContext->Canonical()
1145 : nullptr,
1146 aTransferable, mClipboardType);
1147 if (shouldAllowContent) {
1148 return NS_OK;
1150 aTransferable->ClearAllData();
1151 return NS_ERROR_CONTENT_BLOCKED;
1154 bool nsBaseClipboard::ClipboardDataSnapshot::IsValid() {
1155 if (!mClipboard) {
1156 return false;
1159 // If the data should from cache, check if cache is still valid or the
1160 // sequence numbers are matched.
1161 if (mFromCache) {
1162 const auto* clipboardCache =
1163 mClipboard->GetClipboardCacheIfValid(mClipboardType);
1164 if (!clipboardCache) {
1165 mClipboard = nullptr;
1166 return false;
1169 return mSequenceNumber == clipboardCache->GetSequenceNumber();
1172 auto resultOrError =
1173 mClipboard->GetNativeClipboardSequenceNumber(mClipboardType);
1174 if (resultOrError.isErr()) {
1175 mClipboard = nullptr;
1176 return false;
1179 if (mSequenceNumber != resultOrError.unwrap()) {
1180 mClipboard = nullptr;
1181 return false;
1184 return true;
1187 NS_IMPL_ISUPPORTS(nsBaseClipboard::ClipboardPopulatedDataSnapshot,
1188 nsIClipboardDataSnapshot)
1190 nsBaseClipboard::ClipboardPopulatedDataSnapshot::ClipboardPopulatedDataSnapshot(
1191 nsITransferable* aTransferable)
1192 : mTransferable(aTransferable) {
1193 MOZ_ASSERT(mTransferable);
1194 aTransferable->FlavorsTransferableCanExport(mFlavors);
1197 NS_IMETHODIMP nsBaseClipboard::ClipboardPopulatedDataSnapshot::GetValid(
1198 bool* aOutResult) {
1199 // Since this is a snapshot of what the clipboard data was, this is always
1200 // valid
1201 *aOutResult = true;
1202 return NS_OK;
1205 NS_IMETHODIMP nsBaseClipboard::ClipboardPopulatedDataSnapshot::GetFlavorList(
1206 nsTArray<nsCString>& aFlavors) {
1207 aFlavors.AppendElements(mFlavors);
1208 return NS_OK;
1211 NS_IMETHODIMP nsBaseClipboard::ClipboardPopulatedDataSnapshot::GetData(
1212 nsITransferable* aTransferable,
1213 nsIAsyncClipboardRequestCallback* aCallback) {
1214 if (!aTransferable || !aCallback) {
1215 return NS_ERROR_INVALID_ARG;
1218 NS_DispatchToMainThread(NS_NewRunnableFunction(
1219 "ClipboardPopulatedDataSnapshot::GetData",
1220 [self = RefPtr{this}, transferable = RefPtr{aTransferable},
1221 callback = RefPtr{aCallback}]() {
1222 nsresult rv = self->GetDataSync(transferable);
1223 callback->OnComplete(rv);
1224 }));
1226 return NS_OK;
1229 NS_IMETHODIMP nsBaseClipboard::ClipboardPopulatedDataSnapshot::GetDataSync(
1230 nsITransferable* aTransferable) {
1231 MOZ_CLIPBOARD_LOG("ClipboardPopulatedDataSnapshot::GetDataSync: %p", this);
1233 if (!aTransferable) {
1234 return NS_ERROR_INVALID_ARG;
1237 nsTArray<nsCString> flavors;
1238 nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
1239 if (NS_FAILED(rv)) {
1240 return rv;
1243 // If the requested flavor is not in the list, throw an error.
1244 for (const auto& flavor : flavors) {
1245 if (!mFlavors.Contains(flavor)) {
1246 return NS_ERROR_FAILURE;
1250 // This method only fills in the data for the first flavor passed in. This
1251 // seems weird but matches the IDL documentation and behavior.
1252 if (!flavors.IsEmpty()) {
1253 nsCOMPtr<nsISupports> data;
1254 rv = mTransferable->GetTransferData(flavors[0].get(), getter_AddRefs(data));
1255 if (NS_FAILED(rv)) {
1256 aTransferable->ClearAllData();
1257 return rv;
1259 rv = aTransferable->SetTransferData(flavors[0].get(), data);
1260 if (NS_FAILED(rv)) {
1261 aTransferable->ClearAllData();
1262 return rv;
1265 return NS_OK;
1268 mozilla::Maybe<uint64_t> nsBaseClipboard::GetClipboardCacheInnerWindowId(
1269 ClipboardType aClipboardType) {
1270 auto* clipboardCache = GetClipboardCacheIfValid(aClipboardType);
1271 return clipboardCache ? clipboardCache->GetInnerWindowId()
1272 : mozilla::Nothing();
1275 nsBaseClipboard::ClipboardCache* nsBaseClipboard::GetClipboardCacheIfValid(
1276 ClipboardType aClipboardType) {
1277 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
1279 const mozilla::UniquePtr<ClipboardCache>& cache = mCaches[aClipboardType];
1280 MOZ_ASSERT(cache);
1282 if (!cache->GetTransferable()) {
1283 MOZ_ASSERT(cache->GetSequenceNumber() == -1);
1284 return nullptr;
1287 auto changeCountOrError = GetNativeClipboardSequenceNumber(aClipboardType);
1288 if (changeCountOrError.isErr()) {
1289 return nullptr;
1292 if (changeCountOrError.unwrap() != cache->GetSequenceNumber()) {
1293 // Clipboard cache is invalid, clear it.
1294 cache->Clear();
1295 return nullptr;
1298 return cache.get();
1301 void nsBaseClipboard::ClipboardCache::Clear() {
1302 if (mClipboardOwner) {
1303 mClipboardOwner->LosingOwnership(mTransferable);
1304 mClipboardOwner = nullptr;
1306 mTransferable = nullptr;
1307 mSequenceNumber = -1;
1310 nsresult nsBaseClipboard::ClipboardCache::GetData(
1311 nsITransferable* aTransferable) const {
1312 MOZ_ASSERT(aTransferable);
1313 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
1315 // get flavor list that includes all acceptable flavors (including ones
1316 // obtained through conversion)
1317 nsTArray<nsCString> flavors;
1318 if (NS_FAILED(aTransferable->FlavorsTransferableCanImport(flavors))) {
1319 return NS_ERROR_FAILURE;
1322 MOZ_ASSERT(mTransferable);
1323 for (const auto& flavor : flavors) {
1324 nsCOMPtr<nsISupports> dataSupports;
1325 // XXX Maybe we need special check for image as we always put the image as
1326 // "native" on the clipboard.
1327 if (NS_SUCCEEDED(mTransferable->GetTransferData(
1328 flavor.get(), getter_AddRefs(dataSupports)))) {
1329 MOZ_CLIPBOARD_LOG("%s: getting %s from cache.", __FUNCTION__,
1330 flavor.get());
1331 aTransferable->SetTransferData(flavor.get(), dataSupports);
1332 // XXX we only read the first available type from native clipboard, so
1333 // make cache behave the same.
1334 return NS_OK;
1338 return NS_ERROR_FAILURE;