Bug 1889091 - Part 4: Remove extra stack pointer move. r=jandem
[gecko.git] / widget / nsBaseClipboard.cpp
blob9ffc53b4c2ebde82255e42f7b46e53508edccd14
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 nsIAsyncClipboardRequestCallback* aCallback)
215 : mClipboardType(aClipboardType),
216 mClipboard(aClipboard),
217 mCallback(aCallback) {
218 MOZ_ASSERT(mClipboard);
219 MOZ_ASSERT(
220 mClipboard->nsIClipboard::IsClipboardTypeSupported(mClipboardType));
223 NS_IMETHODIMP
224 nsBaseClipboard::AsyncSetClipboardData::SetData(nsITransferable* aTransferable,
225 nsIClipboardOwner* aOwner) {
226 MOZ_CLIPBOARD_LOG("AsyncSetClipboardData::SetData (%p): clipboard=%d", this,
227 mClipboardType);
229 if (!IsValid()) {
230 return NS_ERROR_FAILURE;
233 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
234 nsTArray<nsCString> flavors;
235 if (NS_SUCCEEDED(aTransferable->FlavorsTransferableCanImport(flavors))) {
236 for (const auto& flavor : flavors) {
237 MOZ_CLIPBOARD_LOG(" MIME %s", flavor.get());
242 MOZ_ASSERT(mClipboard);
243 MOZ_ASSERT(
244 mClipboard->nsIClipboard::IsClipboardTypeSupported(mClipboardType));
245 MOZ_DIAGNOSTIC_ASSERT(mClipboard->mPendingWriteRequests[mClipboardType] ==
246 this);
248 RefPtr<AsyncSetClipboardData> request =
249 std::move(mClipboard->mPendingWriteRequests[mClipboardType]);
250 nsresult rv = mClipboard->SetData(aTransferable, aOwner, mClipboardType);
251 MaybeNotifyCallback(rv);
253 return rv;
256 NS_IMETHODIMP
257 nsBaseClipboard::AsyncSetClipboardData::Abort(nsresult aReason) {
258 // Note: This may be called during destructor, so it should not attempt to
259 // take a reference to mClipboard.
261 if (!IsValid() || !NS_FAILED(aReason)) {
262 return NS_ERROR_FAILURE;
265 MaybeNotifyCallback(aReason);
266 return NS_OK;
269 void nsBaseClipboard::AsyncSetClipboardData::MaybeNotifyCallback(
270 nsresult aResult) {
271 // Note: This may be called during destructor, so it should not attempt to
272 // take a reference to mClipboard.
274 MOZ_ASSERT(IsValid());
275 if (nsCOMPtr<nsIAsyncClipboardRequestCallback> callback =
276 mCallback.forget()) {
277 callback->OnComplete(aResult);
279 // Once the callback is notified, setData should not be allowed, so invalidate
280 // this request.
281 mClipboard = nullptr;
284 void nsBaseClipboard::RejectPendingAsyncSetDataRequestIfAny(
285 int32_t aClipboardType) {
286 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
287 auto& request = mPendingWriteRequests[aClipboardType];
288 if (request) {
289 request->Abort(NS_ERROR_ABORT);
290 request = nullptr;
294 NS_IMETHODIMP nsBaseClipboard::AsyncSetData(
295 int32_t aWhichClipboard, nsIAsyncClipboardRequestCallback* aCallback,
296 nsIAsyncSetClipboardData** _retval) {
297 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
299 *_retval = nullptr;
300 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
301 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
302 aWhichClipboard);
303 return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
306 // Reject existing pending AsyncSetData request if any.
307 RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard);
309 // Create a new AsyncSetClipboardData.
310 RefPtr<AsyncSetClipboardData> request =
311 mozilla::MakeRefPtr<AsyncSetClipboardData>(aWhichClipboard, this,
312 aCallback);
313 mPendingWriteRequests[aWhichClipboard] = request;
314 request.forget(_retval);
315 return NS_OK;
318 namespace {
319 class SafeContentAnalysisResultCallback final
320 : public nsIContentAnalysisCallback {
321 public:
322 explicit SafeContentAnalysisResultCallback(
323 std::function<void(RefPtr<nsIContentAnalysisResult>&&)> aResolver)
324 : mResolver(std::move(aResolver)) {}
325 void Callback(RefPtr<nsIContentAnalysisResult>&& aResult) {
326 MOZ_ASSERT(mResolver, "Called SafeContentAnalysisResultCallback twice!");
327 if (auto resolver = std::move(mResolver)) {
328 resolver(std::move(aResult));
332 NS_IMETHODIMP ContentResult(nsIContentAnalysisResponse* aResponse) override {
333 using namespace mozilla::contentanalysis;
334 RefPtr<ContentAnalysisResult> result =
335 ContentAnalysisResult::FromContentAnalysisResponse(aResponse);
336 Callback(result);
337 return NS_OK;
340 NS_IMETHODIMP Error(nsresult aError) override {
341 using namespace mozilla::contentanalysis;
342 Callback(ContentAnalysisResult::FromNoResult(
343 NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR));
344 return NS_OK;
347 NS_DECL_THREADSAFE_ISUPPORTS
348 private:
349 // Private destructor to force this to be allocated in a RefPtr, which is
350 // necessary for safe usage.
351 ~SafeContentAnalysisResultCallback() {
352 MOZ_ASSERT(!mResolver, "SafeContentAnalysisResultCallback never called!");
354 mozilla::MoveOnlyFunction<void(RefPtr<nsIContentAnalysisResult>&&)> mResolver;
356 NS_IMPL_ISUPPORTS(SafeContentAnalysisResultCallback,
357 nsIContentAnalysisCallback);
358 } // namespace
360 // Returning:
361 // - true means a content analysis request was fired
362 // - false means there is no text data in the transferable
363 // - NoContentAnalysisResult means there was an error
364 static mozilla::Result<bool, mozilla::contentanalysis::NoContentAnalysisResult>
365 CheckClipboardContentAnalysisAsText(
366 uint64_t aInnerWindowId, SafeContentAnalysisResultCallback* aResolver,
367 nsIURI* aDocumentURI, nsIContentAnalysis* aContentAnalysis,
368 nsITransferable* aTextTrans) {
369 using namespace mozilla::contentanalysis;
371 nsCOMPtr<nsISupports> transferData;
372 if (NS_FAILED(aTextTrans->GetTransferData(kTextMime,
373 getter_AddRefs(transferData)))) {
374 return false;
376 nsCOMPtr<nsISupportsString> textData = do_QueryInterface(transferData);
377 if (MOZ_UNLIKELY(!textData)) {
378 return false;
380 nsString text;
381 if (NS_FAILED(textData->GetData(text))) {
382 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
384 if (text.IsEmpty()) {
385 // Content Analysis doesn't expect to analyze an empty string.
386 // Just approve it.
387 return true;
389 RefPtr<mozilla::dom::WindowGlobalParent> window =
390 mozilla::dom::WindowGlobalParent::GetByInnerWindowId(aInnerWindowId);
391 if (!window) {
392 // The window has gone away in the meantime
393 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
395 nsCOMPtr<nsIContentAnalysisRequest> contentAnalysisRequest =
396 new ContentAnalysisRequest(
397 nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry,
398 std::move(text), false, EmptyCString(), aDocumentURI,
399 nsIContentAnalysisRequest::OperationType::eClipboard, window);
400 nsresult rv = aContentAnalysis->AnalyzeContentRequestCallback(
401 contentAnalysisRequest, /* aAutoAcknowledge */ true, aResolver);
402 if (NS_FAILED(rv)) {
403 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
405 return true;
408 // Returning:
409 // - true means a content analysis request was fired
410 // - false means there is no file data in the transferable
411 // - NoContentAnalysisResult means there was an error
412 static mozilla::Result<bool, mozilla::contentanalysis::NoContentAnalysisResult>
413 CheckClipboardContentAnalysisAsFile(
414 uint64_t aInnerWindowId, SafeContentAnalysisResultCallback* aResolver,
415 nsIURI* aDocumentURI, nsIContentAnalysis* aContentAnalysis,
416 nsITransferable* aFileTrans) {
417 using namespace mozilla::contentanalysis;
419 nsCOMPtr<nsISupports> transferData;
420 nsresult rv =
421 aFileTrans->GetTransferData(kFileMime, getter_AddRefs(transferData));
422 nsString filePath;
423 if (NS_SUCCEEDED(rv)) {
424 if (nsCOMPtr<nsIFile> file = do_QueryInterface(transferData)) {
425 rv = file->GetPath(filePath);
426 } else {
427 MOZ_ASSERT_UNREACHABLE("clipboard data had kFileMime but no nsIFile!");
428 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
431 if (NS_FAILED(rv) || filePath.IsEmpty()) {
432 return false;
434 RefPtr<mozilla::dom::WindowGlobalParent> window =
435 mozilla::dom::WindowGlobalParent::GetByInnerWindowId(aInnerWindowId);
436 if (!window) {
437 // The window has gone away in the meantime
438 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
440 // Let the content analysis code calculate the digest
441 nsCOMPtr<nsIContentAnalysisRequest> contentAnalysisRequest =
442 new ContentAnalysisRequest(
443 nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry,
444 std::move(filePath), true, EmptyCString(), aDocumentURI,
445 nsIContentAnalysisRequest::OperationType::eCustomDisplayString,
446 window);
447 rv = aContentAnalysis->AnalyzeContentRequestCallback(
448 contentAnalysisRequest,
449 /* aAutoAcknowledge */ true, aResolver);
450 if (NS_FAILED(rv)) {
451 return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
453 return true;
456 static void CheckClipboardContentAnalysis(
457 mozilla::dom::WindowGlobalParent* aWindow, nsITransferable* aTransferable,
458 SafeContentAnalysisResultCallback* aResolver) {
459 using namespace mozilla::contentanalysis;
461 // Content analysis is only needed if an outside webpage has access to
462 // the data. So, skip content analysis if there is:
463 // - no associated window (for example, scripted clipboard read by system
464 // code)
465 // - the window is a chrome docshell
466 // - the window is being rendered in the parent process (for example,
467 // about:support and the like)
468 if (!aWindow || aWindow->GetBrowsingContext()->IsChrome() ||
469 aWindow->IsInProcess()) {
470 aResolver->Callback(ContentAnalysisResult::FromNoResult(
471 NoContentAnalysisResult::
472 ALLOW_DUE_TO_CONTEXT_EXEMPT_FROM_CONTENT_ANALYSIS));
473 return;
475 nsCOMPtr<nsIContentAnalysis> contentAnalysis =
476 mozilla::components::nsIContentAnalysis::Service();
477 if (!contentAnalysis) {
478 aResolver->Callback(ContentAnalysisResult::FromNoResult(
479 NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR));
480 return;
483 bool contentAnalysisIsActive;
484 nsresult rv = contentAnalysis->GetIsActive(&contentAnalysisIsActive);
485 if (MOZ_LIKELY(NS_FAILED(rv) || !contentAnalysisIsActive)) {
486 aResolver->Callback(ContentAnalysisResult::FromNoResult(
487 NoContentAnalysisResult::ALLOW_DUE_TO_CONTENT_ANALYSIS_NOT_ACTIVE));
488 return;
491 nsCOMPtr<nsIURI> currentURI = aWindow->Canonical()->GetDocumentURI();
492 uint64_t innerWindowId = aWindow->InnerWindowId();
493 nsTArray<nsCString> flavors;
494 rv = aTransferable->FlavorsTransferableCanExport(flavors);
495 if (NS_WARN_IF(NS_FAILED(rv))) {
496 aResolver->Callback(ContentAnalysisResult::FromNoResult(
497 NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR));
498 return;
500 bool keepChecking = true;
501 if (flavors.Contains(kFileMime)) {
502 auto fileResult = CheckClipboardContentAnalysisAsFile(
503 innerWindowId, aResolver, currentURI, contentAnalysis, aTransferable);
505 if (fileResult.isErr()) {
506 aResolver->Callback(
507 ContentAnalysisResult::FromNoResult(fileResult.unwrapErr()));
508 return;
510 keepChecking = !fileResult.unwrap();
512 if (keepChecking) {
513 // Failed to get the clipboard data as a file, so try as text
514 auto textResult = CheckClipboardContentAnalysisAsText(
515 innerWindowId, aResolver, currentURI, contentAnalysis, aTransferable);
516 if (textResult.isErr()) {
517 aResolver->Callback(
518 ContentAnalysisResult::FromNoResult(textResult.unwrapErr()));
519 return;
521 if (!textResult.unwrap()) {
522 // Couldn't get file or text data from this
523 aResolver->Callback(ContentAnalysisResult::FromNoResult(
524 NoContentAnalysisResult::ALLOW_DUE_TO_COULD_NOT_GET_DATA));
525 return;
530 static bool CheckClipboardContentAnalysisSync(
531 mozilla::dom::WindowGlobalParent* aWindow,
532 const nsCOMPtr<nsITransferable>& trans) {
533 bool requestDone = false;
534 RefPtr<nsIContentAnalysisResult> result;
535 auto callback = mozilla::MakeRefPtr<SafeContentAnalysisResultCallback>(
536 [&requestDone, &result](RefPtr<nsIContentAnalysisResult>&& aResult) {
537 result = std::move(aResult);
538 requestDone = true;
540 CheckClipboardContentAnalysis(aWindow, trans, callback);
541 mozilla::SpinEventLoopUntil("CheckClipboardContentAnalysisSync"_ns,
542 [&requestDone]() -> bool { return requestDone; });
543 return result->GetShouldAllowContent();
546 nsBaseClipboard::nsBaseClipboard(const ClipboardCapabilities& aClipboardCaps)
547 : mClipboardCaps(aClipboardCaps) {
548 using mozilla::MakeUnique;
549 // Initialize clipboard cache.
550 mCaches[kGlobalClipboard] = MakeUnique<ClipboardCache>();
551 if (mClipboardCaps.supportsSelectionClipboard()) {
552 mCaches[kSelectionClipboard] = MakeUnique<ClipboardCache>();
554 if (mClipboardCaps.supportsFindClipboard()) {
555 mCaches[kFindClipboard] = MakeUnique<ClipboardCache>();
557 if (mClipboardCaps.supportsSelectionCache()) {
558 mCaches[kSelectionCache] = MakeUnique<ClipboardCache>();
562 nsBaseClipboard::~nsBaseClipboard() {
563 for (auto& request : mPendingWriteRequests) {
564 if (request) {
565 request->Abort(NS_ERROR_ABORT);
566 request = nullptr;
571 NS_IMPL_ISUPPORTS(nsBaseClipboard, nsIClipboard)
574 * Sets the transferable object
577 NS_IMETHODIMP nsBaseClipboard::SetData(nsITransferable* aTransferable,
578 nsIClipboardOwner* aOwner,
579 int32_t aWhichClipboard) {
580 NS_ASSERTION(aTransferable, "clipboard given a null transferable");
582 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
584 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
585 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
586 aWhichClipboard);
587 return NS_ERROR_FAILURE;
590 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
591 nsTArray<nsCString> flavors;
592 if (NS_SUCCEEDED(aTransferable->FlavorsTransferableCanImport(flavors))) {
593 for (const auto& flavor : flavors) {
594 MOZ_CLIPBOARD_LOG(" MIME %s", flavor.get());
599 const auto& clipboardCache = mCaches[aWhichClipboard];
600 MOZ_ASSERT(clipboardCache);
601 if (aTransferable == clipboardCache->GetTransferable() &&
602 aOwner == clipboardCache->GetClipboardOwner()) {
603 MOZ_CLIPBOARD_LOG("%s: skipping update.", __FUNCTION__);
604 return NS_OK;
607 clipboardCache->Clear();
609 nsresult rv = NS_ERROR_FAILURE;
610 if (aTransferable) {
611 mIgnoreEmptyNotification = true;
612 // Reject existing pending asyncSetData request if any.
613 RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard);
614 rv = SetNativeClipboardData(aTransferable, aWhichClipboard);
615 mIgnoreEmptyNotification = false;
617 if (NS_FAILED(rv)) {
618 MOZ_CLIPBOARD_LOG("%s: setting native clipboard data failed.",
619 __FUNCTION__);
620 return rv;
623 auto result = GetNativeClipboardSequenceNumber(aWhichClipboard);
624 if (result.isErr()) {
625 MOZ_CLIPBOARD_LOG("%s: getting native clipboard change count failed.",
626 __FUNCTION__);
627 return result.unwrapErr();
630 clipboardCache->Update(aTransferable, aOwner, result.unwrap());
631 return NS_OK;
634 nsresult nsBaseClipboard::GetDataFromClipboardCache(
635 nsITransferable* aTransferable, int32_t aClipboardType) {
636 MOZ_ASSERT(aTransferable);
637 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
638 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
640 const auto* clipboardCache = GetClipboardCacheIfValid(aClipboardType);
641 if (!clipboardCache) {
642 return NS_ERROR_FAILURE;
645 return clipboardCache->GetData(aTransferable);
649 * Gets the transferable object from system clipboard.
651 NS_IMETHODIMP nsBaseClipboard::GetData(
652 nsITransferable* aTransferable, int32_t aWhichClipboard,
653 mozilla::dom::WindowContext* aWindowContext) {
654 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
656 if (!aTransferable) {
657 NS_ASSERTION(false, "clipboard given a null transferable");
658 return NS_ERROR_FAILURE;
661 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
662 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
663 aWhichClipboard);
664 return NS_ERROR_FAILURE;
667 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
668 // If we were the last ones to put something on the native clipboard, then
669 // just use the cached transferable. Otherwise clear it because it isn't
670 // relevant any more.
671 if (NS_SUCCEEDED(
672 GetDataFromClipboardCache(aTransferable, aWhichClipboard))) {
673 // maybe try to fill in more types? Is there a point?
674 if (!CheckClipboardContentAnalysisSync(aWindowContext->Canonical(),
675 aTransferable)) {
676 aTransferable->ClearAllData();
677 return NS_ERROR_CONTENT_BLOCKED;
679 return NS_OK;
682 // at this point we can't satisfy the request from cache data so let's look
683 // for things other people put on the system clipboard
685 nsresult rv = GetNativeClipboardData(aTransferable, aWhichClipboard);
686 if (NS_FAILED(rv)) {
687 return rv;
689 if (!CheckClipboardContentAnalysisSync(aWindowContext->Canonical(),
690 aTransferable)) {
691 aTransferable->ClearAllData();
692 return NS_ERROR_CONTENT_BLOCKED;
694 return NS_OK;
697 void nsBaseClipboard::MaybeRetryGetAvailableFlavors(
698 const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
699 nsIAsyncClipboardGetCallback* aCallback, int32_t aRetryCount,
700 mozilla::dom::WindowContext* aRequestingWindowContext) {
701 // Note we have to get the clipboard sequence number first before the actual
702 // read. This is to use it to verify the clipboard data is still the one we
703 // try to read, instead of the later state.
704 auto sequenceNumberOrError =
705 GetNativeClipboardSequenceNumber(aWhichClipboard);
706 if (sequenceNumberOrError.isErr()) {
707 MOZ_CLIPBOARD_LOG("%s: unable to get sequence number for clipboard %d.",
708 __FUNCTION__, aWhichClipboard);
709 aCallback->OnError(sequenceNumberOrError.unwrapErr());
710 return;
713 int32_t sequenceNumber = sequenceNumberOrError.unwrap();
714 AsyncHasNativeClipboardDataMatchingFlavors(
715 aFlavorList, aWhichClipboard,
716 [self = RefPtr{this}, callback = nsCOMPtr{aCallback}, aWhichClipboard,
717 aRetryCount, flavorList = aFlavorList.Clone(), sequenceNumber,
718 requestingWindowContext =
719 RefPtr{aRequestingWindowContext}](auto aFlavorsOrError) {
720 if (aFlavorsOrError.isErr()) {
721 MOZ_CLIPBOARD_LOG(
722 "%s: unable to get available flavors for clipboard %d.",
723 __FUNCTION__, aWhichClipboard);
724 callback->OnError(aFlavorsOrError.unwrapErr());
725 return;
728 auto sequenceNumberOrError =
729 self->GetNativeClipboardSequenceNumber(aWhichClipboard);
730 if (sequenceNumberOrError.isErr()) {
731 MOZ_CLIPBOARD_LOG(
732 "%s: unable to get sequence number for clipboard %d.",
733 __FUNCTION__, aWhichClipboard);
734 callback->OnError(sequenceNumberOrError.unwrapErr());
735 return;
738 if (sequenceNumber == sequenceNumberOrError.unwrap()) {
739 auto asyncGetClipboardData =
740 mozilla::MakeRefPtr<AsyncGetClipboardData>(
741 aWhichClipboard, sequenceNumber,
742 std::move(aFlavorsOrError.unwrap()), false, self,
743 requestingWindowContext);
744 callback->OnSuccess(asyncGetClipboardData);
745 return;
748 if (aRetryCount > 0) {
749 MOZ_CLIPBOARD_LOG(
750 "%s: clipboard=%d, ignore the data due to the sequence number "
751 "doesn't match, retry (%d) ..",
752 __FUNCTION__, aWhichClipboard, aRetryCount);
753 self->MaybeRetryGetAvailableFlavors(flavorList, aWhichClipboard,
754 callback, aRetryCount - 1,
755 requestingWindowContext);
756 return;
759 MOZ_DIAGNOSTIC_ASSERT(false, "How can this happen?!?");
760 callback->OnError(NS_ERROR_FAILURE);
764 NS_IMETHODIMP nsBaseClipboard::AsyncGetData(
765 const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
766 mozilla::dom::WindowContext* aRequestingWindowContext,
767 nsIPrincipal* aRequestingPrincipal,
768 nsIAsyncClipboardGetCallback* aCallback) {
769 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
771 if (!aCallback || !aRequestingPrincipal || aFlavorList.IsEmpty()) {
772 return NS_ERROR_INVALID_ARG;
775 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
776 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
777 aWhichClipboard);
778 return NS_ERROR_FAILURE;
781 // We want to disable security check for automated tests that have the pref
782 // set to true, or extension that have clipboard read permission.
783 if (mozilla::StaticPrefs::
784 dom_events_testing_asyncClipboard_DoNotUseDirectly() ||
785 nsContentUtils::PrincipalHasPermission(*aRequestingPrincipal,
786 nsGkAtoms::clipboardRead)) {
787 AsyncGetDataInternal(aFlavorList, aWhichClipboard, aRequestingWindowContext,
788 aCallback);
789 return NS_OK;
792 // If cache data is valid, we are the last ones to put something on the native
793 // clipboard, then check if the data is from the same-origin page,
794 if (auto* clipboardCache = GetClipboardCacheIfValid(aWhichClipboard)) {
795 nsCOMPtr<nsITransferable> trans = clipboardCache->GetTransferable();
796 MOZ_ASSERT(trans);
798 if (nsCOMPtr<nsIPrincipal> principal = trans->GetRequestingPrincipal()) {
799 if (aRequestingPrincipal->Subsumes(principal)) {
800 MOZ_CLIPBOARD_LOG("%s: native clipboard data is from same-origin page.",
801 __FUNCTION__);
802 AsyncGetDataInternal(aFlavorList, aWhichClipboard,
803 aRequestingWindowContext, aCallback);
804 return NS_OK;
809 // TODO: enable showing the "Paste" button in this case; see bug 1773681.
810 if (aRequestingPrincipal->GetIsAddonOrExpandedAddonPrincipal()) {
811 MOZ_CLIPBOARD_LOG("%s: Addon without read permission.", __FUNCTION__);
812 return aCallback->OnError(NS_ERROR_FAILURE);
815 RequestUserConfirmation(aWhichClipboard, aFlavorList,
816 aRequestingWindowContext, aRequestingPrincipal,
817 aCallback);
818 return NS_OK;
821 already_AddRefed<nsIAsyncGetClipboardData>
822 nsBaseClipboard::MaybeCreateGetRequestFromClipboardCache(
823 const nsTArray<nsCString>& aFlavorList, int32_t aClipboardType,
824 mozilla::dom::WindowContext* aRequestingWindowContext) {
825 MOZ_DIAGNOSTIC_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
827 if (!mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
828 return nullptr;
831 // If we were the last ones to put something on the native clipboard, then
832 // just use the cached transferable. Otherwise clear it because it isn't
833 // relevant any more.
834 ClipboardCache* clipboardCache = GetClipboardCacheIfValid(aClipboardType);
835 if (!clipboardCache) {
836 return nullptr;
839 nsITransferable* cachedTransferable = clipboardCache->GetTransferable();
840 MOZ_ASSERT(cachedTransferable);
842 nsTArray<nsCString> transferableFlavors;
843 if (NS_FAILED(cachedTransferable->FlavorsTransferableCanExport(
844 transferableFlavors))) {
845 return nullptr;
848 nsTArray<nsCString> results;
849 for (const auto& flavor : aFlavorList) {
850 for (const auto& transferableFlavor : transferableFlavors) {
851 // XXX We need special check for image as we always put the
852 // image as "native" on the clipboard.
853 if (transferableFlavor.Equals(flavor) ||
854 (transferableFlavor.Equals(kNativeImageMime) &&
855 nsContentUtils::IsFlavorImage(flavor))) {
856 MOZ_CLIPBOARD_LOG(" has %s", flavor.get());
857 results.AppendElement(flavor);
862 // XXX Do we need to check system clipboard for the flavors that cannot
863 // be found in cache?
864 return mozilla::MakeAndAddRef<AsyncGetClipboardData>(
865 aClipboardType, clipboardCache->GetSequenceNumber(), std::move(results),
866 true /* aFromCache */, this, aRequestingWindowContext);
869 void nsBaseClipboard::AsyncGetDataInternal(
870 const nsTArray<nsCString>& aFlavorList, int32_t aClipboardType,
871 mozilla::dom::WindowContext* aRequestingWindowContext,
872 nsIAsyncClipboardGetCallback* aCallback) {
873 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
875 if (nsCOMPtr<nsIAsyncGetClipboardData> asyncGetClipboardData =
876 MaybeCreateGetRequestFromClipboardCache(aFlavorList, aClipboardType,
877 aRequestingWindowContext)) {
878 aCallback->OnSuccess(asyncGetClipboardData);
879 return;
882 // At this point we can't satisfy the request from cache data so let's
883 // look for things other people put on the system clipboard.
884 MaybeRetryGetAvailableFlavors(aFlavorList, aClipboardType, aCallback,
885 kGetAvailableFlavorsRetryCount,
886 aRequestingWindowContext);
889 NS_IMETHODIMP nsBaseClipboard::GetDataSnapshotSync(
890 const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
891 mozilla::dom::WindowContext* aRequestingWindowContext,
892 nsIAsyncGetClipboardData** _retval) {
893 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
895 *_retval = nullptr;
897 if (aFlavorList.IsEmpty()) {
898 return NS_ERROR_INVALID_ARG;
901 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
902 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
903 aWhichClipboard);
904 return NS_ERROR_FAILURE;
907 if (nsCOMPtr<nsIAsyncGetClipboardData> asyncGetClipboardData =
908 MaybeCreateGetRequestFromClipboardCache(aFlavorList, aWhichClipboard,
909 aRequestingWindowContext)) {
910 asyncGetClipboardData.forget(_retval);
911 return NS_OK;
914 auto sequenceNumberOrError =
915 GetNativeClipboardSequenceNumber(aWhichClipboard);
916 if (sequenceNumberOrError.isErr()) {
917 MOZ_CLIPBOARD_LOG("%s: unable to get sequence number for clipboard %d.",
918 __FUNCTION__, aWhichClipboard);
919 return sequenceNumberOrError.unwrapErr();
922 nsTArray<nsCString> results;
923 for (const auto& flavor : aFlavorList) {
924 auto resultOrError = HasNativeClipboardDataMatchingFlavors(
925 AutoTArray<nsCString, 1>{flavor}, aWhichClipboard);
926 if (resultOrError.isOk() && resultOrError.unwrap()) {
927 results.AppendElement(flavor);
931 *_retval =
932 mozilla::MakeAndAddRef<AsyncGetClipboardData>(
933 aWhichClipboard, sequenceNumberOrError.unwrap(), std::move(results),
934 false /* aFromCache */, this, aRequestingWindowContext)
935 .take();
936 return NS_OK;
939 NS_IMETHODIMP nsBaseClipboard::EmptyClipboard(int32_t aWhichClipboard) {
940 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
942 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
943 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
944 aWhichClipboard);
945 return NS_ERROR_FAILURE;
948 EmptyNativeClipboardData(aWhichClipboard);
950 const auto& clipboardCache = mCaches[aWhichClipboard];
951 MOZ_ASSERT(clipboardCache);
953 if (mIgnoreEmptyNotification) {
954 MOZ_DIAGNOSTIC_ASSERT(!clipboardCache->GetTransferable() &&
955 !clipboardCache->GetClipboardOwner() &&
956 clipboardCache->GetSequenceNumber() == -1,
957 "How did we have data in clipboard cache here?");
958 return NS_OK;
961 clipboardCache->Clear();
963 return NS_OK;
966 mozilla::Result<nsTArray<nsCString>, nsresult>
967 nsBaseClipboard::GetFlavorsFromClipboardCache(int32_t aClipboardType) {
968 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
969 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
971 const auto* clipboardCache = GetClipboardCacheIfValid(aClipboardType);
972 if (!clipboardCache) {
973 return mozilla::Err(NS_ERROR_FAILURE);
976 nsITransferable* cachedTransferable = clipboardCache->GetTransferable();
977 MOZ_ASSERT(cachedTransferable);
979 nsTArray<nsCString> flavors;
980 nsresult rv = cachedTransferable->FlavorsTransferableCanExport(flavors);
981 if (NS_FAILED(rv)) {
982 return mozilla::Err(rv);
985 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
986 MOZ_CLIPBOARD_LOG(" Cached transferable types (nums %zu)\n",
987 flavors.Length());
988 for (const auto& flavor : flavors) {
989 MOZ_CLIPBOARD_LOG(" MIME %s", flavor.get());
993 return std::move(flavors);
996 NS_IMETHODIMP
997 nsBaseClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
998 int32_t aWhichClipboard,
999 bool* aOutResult) {
1000 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
1001 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
1002 MOZ_CLIPBOARD_LOG(" Asking for content clipboard=%i:\n",
1003 aWhichClipboard);
1004 for (const auto& flavor : aFlavorList) {
1005 MOZ_CLIPBOARD_LOG(" MIME %s", flavor.get());
1009 *aOutResult = false;
1011 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
1012 // First, check if we have valid data in our cached transferable.
1013 auto flavorsOrError = GetFlavorsFromClipboardCache(aWhichClipboard);
1014 if (flavorsOrError.isOk()) {
1015 for (const auto& transferableFlavor : flavorsOrError.unwrap()) {
1016 for (const auto& flavor : aFlavorList) {
1017 if (transferableFlavor.Equals(flavor)) {
1018 MOZ_CLIPBOARD_LOG(" has %s", flavor.get());
1019 *aOutResult = true;
1020 return NS_OK;
1027 auto resultOrError =
1028 HasNativeClipboardDataMatchingFlavors(aFlavorList, aWhichClipboard);
1029 if (resultOrError.isErr()) {
1030 MOZ_CLIPBOARD_LOG(
1031 "%s: checking native clipboard data matching flavors falied.",
1032 __FUNCTION__);
1033 return resultOrError.unwrapErr();
1036 *aOutResult = resultOrError.unwrap();
1037 return NS_OK;
1040 NS_IMETHODIMP
1041 nsBaseClipboard::IsClipboardTypeSupported(int32_t aWhichClipboard,
1042 bool* aRetval) {
1043 NS_ENSURE_ARG_POINTER(aRetval);
1044 switch (aWhichClipboard) {
1045 case kGlobalClipboard:
1046 // We always support the global clipboard.
1047 *aRetval = true;
1048 return NS_OK;
1049 case kSelectionClipboard:
1050 *aRetval = mClipboardCaps.supportsSelectionClipboard();
1051 return NS_OK;
1052 case kFindClipboard:
1053 *aRetval = mClipboardCaps.supportsFindClipboard();
1054 return NS_OK;
1055 case kSelectionCache:
1056 *aRetval = mClipboardCaps.supportsSelectionCache();
1057 return NS_OK;
1058 default:
1059 *aRetval = false;
1060 return NS_OK;
1064 void nsBaseClipboard::AsyncHasNativeClipboardDataMatchingFlavors(
1065 const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
1066 HasMatchingFlavorsCallback&& aCallback) {
1067 MOZ_DIAGNOSTIC_ASSERT(
1068 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
1070 MOZ_CLIPBOARD_LOG(
1071 "nsBaseClipboard::AsyncHasNativeClipboardDataMatchingFlavors: "
1072 "clipboard=%d",
1073 aWhichClipboard);
1075 nsTArray<nsCString> results;
1076 for (const auto& flavor : aFlavorList) {
1077 auto resultOrError = HasNativeClipboardDataMatchingFlavors(
1078 AutoTArray<nsCString, 1>{flavor}, aWhichClipboard);
1079 if (resultOrError.isOk() && resultOrError.unwrap()) {
1080 results.AppendElement(flavor);
1083 aCallback(std::move(results));
1086 void nsBaseClipboard::AsyncGetNativeClipboardData(
1087 nsITransferable* aTransferable, int32_t aWhichClipboard,
1088 GetDataCallback&& aCallback) {
1089 aCallback(GetNativeClipboardData(aTransferable, aWhichClipboard));
1092 void nsBaseClipboard::ClearClipboardCache(int32_t aClipboardType) {
1093 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
1094 const mozilla::UniquePtr<ClipboardCache>& cache = mCaches[aClipboardType];
1095 MOZ_ASSERT(cache);
1096 cache->Clear();
1099 void nsBaseClipboard::RequestUserConfirmation(
1100 int32_t aClipboardType, const nsTArray<nsCString>& aFlavorList,
1101 mozilla::dom::WindowContext* aWindowContext,
1102 nsIPrincipal* aRequestingPrincipal,
1103 nsIAsyncClipboardGetCallback* aCallback) {
1104 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
1105 MOZ_ASSERT(aCallback);
1107 if (!aWindowContext) {
1108 aCallback->OnError(NS_ERROR_FAILURE);
1109 return;
1112 CanonicalBrowsingContext* cbc =
1113 CanonicalBrowsingContext::Cast(aWindowContext->GetBrowsingContext());
1114 MOZ_ASSERT(
1115 cbc->IsContent(),
1116 "Should not require user confirmation when access from chrome window");
1118 RefPtr<CanonicalBrowsingContext> chromeTop = cbc->TopCrossChromeBoundary();
1119 Document* chromeDoc = chromeTop ? chromeTop->GetDocument() : nullptr;
1120 if (!chromeDoc || !chromeDoc->HasFocus(mozilla::IgnoreErrors())) {
1121 MOZ_CLIPBOARD_LOG("%s: reject due to not in the focused window",
1122 __FUNCTION__);
1123 aCallback->OnError(NS_ERROR_FAILURE);
1124 return;
1127 mozilla::dom::Element* activeElementInChromeDoc =
1128 chromeDoc->GetActiveElement();
1129 if (activeElementInChromeDoc != cbc->Top()->GetEmbedderElement()) {
1130 // Reject if the request is not from web content that is in the focused tab.
1131 MOZ_CLIPBOARD_LOG("%s: reject due to not in the focused tab", __FUNCTION__);
1132 aCallback->OnError(NS_ERROR_FAILURE);
1133 return;
1136 // If there is a pending user confirmation request, check if we could reuse
1137 // it. If not, reject the request.
1138 if (sUserConfirmationRequest) {
1139 if (sUserConfirmationRequest->IsEqual(
1140 aClipboardType, chromeDoc, aRequestingPrincipal, aWindowContext)) {
1141 sUserConfirmationRequest->AddClipboardGetRequest(aFlavorList, aCallback);
1142 return;
1145 aCallback->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR);
1146 return;
1149 nsresult rv = NS_ERROR_FAILURE;
1150 nsCOMPtr<nsIPromptService> promptService =
1151 do_GetService("@mozilla.org/prompter;1", &rv);
1152 if (NS_FAILED(rv)) {
1153 aCallback->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR);
1154 return;
1157 RefPtr<mozilla::dom::Promise> promise;
1158 if (NS_FAILED(promptService->ConfirmUserPaste(aWindowContext->Canonical(),
1159 getter_AddRefs(promise)))) {
1160 aCallback->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR);
1161 return;
1164 sUserConfirmationRequest = new UserConfirmationRequest(
1165 aClipboardType, chromeDoc, aRequestingPrincipal, this, aWindowContext);
1166 sUserConfirmationRequest->AddClipboardGetRequest(aFlavorList, aCallback);
1167 promise->AppendNativeHandler(sUserConfirmationRequest);
1170 NS_IMPL_ISUPPORTS(nsBaseClipboard::AsyncGetClipboardData,
1171 nsIAsyncGetClipboardData)
1173 nsBaseClipboard::AsyncGetClipboardData::AsyncGetClipboardData(
1174 int32_t aClipboardType, int32_t aSequenceNumber,
1175 nsTArray<nsCString>&& aFlavors, bool aFromCache,
1176 nsBaseClipboard* aClipboard,
1177 mozilla::dom::WindowContext* aRequestingWindowContext)
1178 : mClipboardType(aClipboardType),
1179 mSequenceNumber(aSequenceNumber),
1180 mFlavors(std::move(aFlavors)),
1181 mFromCache(aFromCache),
1182 mClipboard(aClipboard),
1183 mRequestingWindowContext(aRequestingWindowContext) {
1184 MOZ_ASSERT(mClipboard);
1185 MOZ_ASSERT(
1186 mClipboard->nsIClipboard::IsClipboardTypeSupported(mClipboardType));
1189 NS_IMETHODIMP nsBaseClipboard::AsyncGetClipboardData::GetValid(
1190 bool* aOutResult) {
1191 *aOutResult = IsValid();
1192 return NS_OK;
1195 NS_IMETHODIMP nsBaseClipboard::AsyncGetClipboardData::GetFlavorList(
1196 nsTArray<nsCString>& aFlavors) {
1197 aFlavors.AppendElements(mFlavors);
1198 return NS_OK;
1201 NS_IMETHODIMP nsBaseClipboard::AsyncGetClipboardData::GetData(
1202 nsITransferable* aTransferable,
1203 nsIAsyncClipboardRequestCallback* aCallback) {
1204 MOZ_CLIPBOARD_LOG("AsyncGetClipboardData::GetData: %p", this);
1206 if (!aTransferable || !aCallback) {
1207 return NS_ERROR_INVALID_ARG;
1210 nsTArray<nsCString> flavors;
1211 nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
1212 if (NS_FAILED(rv)) {
1213 return rv;
1216 // If the requested flavor is not in the list, throw an error.
1217 for (const auto& flavor : flavors) {
1218 if (!mFlavors.Contains(flavor)) {
1219 return NS_ERROR_FAILURE;
1223 if (!IsValid()) {
1224 aCallback->OnComplete(NS_ERROR_FAILURE);
1225 return NS_OK;
1228 MOZ_ASSERT(mClipboard);
1230 auto contentAnalysisCallback =
1231 mozilla::MakeRefPtr<SafeContentAnalysisResultCallback>(
1232 [transferable = nsCOMPtr{aTransferable},
1233 callback = nsCOMPtr{aCallback}](
1234 RefPtr<nsIContentAnalysisResult>&& aResult) {
1235 if (aResult->GetShouldAllowContent()) {
1236 callback->OnComplete(NS_OK);
1237 } else {
1238 transferable->ClearAllData();
1239 callback->OnComplete(NS_ERROR_CONTENT_BLOCKED);
1243 if (mFromCache) {
1244 const auto* clipboardCache =
1245 mClipboard->GetClipboardCacheIfValid(mClipboardType);
1246 // `IsValid()` above ensures we should get a valid cache and matched
1247 // sequence number here.
1248 MOZ_DIAGNOSTIC_ASSERT(clipboardCache);
1249 MOZ_DIAGNOSTIC_ASSERT(clipboardCache->GetSequenceNumber() ==
1250 mSequenceNumber);
1251 if (NS_SUCCEEDED(clipboardCache->GetData(aTransferable))) {
1252 CheckClipboardContentAnalysis(mRequestingWindowContext
1253 ? mRequestingWindowContext->Canonical()
1254 : nullptr,
1255 aTransferable, contentAnalysisCallback);
1256 return NS_OK;
1259 // At this point we can't satisfy the request from cache data so let's look
1260 // for things other people put on the system clipboard.
1263 // Since this is an async operation, we need to check if the data is still
1264 // valid after we get the result.
1265 mClipboard->AsyncGetNativeClipboardData(
1266 aTransferable, mClipboardType,
1267 [callback = nsCOMPtr{aCallback}, self = RefPtr{this},
1268 transferable = nsCOMPtr{aTransferable},
1269 contentAnalysisCallback =
1270 std::move(contentAnalysisCallback)](nsresult aResult) mutable {
1271 if (NS_FAILED(aResult)) {
1272 callback->OnComplete(aResult);
1273 return;
1275 // `IsValid()` checks the clipboard sequence number to ensure the data
1276 // we are requesting is still valid.
1277 if (!self->IsValid()) {
1278 callback->OnComplete(NS_ERROR_FAILURE);
1279 return;
1281 CheckClipboardContentAnalysis(
1282 self->mRequestingWindowContext
1283 ? self->mRequestingWindowContext->Canonical()
1284 : nullptr,
1285 transferable, contentAnalysisCallback);
1287 return NS_OK;
1290 bool nsBaseClipboard::AsyncGetClipboardData::IsValid() {
1291 if (!mClipboard) {
1292 return false;
1295 // If the data should from cache, check if cache is still valid or the
1296 // sequence numbers are matched.
1297 if (mFromCache) {
1298 const auto* clipboardCache =
1299 mClipboard->GetClipboardCacheIfValid(mClipboardType);
1300 if (!clipboardCache) {
1301 mClipboard = nullptr;
1302 return false;
1305 return mSequenceNumber == clipboardCache->GetSequenceNumber();
1308 auto resultOrError =
1309 mClipboard->GetNativeClipboardSequenceNumber(mClipboardType);
1310 if (resultOrError.isErr()) {
1311 mClipboard = nullptr;
1312 return false;
1315 if (mSequenceNumber != resultOrError.unwrap()) {
1316 mClipboard = nullptr;
1317 return false;
1320 return true;
1323 nsBaseClipboard::ClipboardCache* nsBaseClipboard::GetClipboardCacheIfValid(
1324 int32_t aClipboardType) {
1325 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
1327 const mozilla::UniquePtr<ClipboardCache>& cache = mCaches[aClipboardType];
1328 MOZ_ASSERT(cache);
1330 if (!cache->GetTransferable()) {
1331 MOZ_ASSERT(cache->GetSequenceNumber() == -1);
1332 return nullptr;
1335 auto changeCountOrError = GetNativeClipboardSequenceNumber(aClipboardType);
1336 if (changeCountOrError.isErr()) {
1337 return nullptr;
1340 if (changeCountOrError.unwrap() != cache->GetSequenceNumber()) {
1341 // Clipboard cache is invalid, clear it.
1342 cache->Clear();
1343 return nullptr;
1346 return cache.get();
1349 void nsBaseClipboard::ClipboardCache::Clear() {
1350 if (mClipboardOwner) {
1351 mClipboardOwner->LosingOwnership(mTransferable);
1352 mClipboardOwner = nullptr;
1354 mTransferable = nullptr;
1355 mSequenceNumber = -1;
1358 nsresult nsBaseClipboard::ClipboardCache::GetData(
1359 nsITransferable* aTransferable) const {
1360 MOZ_ASSERT(aTransferable);
1361 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
1363 // get flavor list that includes all acceptable flavors (including ones
1364 // obtained through conversion)
1365 nsTArray<nsCString> flavors;
1366 if (NS_FAILED(aTransferable->FlavorsTransferableCanImport(flavors))) {
1367 return NS_ERROR_FAILURE;
1370 MOZ_ASSERT(mTransferable);
1371 for (const auto& flavor : flavors) {
1372 nsCOMPtr<nsISupports> dataSupports;
1373 // XXX Maybe we need special check for image as we always put the image as
1374 // "native" on the clipboard.
1375 if (NS_SUCCEEDED(mTransferable->GetTransferData(
1376 flavor.get(), getter_AddRefs(dataSupports)))) {
1377 MOZ_CLIPBOARD_LOG("%s: getting %s from cache.", __FUNCTION__,
1378 flavor.get());
1379 aTransferable->SetTransferData(flavor.get(), dataSupports);
1380 // XXX we only read the first available type from native clipboard, so
1381 // make cache behave the same.
1382 return NS_OK;
1386 return NS_ERROR_FAILURE;