Bug 1890689 accumulate input in LargerReceiverBlockSizeThanDesiredBuffering GTest...
[gecko.git] / widget / nsBaseClipboard.cpp
blobf3525aaead698d6dd30de29ecee114e507696cd7
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsBaseClipboard.h"
8 #include "ContentAnalysis.h"
9 #include "mozilla/Components.h"
10 #include "mozilla/contentanalysis/ContentAnalysisIPCTypes.h"
11 #include "mozilla/dom/BindingUtils.h"
12 #include "mozilla/dom/CanonicalBrowsingContext.h"
13 #include "mozilla/dom/Document.h"
14 #include "mozilla/dom/Promise.h"
15 #include "mozilla/dom/PromiseNativeHandler.h"
16 #include "mozilla/dom/WindowGlobalParent.h"
17 #include "mozilla/dom/WindowContext.h"
18 #include "mozilla/ErrorResult.h"
19 #include "mozilla/MoveOnlyFunction.h"
20 #include "mozilla/RefPtr.h"
21 #include "mozilla/Services.h"
22 #include "mozilla/SpinEventLoopUntil.h"
23 #include "mozilla/StaticPrefs_dom.h"
24 #include "mozilla/StaticPrefs_widget.h"
25 #include "nsContentUtils.h"
26 #include "nsFocusManager.h"
27 #include "nsIClipboardOwner.h"
28 #include "nsIPromptService.h"
29 #include "nsISupportsPrimitives.h"
30 #include "nsError.h"
31 #include "nsXPCOM.h"
33 using mozilla::GenericPromise;
34 using mozilla::LogLevel;
35 using mozilla::UniquePtr;
36 using mozilla::dom::BrowsingContext;
37 using mozilla::dom::CanonicalBrowsingContext;
38 using mozilla::dom::ClipboardCapabilities;
39 using mozilla::dom::Document;
41 static const int32_t kGetAvailableFlavorsRetryCount = 5;
43 namespace {
45 struct ClipboardGetRequest {
46 ClipboardGetRequest(const nsTArray<nsCString>& aFlavorList,
47 nsIAsyncClipboardGetCallback* aCallback)
48 : mFlavorList(aFlavorList.Clone()), mCallback(aCallback) {}
50 const nsTArray<nsCString> mFlavorList;
51 const nsCOMPtr<nsIAsyncClipboardGetCallback> mCallback;
54 class UserConfirmationRequest final
55 : public mozilla::dom::PromiseNativeHandler {
56 public:
57 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
58 NS_DECL_CYCLE_COLLECTION_CLASS(UserConfirmationRequest)
60 UserConfirmationRequest(int32_t aClipboardType,
61 Document* aRequestingChromeDocument,
62 nsIPrincipal* aRequestingPrincipal,
63 nsBaseClipboard* aClipboard,
64 mozilla::dom::WindowContext* aRequestingWindowContext)
65 : mClipboardType(aClipboardType),
66 mRequestingChromeDocument(aRequestingChromeDocument),
67 mRequestingPrincipal(aRequestingPrincipal),
68 mClipboard(aClipboard),
69 mRequestingWindowContext(aRequestingWindowContext) {
70 MOZ_ASSERT(
71 mClipboard->nsIClipboard::IsClipboardTypeSupported(aClipboardType));
74 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
75 mozilla::ErrorResult& aRv) override;
77 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
78 mozilla::ErrorResult& aRv) override;
80 bool IsEqual(int32_t aClipboardType, Document* aRequestingChromeDocument,
81 nsIPrincipal* aRequestingPrincipal,
82 mozilla::dom::WindowContext* aRequestingWindowContext) const {
83 if (!(ClipboardType() == aClipboardType &&
84 RequestingChromeDocument() == aRequestingChromeDocument &&
85 RequestingPrincipal()->Equals(aRequestingPrincipal) &&
86 (mRequestingWindowContext && aRequestingWindowContext))) {
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 int32_t ClipboardType() const { return mClipboardType; }
106 Document* RequestingChromeDocument() const {
107 return mRequestingChromeDocument;
110 nsIPrincipal* RequestingPrincipal() const { return mRequestingPrincipal; }
112 void AddClipboardGetRequest(const nsTArray<nsCString>& aFlavorList,
113 nsIAsyncClipboardGetCallback* aCallback) {
114 MOZ_ASSERT(!aFlavorList.IsEmpty());
115 MOZ_ASSERT(aCallback);
116 mPendingClipboardGetRequests.AppendElement(
117 mozilla::MakeUnique<ClipboardGetRequest>(aFlavorList, aCallback));
120 void RejectPendingClipboardGetRequests(nsresult aError) {
121 MOZ_ASSERT(NS_FAILED(aError));
122 auto requests = std::move(mPendingClipboardGetRequests);
123 for (const auto& request : requests) {
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->AsyncGetDataInternal(request->mFlavorList, mClipboardType,
137 mRequestingWindowContext,
138 request->mCallback);
142 nsTArray<UniquePtr<ClipboardGetRequest>>& GetPendingClipboardGetRequests() {
143 return mPendingClipboardGetRequests;
146 private:
147 ~UserConfirmationRequest() = default;
149 const int32_t mClipboardType;
150 RefPtr<Document> mRequestingChromeDocument;
151 const nsCOMPtr<nsIPrincipal> mRequestingPrincipal;
152 const RefPtr<nsBaseClipboard> mClipboard;
153 const RefPtr<mozilla::dom::WindowContext> mRequestingWindowContext;
154 // Track the pending read requests that wait for user confirmation.
155 nsTArray<UniquePtr<ClipboardGetRequest>> mPendingClipboardGetRequests;
158 NS_IMPL_CYCLE_COLLECTION(UserConfirmationRequest, mRequestingChromeDocument)
160 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UserConfirmationRequest)
161 NS_INTERFACE_MAP_ENTRY(nsISupports)
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 int32_t aClipboardType, nsBaseClipboard* aClipboard,
214 mozilla::dom::WindowContext* aSettingWindowContext,
215 nsIAsyncClipboardRequestCallback* aCallback)
216 : mClipboardType(aClipboardType),
217 mClipboard(aClipboard),
218 mWindowContext(aSettingWindowContext),
219 mCallback(aCallback) {
220 MOZ_ASSERT(mClipboard);
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 int32_t 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 int32_t aWhichClipboard, mozilla::dom::WindowContext* aSettingWindowContext,
299 nsIAsyncClipboardRequestCallback* aCallback,
300 nsIAsyncSetClipboardData** _retval) {
301 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
303 *_retval = nullptr;
304 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
305 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
306 aWhichClipboard);
307 return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
310 // Reject existing pending AsyncSetData request if any.
311 RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard);
313 // Create a new AsyncSetClipboardData.
314 RefPtr<AsyncSetClipboardData> request =
315 mozilla::MakeRefPtr<AsyncSetClipboardData>(
316 aWhichClipboard, this, aSettingWindowContext, aCallback);
317 mPendingWriteRequests[aWhichClipboard] = request;
318 request.forget(_retval);
319 return NS_OK;
322 NS_IMPL_ISUPPORTS(nsBaseClipboard::SafeContentAnalysisResultCallback,
323 nsIContentAnalysisCallback);
325 nsBaseClipboard::ClipboardContentAnalysisResult
326 nsBaseClipboard::CheckClipboardContentAnalysisAsText(
327 uint64_t aInnerWindowId, SafeContentAnalysisResultCallback* aResolver,
328 nsIURI* aDocumentURI, nsIContentAnalysis* aContentAnalysis,
329 nsITransferable* aTextTrans) {
330 using namespace mozilla::contentanalysis;
332 nsCOMPtr<nsISupports> transferData;
333 if (NS_FAILED(aTextTrans->GetTransferData(kTextMime,
334 getter_AddRefs(transferData)))) {
335 return false;
337 nsCOMPtr<nsISupportsString> textData = do_QueryInterface(transferData);
338 if (MOZ_UNLIKELY(!textData)) {
339 return false;
341 nsString text;
342 if (NS_FAILED(textData->GetData(text))) {
343 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
345 if (text.IsEmpty()) {
346 // Content Analysis doesn't expect to analyze an empty string.
347 // Just approve it.
348 return true;
350 RefPtr<mozilla::dom::WindowGlobalParent> window =
351 mozilla::dom::WindowGlobalParent::GetByInnerWindowId(aInnerWindowId);
352 if (!window) {
353 // The window has gone away in the meantime
354 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
356 nsCOMPtr<nsIContentAnalysisRequest> contentAnalysisRequest =
357 new ContentAnalysisRequest(
358 nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry,
359 std::move(text), false, EmptyCString(), aDocumentURI,
360 nsIContentAnalysisRequest::OperationType::eClipboard, window);
361 nsresult rv = aContentAnalysis->AnalyzeContentRequestCallback(
362 contentAnalysisRequest, /* aAutoAcknowledge */ true, aResolver);
363 if (NS_FAILED(rv)) {
364 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
366 return true;
369 nsBaseClipboard::ClipboardContentAnalysisResult
370 nsBaseClipboard::CheckClipboardContentAnalysisAsFile(
371 uint64_t aInnerWindowId, SafeContentAnalysisResultCallback* aResolver,
372 nsIURI* aDocumentURI, nsIContentAnalysis* aContentAnalysis,
373 nsITransferable* aFileTrans) {
374 using namespace mozilla::contentanalysis;
376 nsCOMPtr<nsISupports> transferData;
377 nsresult rv =
378 aFileTrans->GetTransferData(kFileMime, getter_AddRefs(transferData));
379 nsString filePath;
380 if (NS_SUCCEEDED(rv)) {
381 if (nsCOMPtr<nsIFile> file = do_QueryInterface(transferData)) {
382 rv = file->GetPath(filePath);
383 } else {
384 MOZ_ASSERT_UNREACHABLE("clipboard data had kFileMime but no nsIFile!");
385 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
388 if (NS_FAILED(rv) || filePath.IsEmpty()) {
389 return false;
391 RefPtr<mozilla::dom::WindowGlobalParent> window =
392 mozilla::dom::WindowGlobalParent::GetByInnerWindowId(aInnerWindowId);
393 if (!window) {
394 // The window has gone away in the meantime
395 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
397 // Let the content analysis code calculate the digest
398 nsCOMPtr<nsIContentAnalysisRequest> contentAnalysisRequest =
399 new ContentAnalysisRequest(
400 nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry,
401 std::move(filePath), true, EmptyCString(), aDocumentURI,
402 nsIContentAnalysisRequest::OperationType::eCustomDisplayString,
403 window);
404 rv = aContentAnalysis->AnalyzeContentRequestCallback(
405 contentAnalysisRequest,
406 /* aAutoAcknowledge */ true, aResolver);
407 if (NS_FAILED(rv)) {
408 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
410 return true;
413 void nsBaseClipboard::CheckClipboardContentAnalysis(
414 mozilla::dom::WindowGlobalParent* aWindow, nsITransferable* aTransferable,
415 int32_t aClipboardType, SafeContentAnalysisResultCallback* aResolver) {
416 using namespace mozilla::contentanalysis;
418 // Content analysis is only needed if an outside webpage has access to
419 // the data. So, skip content analysis if there is:
420 // - no associated window (for example, scripted clipboard read by system
421 // code)
422 // - the window is a chrome docshell
423 // - the window is being rendered in the parent process (for example,
424 // about:support and the like)
425 if (!aWindow || aWindow->GetBrowsingContext()->IsChrome() ||
426 aWindow->IsInProcess()) {
427 aResolver->Callback(ContentAnalysisResult::FromNoResult(
428 NoContentAnalysisResult::
429 ALLOW_DUE_TO_CONTEXT_EXEMPT_FROM_CONTENT_ANALYSIS));
430 return;
432 nsCOMPtr<nsIContentAnalysis> contentAnalysis =
433 mozilla::components::nsIContentAnalysis::Service();
434 if (!contentAnalysis) {
435 aResolver->Callback(ContentAnalysisResult::FromNoResult(
436 NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR));
437 return;
440 bool contentAnalysisIsActive;
441 nsresult rv = contentAnalysis->GetIsActive(&contentAnalysisIsActive);
442 if (MOZ_LIKELY(NS_FAILED(rv) || !contentAnalysisIsActive)) {
443 aResolver->Callback(ContentAnalysisResult::FromNoResult(
444 NoContentAnalysisResult::ALLOW_DUE_TO_CONTENT_ANALYSIS_NOT_ACTIVE));
445 return;
448 uint64_t innerWindowId = aWindow->InnerWindowId();
449 if (mozilla::StaticPrefs::
450 browser_contentanalysis_bypass_for_same_tab_operations()) {
451 const auto* clipboardCache = GetClipboardCacheIfValid(aClipboardType);
452 if (clipboardCache) {
453 if (clipboardCache->GetInnerWindowId().isSome() &&
454 *(clipboardCache->GetInnerWindowId()) == innerWindowId) {
455 // If the same page copied this data to the clipboard (and the above
456 // preference is set) we can skip content analysis and immediately allow
457 // this.
458 aResolver->Callback(ContentAnalysisResult::FromNoResult(
459 NoContentAnalysisResult::ALLOW_DUE_TO_SAME_TAB_SOURCE));
460 return;
465 nsCOMPtr<nsIURI> currentURI = aWindow->Canonical()->GetDocumentURI();
466 nsTArray<nsCString> flavors;
467 rv = aTransferable->FlavorsTransferableCanExport(flavors);
468 if (NS_WARN_IF(NS_FAILED(rv))) {
469 aResolver->Callback(ContentAnalysisResult::FromNoResult(
470 NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR));
471 return;
473 bool keepChecking = true;
474 if (flavors.Contains(kFileMime)) {
475 auto fileResult = CheckClipboardContentAnalysisAsFile(
476 innerWindowId, aResolver, currentURI, contentAnalysis, aTransferable);
478 if (fileResult.isErr()) {
479 aResolver->Callback(
480 ContentAnalysisResult::FromNoResult(fileResult.unwrapErr()));
481 return;
483 keepChecking = !fileResult.unwrap();
485 if (keepChecking) {
486 // Failed to get the clipboard data as a file, so try as text
487 auto textResult = CheckClipboardContentAnalysisAsText(
488 innerWindowId, aResolver, currentURI, contentAnalysis, aTransferable);
489 if (textResult.isErr()) {
490 aResolver->Callback(
491 ContentAnalysisResult::FromNoResult(textResult.unwrapErr()));
492 return;
494 if (!textResult.unwrap()) {
495 // Couldn't get file or text data from this
496 aResolver->Callback(ContentAnalysisResult::FromNoResult(
497 NoContentAnalysisResult::ALLOW_DUE_TO_COULD_NOT_GET_DATA));
498 return;
503 bool nsBaseClipboard::CheckClipboardContentAnalysisSync(
504 mozilla::dom::WindowGlobalParent* aWindow,
505 const nsCOMPtr<nsITransferable>& trans, int32_t aClipboardType) {
506 bool requestDone = false;
507 RefPtr<nsIContentAnalysisResult> result;
508 auto callback = mozilla::MakeRefPtr<SafeContentAnalysisResultCallback>(
509 [&requestDone, &result](RefPtr<nsIContentAnalysisResult>&& aResult) {
510 result = std::move(aResult);
511 requestDone = true;
513 CheckClipboardContentAnalysis(aWindow, trans, aClipboardType, callback);
514 mozilla::SpinEventLoopUntil("CheckClipboardContentAnalysisSync"_ns,
515 [&requestDone]() -> bool { return requestDone; });
516 return result->GetShouldAllowContent();
519 nsBaseClipboard::nsBaseClipboard(const ClipboardCapabilities& aClipboardCaps)
520 : mClipboardCaps(aClipboardCaps) {
521 using mozilla::MakeUnique;
522 // Initialize clipboard cache.
523 mCaches[kGlobalClipboard] = MakeUnique<ClipboardCache>();
524 if (mClipboardCaps.supportsSelectionClipboard()) {
525 mCaches[kSelectionClipboard] = MakeUnique<ClipboardCache>();
527 if (mClipboardCaps.supportsFindClipboard()) {
528 mCaches[kFindClipboard] = MakeUnique<ClipboardCache>();
530 if (mClipboardCaps.supportsSelectionCache()) {
531 mCaches[kSelectionCache] = MakeUnique<ClipboardCache>();
535 nsBaseClipboard::~nsBaseClipboard() {
536 for (auto& request : mPendingWriteRequests) {
537 if (request) {
538 request->Abort(NS_ERROR_ABORT);
539 request = nullptr;
544 NS_IMPL_ISUPPORTS(nsBaseClipboard, nsIClipboard)
547 * Sets the transferable object
550 NS_IMETHODIMP nsBaseClipboard::SetData(
551 nsITransferable* aTransferable, nsIClipboardOwner* aOwner,
552 int32_t aWhichClipboard, mozilla::dom::WindowContext* aWindowContext) {
553 NS_ASSERTION(aTransferable, "clipboard given a null transferable");
555 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
557 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
558 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
559 aWhichClipboard);
560 return NS_ERROR_FAILURE;
563 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
564 nsTArray<nsCString> flavors;
565 if (NS_SUCCEEDED(aTransferable->FlavorsTransferableCanImport(flavors))) {
566 for (const auto& flavor : flavors) {
567 MOZ_CLIPBOARD_LOG(" MIME %s", flavor.get());
572 const auto& clipboardCache = mCaches[aWhichClipboard];
573 MOZ_ASSERT(clipboardCache);
574 if (aTransferable == clipboardCache->GetTransferable() &&
575 aOwner == clipboardCache->GetClipboardOwner()) {
576 MOZ_CLIPBOARD_LOG("%s: skipping update.", __FUNCTION__);
577 return NS_OK;
580 clipboardCache->Clear();
582 nsresult rv = NS_ERROR_FAILURE;
583 if (aTransferable) {
584 mIgnoreEmptyNotification = true;
585 // Reject existing pending asyncSetData request if any.
586 RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard);
587 rv = SetNativeClipboardData(aTransferable, aWhichClipboard);
588 mIgnoreEmptyNotification = false;
590 if (NS_FAILED(rv)) {
591 MOZ_CLIPBOARD_LOG("%s: setting native clipboard data failed.",
592 __FUNCTION__);
593 return rv;
596 auto result = GetNativeClipboardSequenceNumber(aWhichClipboard);
597 if (result.isErr()) {
598 MOZ_CLIPBOARD_LOG("%s: getting native clipboard change count failed.",
599 __FUNCTION__);
600 return result.unwrapErr();
603 clipboardCache->Update(aTransferable, aOwner, result.unwrap(),
604 aWindowContext
605 ? mozilla::Some(aWindowContext->InnerWindowId())
606 : mozilla::Nothing());
607 return NS_OK;
610 nsresult nsBaseClipboard::GetDataFromClipboardCache(
611 nsITransferable* aTransferable, int32_t aClipboardType) {
612 MOZ_ASSERT(aTransferable);
613 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
615 const auto* clipboardCache = GetClipboardCacheIfValid(aClipboardType);
616 if (!clipboardCache) {
617 return NS_ERROR_FAILURE;
619 return clipboardCache->GetData(aTransferable);
623 * Gets the transferable object from system clipboard.
625 NS_IMETHODIMP nsBaseClipboard::GetData(
626 nsITransferable* aTransferable, int32_t aWhichClipboard,
627 mozilla::dom::WindowContext* aWindowContext) {
628 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
630 if (!aTransferable) {
631 NS_ASSERTION(false, "clipboard given a null transferable");
632 return NS_ERROR_FAILURE;
635 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
636 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
637 aWhichClipboard);
638 return NS_ERROR_FAILURE;
641 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
642 // If we were the last ones to put something on the native clipboard, then
643 // just use the cached transferable. Otherwise clear it because it isn't
644 // relevant any more.
645 if (NS_SUCCEEDED(
646 GetDataFromClipboardCache(aTransferable, aWhichClipboard))) {
647 // maybe try to fill in more types? Is there a point?
648 if (!CheckClipboardContentAnalysisSync(aWindowContext->Canonical(),
649 aTransferable, aWhichClipboard)) {
650 aTransferable->ClearAllData();
651 return NS_ERROR_CONTENT_BLOCKED;
653 return NS_OK;
656 // at this point we can't satisfy the request from cache data so let's look
657 // for things other people put on the system clipboard
659 nsresult rv = GetNativeClipboardData(aTransferable, aWhichClipboard);
660 if (NS_FAILED(rv)) {
661 return rv;
663 if (!CheckClipboardContentAnalysisSync(aWindowContext->Canonical(),
664 aTransferable, aWhichClipboard)) {
665 aTransferable->ClearAllData();
666 return NS_ERROR_CONTENT_BLOCKED;
668 return NS_OK;
671 void nsBaseClipboard::MaybeRetryGetAvailableFlavors(
672 const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
673 nsIAsyncClipboardGetCallback* aCallback, int32_t aRetryCount,
674 mozilla::dom::WindowContext* aRequestingWindowContext) {
675 // Note we have to get the clipboard sequence number first before the actual
676 // read. This is to use it to verify the clipboard data is still the one we
677 // try to read, instead of the later state.
678 auto sequenceNumberOrError =
679 GetNativeClipboardSequenceNumber(aWhichClipboard);
680 if (sequenceNumberOrError.isErr()) {
681 MOZ_CLIPBOARD_LOG("%s: unable to get sequence number for clipboard %d.",
682 __FUNCTION__, aWhichClipboard);
683 aCallback->OnError(sequenceNumberOrError.unwrapErr());
684 return;
687 int32_t sequenceNumber = sequenceNumberOrError.unwrap();
688 AsyncHasNativeClipboardDataMatchingFlavors(
689 aFlavorList, aWhichClipboard,
690 [self = RefPtr{this}, callback = nsCOMPtr{aCallback}, aWhichClipboard,
691 aRetryCount, flavorList = aFlavorList.Clone(), sequenceNumber,
692 requestingWindowContext =
693 RefPtr{aRequestingWindowContext}](auto aFlavorsOrError) {
694 if (aFlavorsOrError.isErr()) {
695 MOZ_CLIPBOARD_LOG(
696 "%s: unable to get available flavors for clipboard %d.",
697 __FUNCTION__, aWhichClipboard);
698 callback->OnError(aFlavorsOrError.unwrapErr());
699 return;
702 auto sequenceNumberOrError =
703 self->GetNativeClipboardSequenceNumber(aWhichClipboard);
704 if (sequenceNumberOrError.isErr()) {
705 MOZ_CLIPBOARD_LOG(
706 "%s: unable to get sequence number for clipboard %d.",
707 __FUNCTION__, aWhichClipboard);
708 callback->OnError(sequenceNumberOrError.unwrapErr());
709 return;
712 if (sequenceNumber == sequenceNumberOrError.unwrap()) {
713 auto asyncGetClipboardData =
714 mozilla::MakeRefPtr<AsyncGetClipboardData>(
715 aWhichClipboard, sequenceNumber,
716 std::move(aFlavorsOrError.unwrap()), false, self,
717 requestingWindowContext);
718 callback->OnSuccess(asyncGetClipboardData);
719 return;
722 if (aRetryCount > 0) {
723 MOZ_CLIPBOARD_LOG(
724 "%s: clipboard=%d, ignore the data due to the sequence number "
725 "doesn't match, retry (%d) ..",
726 __FUNCTION__, aWhichClipboard, aRetryCount);
727 self->MaybeRetryGetAvailableFlavors(flavorList, aWhichClipboard,
728 callback, aRetryCount - 1,
729 requestingWindowContext);
730 return;
733 MOZ_DIAGNOSTIC_ASSERT(false, "How can this happen?!?");
734 callback->OnError(NS_ERROR_FAILURE);
738 NS_IMETHODIMP nsBaseClipboard::AsyncGetData(
739 const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
740 mozilla::dom::WindowContext* aRequestingWindowContext,
741 nsIPrincipal* aRequestingPrincipal,
742 nsIAsyncClipboardGetCallback* aCallback) {
743 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
745 if (!aCallback || !aRequestingPrincipal || aFlavorList.IsEmpty()) {
746 return NS_ERROR_INVALID_ARG;
749 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
750 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
751 aWhichClipboard);
752 return NS_ERROR_FAILURE;
755 // We want to disable security check for automated tests that have the pref
756 // set to true, or extension that have clipboard read permission.
757 if (mozilla::StaticPrefs::
758 dom_events_testing_asyncClipboard_DoNotUseDirectly() ||
759 nsContentUtils::PrincipalHasPermission(*aRequestingPrincipal,
760 nsGkAtoms::clipboardRead)) {
761 AsyncGetDataInternal(aFlavorList, aWhichClipboard, aRequestingWindowContext,
762 aCallback);
763 return NS_OK;
766 // If cache data is valid, we are the last ones to put something on the native
767 // clipboard, then check if the data is from the same-origin page,
768 if (auto* clipboardCache = GetClipboardCacheIfValid(aWhichClipboard)) {
769 nsCOMPtr<nsITransferable> trans = clipboardCache->GetTransferable();
770 MOZ_ASSERT(trans);
772 if (nsCOMPtr<nsIPrincipal> principal = trans->GetRequestingPrincipal()) {
773 if (aRequestingPrincipal->Subsumes(principal)) {
774 MOZ_CLIPBOARD_LOG("%s: native clipboard data is from same-origin page.",
775 __FUNCTION__);
776 AsyncGetDataInternal(aFlavorList, aWhichClipboard,
777 aRequestingWindowContext, aCallback);
778 return NS_OK;
783 // TODO: enable showing the "Paste" button in this case; see bug 1773681.
784 if (aRequestingPrincipal->GetIsAddonOrExpandedAddonPrincipal()) {
785 MOZ_CLIPBOARD_LOG("%s: Addon without read permission.", __FUNCTION__);
786 return aCallback->OnError(NS_ERROR_FAILURE);
789 RequestUserConfirmation(aWhichClipboard, aFlavorList,
790 aRequestingWindowContext, aRequestingPrincipal,
791 aCallback);
792 return NS_OK;
795 already_AddRefed<nsIAsyncGetClipboardData>
796 nsBaseClipboard::MaybeCreateGetRequestFromClipboardCache(
797 const nsTArray<nsCString>& aFlavorList, int32_t aClipboardType,
798 mozilla::dom::WindowContext* aRequestingWindowContext) {
799 MOZ_DIAGNOSTIC_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
801 if (!mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
802 return nullptr;
805 // If we were the last ones to put something on the native clipboard, then
806 // just use the cached transferable. Otherwise clear it because it isn't
807 // relevant any more.
808 ClipboardCache* clipboardCache = GetClipboardCacheIfValid(aClipboardType);
809 if (!clipboardCache) {
810 return nullptr;
813 nsITransferable* cachedTransferable = clipboardCache->GetTransferable();
814 MOZ_ASSERT(cachedTransferable);
816 nsTArray<nsCString> transferableFlavors;
817 if (NS_FAILED(cachedTransferable->FlavorsTransferableCanExport(
818 transferableFlavors))) {
819 return nullptr;
822 nsTArray<nsCString> results;
823 for (const auto& flavor : aFlavorList) {
824 for (const auto& transferableFlavor : transferableFlavors) {
825 // XXX We need special check for image as we always put the
826 // image as "native" on the clipboard.
827 if (transferableFlavor.Equals(flavor) ||
828 (transferableFlavor.Equals(kNativeImageMime) &&
829 nsContentUtils::IsFlavorImage(flavor))) {
830 MOZ_CLIPBOARD_LOG(" has %s", flavor.get());
831 results.AppendElement(flavor);
836 // XXX Do we need to check system clipboard for the flavors that cannot
837 // be found in cache?
838 return mozilla::MakeAndAddRef<AsyncGetClipboardData>(
839 aClipboardType, clipboardCache->GetSequenceNumber(), std::move(results),
840 true /* aFromCache */, this, aRequestingWindowContext);
843 void nsBaseClipboard::AsyncGetDataInternal(
844 const nsTArray<nsCString>& aFlavorList, int32_t aClipboardType,
845 mozilla::dom::WindowContext* aRequestingWindowContext,
846 nsIAsyncClipboardGetCallback* aCallback) {
847 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
849 if (nsCOMPtr<nsIAsyncGetClipboardData> asyncGetClipboardData =
850 MaybeCreateGetRequestFromClipboardCache(aFlavorList, aClipboardType,
851 aRequestingWindowContext)) {
852 aCallback->OnSuccess(asyncGetClipboardData);
853 return;
856 // At this point we can't satisfy the request from cache data so let's
857 // look for things other people put on the system clipboard.
858 MaybeRetryGetAvailableFlavors(aFlavorList, aClipboardType, aCallback,
859 kGetAvailableFlavorsRetryCount,
860 aRequestingWindowContext);
863 NS_IMETHODIMP nsBaseClipboard::GetDataSnapshotSync(
864 const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
865 mozilla::dom::WindowContext* aRequestingWindowContext,
866 nsIAsyncGetClipboardData** _retval) {
867 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
869 *_retval = nullptr;
871 if (aFlavorList.IsEmpty()) {
872 return NS_ERROR_INVALID_ARG;
875 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
876 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
877 aWhichClipboard);
878 return NS_ERROR_FAILURE;
881 if (nsCOMPtr<nsIAsyncGetClipboardData> asyncGetClipboardData =
882 MaybeCreateGetRequestFromClipboardCache(aFlavorList, aWhichClipboard,
883 aRequestingWindowContext)) {
884 asyncGetClipboardData.forget(_retval);
885 return NS_OK;
888 auto sequenceNumberOrError =
889 GetNativeClipboardSequenceNumber(aWhichClipboard);
890 if (sequenceNumberOrError.isErr()) {
891 MOZ_CLIPBOARD_LOG("%s: unable to get sequence number for clipboard %d.",
892 __FUNCTION__, aWhichClipboard);
893 return sequenceNumberOrError.unwrapErr();
896 nsTArray<nsCString> results;
897 for (const auto& flavor : aFlavorList) {
898 auto resultOrError = HasNativeClipboardDataMatchingFlavors(
899 AutoTArray<nsCString, 1>{flavor}, aWhichClipboard);
900 if (resultOrError.isOk() && resultOrError.unwrap()) {
901 results.AppendElement(flavor);
905 *_retval =
906 mozilla::MakeAndAddRef<AsyncGetClipboardData>(
907 aWhichClipboard, sequenceNumberOrError.unwrap(), std::move(results),
908 false /* aFromCache */, this, aRequestingWindowContext)
909 .take();
910 return NS_OK;
913 NS_IMETHODIMP nsBaseClipboard::EmptyClipboard(int32_t aWhichClipboard) {
914 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
916 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
917 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
918 aWhichClipboard);
919 return NS_ERROR_FAILURE;
922 EmptyNativeClipboardData(aWhichClipboard);
924 const auto& clipboardCache = mCaches[aWhichClipboard];
925 MOZ_ASSERT(clipboardCache);
927 if (mIgnoreEmptyNotification) {
928 MOZ_DIAGNOSTIC_ASSERT(!clipboardCache->GetTransferable() &&
929 !clipboardCache->GetClipboardOwner() &&
930 clipboardCache->GetSequenceNumber() == -1,
931 "How did we have data in clipboard cache here?");
932 return NS_OK;
935 clipboardCache->Clear();
937 return NS_OK;
940 mozilla::Result<nsTArray<nsCString>, nsresult>
941 nsBaseClipboard::GetFlavorsFromClipboardCache(int32_t aClipboardType) {
942 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
943 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
945 const auto* clipboardCache = GetClipboardCacheIfValid(aClipboardType);
946 if (!clipboardCache) {
947 return mozilla::Err(NS_ERROR_FAILURE);
950 nsITransferable* cachedTransferable = clipboardCache->GetTransferable();
951 MOZ_ASSERT(cachedTransferable);
953 nsTArray<nsCString> flavors;
954 nsresult rv = cachedTransferable->FlavorsTransferableCanExport(flavors);
955 if (NS_FAILED(rv)) {
956 return mozilla::Err(rv);
959 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
960 MOZ_CLIPBOARD_LOG(" Cached transferable types (nums %zu)\n",
961 flavors.Length());
962 for (const auto& flavor : flavors) {
963 MOZ_CLIPBOARD_LOG(" MIME %s", flavor.get());
967 return std::move(flavors);
970 NS_IMETHODIMP
971 nsBaseClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
972 int32_t aWhichClipboard,
973 bool* aOutResult) {
974 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
975 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
976 MOZ_CLIPBOARD_LOG(" Asking for content clipboard=%i:\n",
977 aWhichClipboard);
978 for (const auto& flavor : aFlavorList) {
979 MOZ_CLIPBOARD_LOG(" MIME %s", flavor.get());
983 *aOutResult = false;
985 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
986 // First, check if we have valid data in our cached transferable.
987 auto flavorsOrError = GetFlavorsFromClipboardCache(aWhichClipboard);
988 if (flavorsOrError.isOk()) {
989 for (const auto& transferableFlavor : flavorsOrError.unwrap()) {
990 for (const auto& flavor : aFlavorList) {
991 if (transferableFlavor.Equals(flavor)) {
992 MOZ_CLIPBOARD_LOG(" has %s", flavor.get());
993 *aOutResult = true;
994 return NS_OK;
1001 auto resultOrError =
1002 HasNativeClipboardDataMatchingFlavors(aFlavorList, aWhichClipboard);
1003 if (resultOrError.isErr()) {
1004 MOZ_CLIPBOARD_LOG(
1005 "%s: checking native clipboard data matching flavors falied.",
1006 __FUNCTION__);
1007 return resultOrError.unwrapErr();
1010 *aOutResult = resultOrError.unwrap();
1011 return NS_OK;
1014 NS_IMETHODIMP
1015 nsBaseClipboard::IsClipboardTypeSupported(int32_t aWhichClipboard,
1016 bool* aRetval) {
1017 NS_ENSURE_ARG_POINTER(aRetval);
1018 switch (aWhichClipboard) {
1019 case kGlobalClipboard:
1020 // We always support the global clipboard.
1021 *aRetval = true;
1022 return NS_OK;
1023 case kSelectionClipboard:
1024 *aRetval = mClipboardCaps.supportsSelectionClipboard();
1025 return NS_OK;
1026 case kFindClipboard:
1027 *aRetval = mClipboardCaps.supportsFindClipboard();
1028 return NS_OK;
1029 case kSelectionCache:
1030 *aRetval = mClipboardCaps.supportsSelectionCache();
1031 return NS_OK;
1032 default:
1033 *aRetval = false;
1034 return NS_OK;
1038 void nsBaseClipboard::AsyncHasNativeClipboardDataMatchingFlavors(
1039 const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
1040 HasMatchingFlavorsCallback&& aCallback) {
1041 MOZ_DIAGNOSTIC_ASSERT(
1042 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
1044 MOZ_CLIPBOARD_LOG(
1045 "nsBaseClipboard::AsyncHasNativeClipboardDataMatchingFlavors: "
1046 "clipboard=%d",
1047 aWhichClipboard);
1049 nsTArray<nsCString> results;
1050 for (const auto& flavor : aFlavorList) {
1051 auto resultOrError = HasNativeClipboardDataMatchingFlavors(
1052 AutoTArray<nsCString, 1>{flavor}, aWhichClipboard);
1053 if (resultOrError.isOk() && resultOrError.unwrap()) {
1054 results.AppendElement(flavor);
1057 aCallback(std::move(results));
1060 void nsBaseClipboard::AsyncGetNativeClipboardData(
1061 nsITransferable* aTransferable, int32_t aWhichClipboard,
1062 GetDataCallback&& aCallback) {
1063 aCallback(GetNativeClipboardData(aTransferable, aWhichClipboard));
1066 void nsBaseClipboard::ClearClipboardCache(int32_t aClipboardType) {
1067 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
1068 const mozilla::UniquePtr<ClipboardCache>& cache = mCaches[aClipboardType];
1069 MOZ_ASSERT(cache);
1070 cache->Clear();
1073 void nsBaseClipboard::RequestUserConfirmation(
1074 int32_t aClipboardType, const nsTArray<nsCString>& aFlavorList,
1075 mozilla::dom::WindowContext* aWindowContext,
1076 nsIPrincipal* aRequestingPrincipal,
1077 nsIAsyncClipboardGetCallback* aCallback) {
1078 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
1079 MOZ_ASSERT(aCallback);
1081 if (!aWindowContext) {
1082 aCallback->OnError(NS_ERROR_FAILURE);
1083 return;
1086 CanonicalBrowsingContext* cbc =
1087 CanonicalBrowsingContext::Cast(aWindowContext->GetBrowsingContext());
1088 MOZ_ASSERT(
1089 cbc->IsContent(),
1090 "Should not require user confirmation when access from chrome window");
1092 RefPtr<CanonicalBrowsingContext> chromeTop = cbc->TopCrossChromeBoundary();
1093 Document* chromeDoc = chromeTop ? chromeTop->GetDocument() : nullptr;
1094 if (!chromeDoc || !chromeDoc->HasFocus(mozilla::IgnoreErrors())) {
1095 MOZ_CLIPBOARD_LOG("%s: reject due to not in the focused window",
1096 __FUNCTION__);
1097 aCallback->OnError(NS_ERROR_FAILURE);
1098 return;
1101 mozilla::dom::Element* activeElementInChromeDoc =
1102 chromeDoc->GetActiveElement();
1103 if (activeElementInChromeDoc != cbc->Top()->GetEmbedderElement()) {
1104 // Reject if the request is not from web content that is in the focused tab.
1105 MOZ_CLIPBOARD_LOG("%s: reject due to not in the focused tab", __FUNCTION__);
1106 aCallback->OnError(NS_ERROR_FAILURE);
1107 return;
1110 // If there is a pending user confirmation request, check if we could reuse
1111 // it. If not, reject the request.
1112 if (sUserConfirmationRequest) {
1113 if (sUserConfirmationRequest->IsEqual(
1114 aClipboardType, chromeDoc, aRequestingPrincipal, aWindowContext)) {
1115 sUserConfirmationRequest->AddClipboardGetRequest(aFlavorList, aCallback);
1116 return;
1119 aCallback->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR);
1120 return;
1123 nsresult rv = NS_ERROR_FAILURE;
1124 nsCOMPtr<nsIPromptService> promptService =
1125 do_GetService("@mozilla.org/prompter;1", &rv);
1126 if (NS_FAILED(rv)) {
1127 aCallback->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR);
1128 return;
1131 RefPtr<mozilla::dom::Promise> promise;
1132 if (NS_FAILED(promptService->ConfirmUserPaste(aWindowContext->Canonical(),
1133 getter_AddRefs(promise)))) {
1134 aCallback->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR);
1135 return;
1138 sUserConfirmationRequest = new UserConfirmationRequest(
1139 aClipboardType, chromeDoc, aRequestingPrincipal, this, aWindowContext);
1140 sUserConfirmationRequest->AddClipboardGetRequest(aFlavorList, aCallback);
1141 promise->AppendNativeHandler(sUserConfirmationRequest);
1144 NS_IMPL_ISUPPORTS(nsBaseClipboard::AsyncGetClipboardData,
1145 nsIAsyncGetClipboardData)
1147 nsBaseClipboard::AsyncGetClipboardData::AsyncGetClipboardData(
1148 int32_t aClipboardType, int32_t aSequenceNumber,
1149 nsTArray<nsCString>&& aFlavors, bool aFromCache,
1150 nsBaseClipboard* aClipboard,
1151 mozilla::dom::WindowContext* aRequestingWindowContext)
1152 : mClipboardType(aClipboardType),
1153 mSequenceNumber(aSequenceNumber),
1154 mFlavors(std::move(aFlavors)),
1155 mFromCache(aFromCache),
1156 mClipboard(aClipboard),
1157 mRequestingWindowContext(aRequestingWindowContext) {
1158 MOZ_ASSERT(mClipboard);
1159 MOZ_ASSERT(
1160 mClipboard->nsIClipboard::IsClipboardTypeSupported(mClipboardType));
1163 NS_IMETHODIMP nsBaseClipboard::AsyncGetClipboardData::GetValid(
1164 bool* aOutResult) {
1165 *aOutResult = IsValid();
1166 return NS_OK;
1169 NS_IMETHODIMP nsBaseClipboard::AsyncGetClipboardData::GetFlavorList(
1170 nsTArray<nsCString>& aFlavors) {
1171 aFlavors.AppendElements(mFlavors);
1172 return NS_OK;
1175 NS_IMETHODIMP nsBaseClipboard::AsyncGetClipboardData::GetData(
1176 nsITransferable* aTransferable,
1177 nsIAsyncClipboardRequestCallback* aCallback) {
1178 MOZ_CLIPBOARD_LOG("AsyncGetClipboardData::GetData: %p", this);
1180 if (!aTransferable || !aCallback) {
1181 return NS_ERROR_INVALID_ARG;
1184 nsTArray<nsCString> flavors;
1185 nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
1186 if (NS_FAILED(rv)) {
1187 return rv;
1190 // If the requested flavor is not in the list, throw an error.
1191 for (const auto& flavor : flavors) {
1192 if (!mFlavors.Contains(flavor)) {
1193 return NS_ERROR_FAILURE;
1197 if (!IsValid()) {
1198 aCallback->OnComplete(NS_ERROR_FAILURE);
1199 return NS_OK;
1202 MOZ_ASSERT(mClipboard);
1204 auto contentAnalysisCallback =
1205 mozilla::MakeRefPtr<SafeContentAnalysisResultCallback>(
1206 [transferable = nsCOMPtr{aTransferable},
1207 callback = nsCOMPtr{aCallback}](
1208 RefPtr<nsIContentAnalysisResult>&& aResult) {
1209 if (aResult->GetShouldAllowContent()) {
1210 callback->OnComplete(NS_OK);
1211 } else {
1212 transferable->ClearAllData();
1213 callback->OnComplete(NS_ERROR_CONTENT_BLOCKED);
1217 if (mFromCache) {
1218 const auto* clipboardCache =
1219 mClipboard->GetClipboardCacheIfValid(mClipboardType);
1220 // `IsValid()` above ensures we should get a valid cache and matched
1221 // sequence number here.
1222 MOZ_DIAGNOSTIC_ASSERT(clipboardCache);
1223 MOZ_DIAGNOSTIC_ASSERT(clipboardCache->GetSequenceNumber() ==
1224 mSequenceNumber);
1225 if (NS_SUCCEEDED(clipboardCache->GetData(aTransferable))) {
1226 mClipboard->CheckClipboardContentAnalysis(
1227 mRequestingWindowContext ? mRequestingWindowContext->Canonical()
1228 : nullptr,
1229 aTransferable, mClipboardType, contentAnalysisCallback);
1230 return NS_OK;
1233 // At this point we can't satisfy the request from cache data so let's look
1234 // for things other people put on the system clipboard.
1237 // Since this is an async operation, we need to check if the data is still
1238 // valid after we get the result.
1239 mClipboard->AsyncGetNativeClipboardData(
1240 aTransferable, mClipboardType,
1241 [callback = nsCOMPtr{aCallback}, self = RefPtr{this},
1242 transferable = nsCOMPtr{aTransferable},
1243 contentAnalysisCallback =
1244 std::move(contentAnalysisCallback)](nsresult aResult) mutable {
1245 if (NS_FAILED(aResult)) {
1246 callback->OnComplete(aResult);
1247 return;
1249 // `IsValid()` checks the clipboard sequence number to ensure the data
1250 // we are requesting is still valid.
1251 if (!self->IsValid()) {
1252 callback->OnComplete(NS_ERROR_FAILURE);
1253 return;
1255 self->mClipboard->CheckClipboardContentAnalysis(
1256 self->mRequestingWindowContext
1257 ? self->mRequestingWindowContext->Canonical()
1258 : nullptr,
1259 transferable, self->mClipboardType, contentAnalysisCallback);
1261 return NS_OK;
1264 bool nsBaseClipboard::AsyncGetClipboardData::IsValid() {
1265 if (!mClipboard) {
1266 return false;
1269 // If the data should from cache, check if cache is still valid or the
1270 // sequence numbers are matched.
1271 if (mFromCache) {
1272 const auto* clipboardCache =
1273 mClipboard->GetClipboardCacheIfValid(mClipboardType);
1274 if (!clipboardCache) {
1275 mClipboard = nullptr;
1276 return false;
1279 return mSequenceNumber == clipboardCache->GetSequenceNumber();
1282 auto resultOrError =
1283 mClipboard->GetNativeClipboardSequenceNumber(mClipboardType);
1284 if (resultOrError.isErr()) {
1285 mClipboard = nullptr;
1286 return false;
1289 if (mSequenceNumber != resultOrError.unwrap()) {
1290 mClipboard = nullptr;
1291 return false;
1294 return true;
1297 nsBaseClipboard::ClipboardCache* nsBaseClipboard::GetClipboardCacheIfValid(
1298 int32_t aClipboardType) {
1299 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
1301 const mozilla::UniquePtr<ClipboardCache>& cache = mCaches[aClipboardType];
1302 MOZ_ASSERT(cache);
1304 if (!cache->GetTransferable()) {
1305 MOZ_ASSERT(cache->GetSequenceNumber() == -1);
1306 return nullptr;
1309 auto changeCountOrError = GetNativeClipboardSequenceNumber(aClipboardType);
1310 if (changeCountOrError.isErr()) {
1311 return nullptr;
1314 if (changeCountOrError.unwrap() != cache->GetSequenceNumber()) {
1315 // Clipboard cache is invalid, clear it.
1316 cache->Clear();
1317 return nullptr;
1320 return cache.get();
1323 void nsBaseClipboard::ClipboardCache::Clear() {
1324 if (mClipboardOwner) {
1325 mClipboardOwner->LosingOwnership(mTransferable);
1326 mClipboardOwner = nullptr;
1328 mTransferable = nullptr;
1329 mSequenceNumber = -1;
1332 nsresult nsBaseClipboard::ClipboardCache::GetData(
1333 nsITransferable* aTransferable) const {
1334 MOZ_ASSERT(aTransferable);
1335 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
1337 // get flavor list that includes all acceptable flavors (including ones
1338 // obtained through conversion)
1339 nsTArray<nsCString> flavors;
1340 if (NS_FAILED(aTransferable->FlavorsTransferableCanImport(flavors))) {
1341 return NS_ERROR_FAILURE;
1344 MOZ_ASSERT(mTransferable);
1345 for (const auto& flavor : flavors) {
1346 nsCOMPtr<nsISupports> dataSupports;
1347 // XXX Maybe we need special check for image as we always put the image as
1348 // "native" on the clipboard.
1349 if (NS_SUCCEEDED(mTransferable->GetTransferData(
1350 flavor.get(), getter_AddRefs(dataSupports)))) {
1351 MOZ_CLIPBOARD_LOG("%s: getting %s from cache.", __FUNCTION__,
1352 flavor.get());
1353 aTransferable->SetTransferData(flavor.get(), dataSupports);
1354 // XXX we only read the first available type from native clipboard, so
1355 // make cache behave the same.
1356 return NS_OK;
1360 return NS_ERROR_FAILURE;