Backed out 7 changesets (bug 1881620) for causing frequent media crashes. CLOSED...
[gecko.git] / widget / nsBaseClipboard.cpp
blob4fdc1a2cd857ebaad60d0a229a26556a78d8822e
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::ERROR_OTHER));
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::ERROR_OTHER);
384 RefPtr<mozilla::dom::WindowGlobalParent> window =
385 mozilla::dom::WindowGlobalParent::GetByInnerWindowId(aInnerWindowId);
386 if (!window) {
387 // The window has gone away in the meantime
388 return mozilla::Err(NoContentAnalysisResult::ERROR_OTHER);
390 nsCOMPtr<nsIContentAnalysisRequest> contentAnalysisRequest =
391 new ContentAnalysisRequest(
392 nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry,
393 std::move(text), false, EmptyCString(), aDocumentURI,
394 nsIContentAnalysisRequest::OperationType::eClipboard, window);
395 nsresult rv = aContentAnalysis->AnalyzeContentRequestCallback(
396 contentAnalysisRequest, /* aAutoAcknowledge */ true, aResolver);
397 if (NS_FAILED(rv)) {
398 return mozilla::Err(NoContentAnalysisResult::ERROR_OTHER);
400 return true;
403 // Returning:
404 // - true means a content analysis request was fired
405 // - false means there is no file data in the transferable
406 // - NoContentAnalysisResult means there was an error
407 static mozilla::Result<bool, mozilla::contentanalysis::NoContentAnalysisResult>
408 CheckClipboardContentAnalysisAsFile(
409 uint64_t aInnerWindowId, SafeContentAnalysisResultCallback* aResolver,
410 nsIURI* aDocumentURI, nsIContentAnalysis* aContentAnalysis,
411 nsITransferable* aFileTrans) {
412 using namespace mozilla::contentanalysis;
414 nsCOMPtr<nsISupports> transferData;
415 nsresult rv =
416 aFileTrans->GetTransferData(kFileMime, getter_AddRefs(transferData));
417 nsString filePath;
418 if (NS_SUCCEEDED(rv)) {
419 if (nsCOMPtr<nsIFile> file = do_QueryInterface(transferData)) {
420 rv = file->GetPath(filePath);
421 } else {
422 MOZ_ASSERT_UNREACHABLE("clipboard data had kFileMime but no nsIFile!");
423 return mozilla::Err(NoContentAnalysisResult::ERROR_OTHER);
426 if (NS_FAILED(rv) || filePath.IsEmpty()) {
427 return false;
429 RefPtr<mozilla::dom::WindowGlobalParent> window =
430 mozilla::dom::WindowGlobalParent::GetByInnerWindowId(aInnerWindowId);
431 if (!window) {
432 // The window has gone away in the meantime
433 return mozilla::Err(NoContentAnalysisResult::ERROR_OTHER);
435 // Let the content analysis code calculate the digest
436 nsCOMPtr<nsIContentAnalysisRequest> contentAnalysisRequest =
437 new ContentAnalysisRequest(
438 nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry,
439 std::move(filePath), true, EmptyCString(), aDocumentURI,
440 nsIContentAnalysisRequest::OperationType::eCustomDisplayString,
441 window);
442 rv = aContentAnalysis->AnalyzeContentRequestCallback(
443 contentAnalysisRequest,
444 /* aAutoAcknowledge */ true, aResolver);
445 if (NS_FAILED(rv)) {
446 return mozilla::Err(NoContentAnalysisResult::ERROR_OTHER);
448 return true;
451 static void CheckClipboardContentAnalysis(
452 mozilla::dom::WindowGlobalParent* aWindow, nsITransferable* aTransferable,
453 SafeContentAnalysisResultCallback* aResolver) {
454 using namespace mozilla::contentanalysis;
456 // Content analysis is only needed if an outside webpage has access to
457 // the data. So, skip content analysis if there is:
458 // - no associated window (for example, scripted clipboard read by system
459 // code)
460 // - the window is a chrome docshell
461 // - the window is being rendered in the parent process (for example,
462 // about:support and the like)
463 if (!aWindow || aWindow->GetBrowsingContext()->IsChrome() ||
464 aWindow->IsInProcess()) {
465 aResolver->Callback(ContentAnalysisResult::FromNoResult(
466 NoContentAnalysisResult::CONTEXT_EXEMPT_FROM_CONTENT_ANALYSIS));
467 return;
469 nsCOMPtr<nsIContentAnalysis> contentAnalysis =
470 mozilla::components::nsIContentAnalysis::Service();
471 if (!contentAnalysis) {
472 aResolver->Callback(ContentAnalysisResult::FromNoResult(
473 NoContentAnalysisResult::ERROR_OTHER));
474 return;
477 bool contentAnalysisIsActive;
478 nsresult rv = contentAnalysis->GetIsActive(&contentAnalysisIsActive);
479 if (MOZ_LIKELY(NS_FAILED(rv) || !contentAnalysisIsActive)) {
480 aResolver->Callback(ContentAnalysisResult::FromNoResult(
481 NoContentAnalysisResult::CONTENT_ANALYSIS_NOT_ACTIVE));
482 return;
485 nsCOMPtr<nsIURI> currentURI = aWindow->Canonical()->GetDocumentURI();
486 uint64_t innerWindowId = aWindow->InnerWindowId();
487 nsTArray<nsCString> flavors;
488 rv = aTransferable->FlavorsTransferableCanExport(flavors);
489 if (NS_WARN_IF(NS_FAILED(rv))) {
490 aResolver->Callback(ContentAnalysisResult::FromNoResult(
491 NoContentAnalysisResult::ERROR_OTHER));
492 return;
494 bool keepChecking = true;
495 if (flavors.Contains(kFileMime)) {
496 auto fileResult = CheckClipboardContentAnalysisAsFile(
497 innerWindowId, aResolver, currentURI, contentAnalysis, aTransferable);
499 if (fileResult.isErr()) {
500 aResolver->Callback(
501 ContentAnalysisResult::FromNoResult(fileResult.unwrapErr()));
502 return;
504 keepChecking = !fileResult.unwrap();
506 if (keepChecking) {
507 // Failed to get the clipboard data as a file, so try as text
508 auto textResult = CheckClipboardContentAnalysisAsText(
509 innerWindowId, aResolver, currentURI, contentAnalysis, aTransferable);
510 if (textResult.isErr()) {
511 aResolver->Callback(
512 ContentAnalysisResult::FromNoResult(textResult.unwrapErr()));
513 return;
515 if (!textResult.unwrap()) {
516 // Couldn't get file or text data from this
517 aResolver->Callback(ContentAnalysisResult::FromNoResult(
518 NoContentAnalysisResult::ERROR_COULD_NOT_GET_DATA));
519 return;
524 static bool CheckClipboardContentAnalysisSync(
525 mozilla::dom::WindowGlobalParent* aWindow,
526 const nsCOMPtr<nsITransferable>& trans) {
527 bool requestDone = false;
528 RefPtr<nsIContentAnalysisResult> result;
529 auto callback = mozilla::MakeRefPtr<SafeContentAnalysisResultCallback>(
530 [&requestDone, &result](RefPtr<nsIContentAnalysisResult>&& aResult) {
531 result = std::move(aResult);
532 requestDone = true;
534 CheckClipboardContentAnalysis(aWindow, trans, callback);
535 mozilla::SpinEventLoopUntil("CheckClipboardContentAnalysisSync"_ns,
536 [&requestDone]() -> bool { return requestDone; });
537 return result->GetShouldAllowContent();
540 nsBaseClipboard::nsBaseClipboard(const ClipboardCapabilities& aClipboardCaps)
541 : mClipboardCaps(aClipboardCaps) {
542 using mozilla::MakeUnique;
543 // Initialize clipboard cache.
544 mCaches[kGlobalClipboard] = MakeUnique<ClipboardCache>();
545 if (mClipboardCaps.supportsSelectionClipboard()) {
546 mCaches[kSelectionClipboard] = MakeUnique<ClipboardCache>();
548 if (mClipboardCaps.supportsFindClipboard()) {
549 mCaches[kFindClipboard] = MakeUnique<ClipboardCache>();
551 if (mClipboardCaps.supportsSelectionCache()) {
552 mCaches[kSelectionCache] = MakeUnique<ClipboardCache>();
556 nsBaseClipboard::~nsBaseClipboard() {
557 for (auto& request : mPendingWriteRequests) {
558 if (request) {
559 request->Abort(NS_ERROR_ABORT);
560 request = nullptr;
565 NS_IMPL_ISUPPORTS(nsBaseClipboard, nsIClipboard)
568 * Sets the transferable object
571 NS_IMETHODIMP nsBaseClipboard::SetData(nsITransferable* aTransferable,
572 nsIClipboardOwner* aOwner,
573 int32_t aWhichClipboard) {
574 NS_ASSERTION(aTransferable, "clipboard given a null transferable");
576 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
578 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
579 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
580 aWhichClipboard);
581 return NS_ERROR_FAILURE;
584 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
585 nsTArray<nsCString> flavors;
586 if (NS_SUCCEEDED(aTransferable->FlavorsTransferableCanImport(flavors))) {
587 for (const auto& flavor : flavors) {
588 MOZ_CLIPBOARD_LOG(" MIME %s", flavor.get());
593 const auto& clipboardCache = mCaches[aWhichClipboard];
594 MOZ_ASSERT(clipboardCache);
595 if (aTransferable == clipboardCache->GetTransferable() &&
596 aOwner == clipboardCache->GetClipboardOwner()) {
597 MOZ_CLIPBOARD_LOG("%s: skipping update.", __FUNCTION__);
598 return NS_OK;
601 clipboardCache->Clear();
603 nsresult rv = NS_ERROR_FAILURE;
604 if (aTransferable) {
605 mIgnoreEmptyNotification = true;
606 // Reject existing pending asyncSetData request if any.
607 RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard);
608 rv = SetNativeClipboardData(aTransferable, aWhichClipboard);
609 mIgnoreEmptyNotification = false;
611 if (NS_FAILED(rv)) {
612 MOZ_CLIPBOARD_LOG("%s: setting native clipboard data failed.",
613 __FUNCTION__);
614 return rv;
617 auto result = GetNativeClipboardSequenceNumber(aWhichClipboard);
618 if (result.isErr()) {
619 MOZ_CLIPBOARD_LOG("%s: getting native clipboard change count failed.",
620 __FUNCTION__);
621 return result.unwrapErr();
624 clipboardCache->Update(aTransferable, aOwner, result.unwrap());
625 return NS_OK;
628 nsresult nsBaseClipboard::GetDataFromClipboardCache(
629 nsITransferable* aTransferable, int32_t aClipboardType) {
630 MOZ_ASSERT(aTransferable);
631 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
632 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
634 const auto* clipboardCache = GetClipboardCacheIfValid(aClipboardType);
635 if (!clipboardCache) {
636 return NS_ERROR_FAILURE;
639 return clipboardCache->GetData(aTransferable);
643 * Gets the transferable object from system clipboard.
645 NS_IMETHODIMP nsBaseClipboard::GetData(
646 nsITransferable* aTransferable, int32_t aWhichClipboard,
647 mozilla::dom::WindowContext* aWindowContext) {
648 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
650 if (!aTransferable) {
651 NS_ASSERTION(false, "clipboard given a null transferable");
652 return NS_ERROR_FAILURE;
655 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
656 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
657 aWhichClipboard);
658 return NS_ERROR_FAILURE;
661 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
662 // If we were the last ones to put something on the native clipboard, then
663 // just use the cached transferable. Otherwise clear it because it isn't
664 // relevant any more.
665 if (NS_SUCCEEDED(
666 GetDataFromClipboardCache(aTransferable, aWhichClipboard))) {
667 // maybe try to fill in more types? Is there a point?
668 if (!CheckClipboardContentAnalysisSync(aWindowContext->Canonical(),
669 aTransferable)) {
670 aTransferable->ClearAllData();
671 return NS_ERROR_CONTENT_BLOCKED;
673 return NS_OK;
676 // at this point we can't satisfy the request from cache data so let's look
677 // for things other people put on the system clipboard
679 nsresult rv = GetNativeClipboardData(aTransferable, aWhichClipboard);
680 if (NS_FAILED(rv)) {
681 return rv;
683 if (!CheckClipboardContentAnalysisSync(aWindowContext->Canonical(),
684 aTransferable)) {
685 aTransferable->ClearAllData();
686 return NS_ERROR_CONTENT_BLOCKED;
688 return NS_OK;
691 void nsBaseClipboard::MaybeRetryGetAvailableFlavors(
692 const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
693 nsIAsyncClipboardGetCallback* aCallback, int32_t aRetryCount,
694 mozilla::dom::WindowContext* aRequestingWindowContext) {
695 // Note we have to get the clipboard sequence number first before the actual
696 // read. This is to use it to verify the clipboard data is still the one we
697 // try to read, instead of the later state.
698 auto sequenceNumberOrError =
699 GetNativeClipboardSequenceNumber(aWhichClipboard);
700 if (sequenceNumberOrError.isErr()) {
701 MOZ_CLIPBOARD_LOG("%s: unable to get sequence number for clipboard %d.",
702 __FUNCTION__, aWhichClipboard);
703 aCallback->OnError(sequenceNumberOrError.unwrapErr());
704 return;
707 int32_t sequenceNumber = sequenceNumberOrError.unwrap();
708 AsyncHasNativeClipboardDataMatchingFlavors(
709 aFlavorList, aWhichClipboard,
710 [self = RefPtr{this}, callback = nsCOMPtr{aCallback}, aWhichClipboard,
711 aRetryCount, flavorList = aFlavorList.Clone(), sequenceNumber,
712 requestingWindowContext =
713 RefPtr{aRequestingWindowContext}](auto aFlavorsOrError) {
714 if (aFlavorsOrError.isErr()) {
715 MOZ_CLIPBOARD_LOG(
716 "%s: unable to get available flavors for clipboard %d.",
717 __FUNCTION__, aWhichClipboard);
718 callback->OnError(aFlavorsOrError.unwrapErr());
719 return;
722 auto sequenceNumberOrError =
723 self->GetNativeClipboardSequenceNumber(aWhichClipboard);
724 if (sequenceNumberOrError.isErr()) {
725 MOZ_CLIPBOARD_LOG(
726 "%s: unable to get sequence number for clipboard %d.",
727 __FUNCTION__, aWhichClipboard);
728 callback->OnError(sequenceNumberOrError.unwrapErr());
729 return;
732 if (sequenceNumber == sequenceNumberOrError.unwrap()) {
733 auto asyncGetClipboardData =
734 mozilla::MakeRefPtr<AsyncGetClipboardData>(
735 aWhichClipboard, sequenceNumber,
736 std::move(aFlavorsOrError.unwrap()), false, self,
737 requestingWindowContext);
738 callback->OnSuccess(asyncGetClipboardData);
739 return;
742 if (aRetryCount > 0) {
743 MOZ_CLIPBOARD_LOG(
744 "%s: clipboard=%d, ignore the data due to the sequence number "
745 "doesn't match, retry (%d) ..",
746 __FUNCTION__, aWhichClipboard, aRetryCount);
747 self->MaybeRetryGetAvailableFlavors(flavorList, aWhichClipboard,
748 callback, aRetryCount - 1,
749 requestingWindowContext);
750 return;
753 MOZ_DIAGNOSTIC_ASSERT(false, "How can this happen?!?");
754 callback->OnError(NS_ERROR_FAILURE);
758 NS_IMETHODIMP nsBaseClipboard::AsyncGetData(
759 const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
760 mozilla::dom::WindowContext* aRequestingWindowContext,
761 nsIPrincipal* aRequestingPrincipal,
762 nsIAsyncClipboardGetCallback* aCallback) {
763 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
765 if (!aCallback || !aRequestingPrincipal || aFlavorList.IsEmpty()) {
766 return NS_ERROR_INVALID_ARG;
769 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
770 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
771 aWhichClipboard);
772 return NS_ERROR_FAILURE;
775 // We want to disable security check for automated tests that have the pref
776 // set to true, or extension that have clipboard read permission.
777 if (mozilla::StaticPrefs::
778 dom_events_testing_asyncClipboard_DoNotUseDirectly() ||
779 nsContentUtils::PrincipalHasPermission(*aRequestingPrincipal,
780 nsGkAtoms::clipboardRead)) {
781 AsyncGetDataInternal(aFlavorList, aWhichClipboard, aRequestingWindowContext,
782 aCallback);
783 return NS_OK;
786 // If cache data is valid, we are the last ones to put something on the native
787 // clipboard, then check if the data is from the same-origin page,
788 if (auto* clipboardCache = GetClipboardCacheIfValid(aWhichClipboard)) {
789 nsCOMPtr<nsITransferable> trans = clipboardCache->GetTransferable();
790 MOZ_ASSERT(trans);
792 if (nsCOMPtr<nsIPrincipal> principal = trans->GetRequestingPrincipal()) {
793 if (aRequestingPrincipal->Subsumes(principal)) {
794 MOZ_CLIPBOARD_LOG("%s: native clipboard data is from same-origin page.",
795 __FUNCTION__);
796 AsyncGetDataInternal(aFlavorList, aWhichClipboard,
797 aRequestingWindowContext, aCallback);
798 return NS_OK;
803 // TODO: enable showing the "Paste" button in this case; see bug 1773681.
804 if (aRequestingPrincipal->GetIsAddonOrExpandedAddonPrincipal()) {
805 MOZ_CLIPBOARD_LOG("%s: Addon without read permission.", __FUNCTION__);
806 return aCallback->OnError(NS_ERROR_FAILURE);
809 RequestUserConfirmation(aWhichClipboard, aFlavorList,
810 aRequestingWindowContext, aRequestingPrincipal,
811 aCallback);
812 return NS_OK;
815 already_AddRefed<nsIAsyncGetClipboardData>
816 nsBaseClipboard::MaybeCreateGetRequestFromClipboardCache(
817 const nsTArray<nsCString>& aFlavorList, int32_t aClipboardType,
818 mozilla::dom::WindowContext* aRequestingWindowContext) {
819 MOZ_DIAGNOSTIC_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
821 if (!mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
822 return nullptr;
825 // If we were the last ones to put something on the native clipboard, then
826 // just use the cached transferable. Otherwise clear it because it isn't
827 // relevant any more.
828 ClipboardCache* clipboardCache = GetClipboardCacheIfValid(aClipboardType);
829 if (!clipboardCache) {
830 return nullptr;
833 nsITransferable* cachedTransferable = clipboardCache->GetTransferable();
834 MOZ_ASSERT(cachedTransferable);
836 nsTArray<nsCString> transferableFlavors;
837 if (NS_FAILED(cachedTransferable->FlavorsTransferableCanExport(
838 transferableFlavors))) {
839 return nullptr;
842 nsTArray<nsCString> results;
843 for (const auto& transferableFlavor : transferableFlavors) {
844 for (const auto& flavor : aFlavorList) {
845 // XXX We need special check for image as we always put the
846 // image as "native" on the clipboard.
847 if (transferableFlavor.Equals(flavor) ||
848 (transferableFlavor.Equals(kNativeImageMime) &&
849 nsContentUtils::IsFlavorImage(flavor))) {
850 MOZ_CLIPBOARD_LOG(" has %s", flavor.get());
851 results.AppendElement(flavor);
856 // XXX Do we need to check system clipboard for the flavors that cannot
857 // be found in cache?
858 return mozilla::MakeAndAddRef<AsyncGetClipboardData>(
859 aClipboardType, clipboardCache->GetSequenceNumber(), std::move(results),
860 true /* aFromCache */, this, aRequestingWindowContext);
863 void nsBaseClipboard::AsyncGetDataInternal(
864 const nsTArray<nsCString>& aFlavorList, int32_t aClipboardType,
865 mozilla::dom::WindowContext* aRequestingWindowContext,
866 nsIAsyncClipboardGetCallback* aCallback) {
867 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
869 if (nsCOMPtr<nsIAsyncGetClipboardData> asyncGetClipboardData =
870 MaybeCreateGetRequestFromClipboardCache(aFlavorList, aClipboardType,
871 aRequestingWindowContext)) {
872 aCallback->OnSuccess(asyncGetClipboardData);
873 return;
876 // At this point we can't satisfy the request from cache data so let's
877 // look for things other people put on the system clipboard.
878 MaybeRetryGetAvailableFlavors(aFlavorList, aClipboardType, aCallback,
879 kGetAvailableFlavorsRetryCount,
880 aRequestingWindowContext);
883 NS_IMETHODIMP nsBaseClipboard::GetDataSnapshotSync(
884 const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
885 mozilla::dom::WindowContext* aRequestingWindowContext,
886 nsIAsyncGetClipboardData** _retval) {
887 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
889 *_retval = nullptr;
891 if (aFlavorList.IsEmpty()) {
892 return NS_ERROR_INVALID_ARG;
895 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
896 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
897 aWhichClipboard);
898 return NS_ERROR_FAILURE;
901 if (nsCOMPtr<nsIAsyncGetClipboardData> asyncGetClipboardData =
902 MaybeCreateGetRequestFromClipboardCache(aFlavorList, aWhichClipboard,
903 aRequestingWindowContext)) {
904 asyncGetClipboardData.forget(_retval);
905 return NS_OK;
908 auto sequenceNumberOrError =
909 GetNativeClipboardSequenceNumber(aWhichClipboard);
910 if (sequenceNumberOrError.isErr()) {
911 MOZ_CLIPBOARD_LOG("%s: unable to get sequence number for clipboard %d.",
912 __FUNCTION__, aWhichClipboard);
913 return sequenceNumberOrError.unwrapErr();
916 nsTArray<nsCString> results;
917 for (const auto& flavor : aFlavorList) {
918 auto resultOrError = HasNativeClipboardDataMatchingFlavors(
919 AutoTArray<nsCString, 1>{flavor}, aWhichClipboard);
920 if (resultOrError.isOk() && resultOrError.unwrap()) {
921 results.AppendElement(flavor);
925 *_retval =
926 mozilla::MakeAndAddRef<AsyncGetClipboardData>(
927 aWhichClipboard, sequenceNumberOrError.unwrap(), std::move(results),
928 false /* aFromCache */, this, aRequestingWindowContext)
929 .take();
930 return NS_OK;
933 NS_IMETHODIMP nsBaseClipboard::EmptyClipboard(int32_t aWhichClipboard) {
934 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
936 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
937 MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
938 aWhichClipboard);
939 return NS_ERROR_FAILURE;
942 EmptyNativeClipboardData(aWhichClipboard);
944 const auto& clipboardCache = mCaches[aWhichClipboard];
945 MOZ_ASSERT(clipboardCache);
947 if (mIgnoreEmptyNotification) {
948 MOZ_DIAGNOSTIC_ASSERT(!clipboardCache->GetTransferable() &&
949 !clipboardCache->GetClipboardOwner() &&
950 clipboardCache->GetSequenceNumber() == -1,
951 "How did we have data in clipboard cache here?");
952 return NS_OK;
955 clipboardCache->Clear();
957 return NS_OK;
960 mozilla::Result<nsTArray<nsCString>, nsresult>
961 nsBaseClipboard::GetFlavorsFromClipboardCache(int32_t aClipboardType) {
962 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
963 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
965 const auto* clipboardCache = GetClipboardCacheIfValid(aClipboardType);
966 if (!clipboardCache) {
967 return mozilla::Err(NS_ERROR_FAILURE);
970 nsITransferable* cachedTransferable = clipboardCache->GetTransferable();
971 MOZ_ASSERT(cachedTransferable);
973 nsTArray<nsCString> flavors;
974 nsresult rv = cachedTransferable->FlavorsTransferableCanExport(flavors);
975 if (NS_FAILED(rv)) {
976 return mozilla::Err(rv);
979 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
980 MOZ_CLIPBOARD_LOG(" Cached transferable types (nums %zu)\n",
981 flavors.Length());
982 for (const auto& flavor : flavors) {
983 MOZ_CLIPBOARD_LOG(" MIME %s", flavor.get());
987 return std::move(flavors);
990 NS_IMETHODIMP
991 nsBaseClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
992 int32_t aWhichClipboard,
993 bool* aOutResult) {
994 MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);
995 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
996 MOZ_CLIPBOARD_LOG(" Asking for content clipboard=%i:\n",
997 aWhichClipboard);
998 for (const auto& flavor : aFlavorList) {
999 MOZ_CLIPBOARD_LOG(" MIME %s", flavor.get());
1003 *aOutResult = false;
1005 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
1006 // First, check if we have valid data in our cached transferable.
1007 auto flavorsOrError = GetFlavorsFromClipboardCache(aWhichClipboard);
1008 if (flavorsOrError.isOk()) {
1009 for (const auto& transferableFlavor : flavorsOrError.unwrap()) {
1010 for (const auto& flavor : aFlavorList) {
1011 if (transferableFlavor.Equals(flavor)) {
1012 MOZ_CLIPBOARD_LOG(" has %s", flavor.get());
1013 *aOutResult = true;
1014 return NS_OK;
1021 auto resultOrError =
1022 HasNativeClipboardDataMatchingFlavors(aFlavorList, aWhichClipboard);
1023 if (resultOrError.isErr()) {
1024 MOZ_CLIPBOARD_LOG(
1025 "%s: checking native clipboard data matching flavors falied.",
1026 __FUNCTION__);
1027 return resultOrError.unwrapErr();
1030 *aOutResult = resultOrError.unwrap();
1031 return NS_OK;
1034 NS_IMETHODIMP
1035 nsBaseClipboard::IsClipboardTypeSupported(int32_t aWhichClipboard,
1036 bool* aRetval) {
1037 NS_ENSURE_ARG_POINTER(aRetval);
1038 switch (aWhichClipboard) {
1039 case kGlobalClipboard:
1040 // We always support the global clipboard.
1041 *aRetval = true;
1042 return NS_OK;
1043 case kSelectionClipboard:
1044 *aRetval = mClipboardCaps.supportsSelectionClipboard();
1045 return NS_OK;
1046 case kFindClipboard:
1047 *aRetval = mClipboardCaps.supportsFindClipboard();
1048 return NS_OK;
1049 case kSelectionCache:
1050 *aRetval = mClipboardCaps.supportsSelectionCache();
1051 return NS_OK;
1052 default:
1053 *aRetval = false;
1054 return NS_OK;
1058 void nsBaseClipboard::AsyncHasNativeClipboardDataMatchingFlavors(
1059 const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
1060 HasMatchingFlavorsCallback&& aCallback) {
1061 MOZ_DIAGNOSTIC_ASSERT(
1062 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
1064 MOZ_CLIPBOARD_LOG(
1065 "nsBaseClipboard::AsyncHasNativeClipboardDataMatchingFlavors: "
1066 "clipboard=%d",
1067 aWhichClipboard);
1069 nsTArray<nsCString> results;
1070 for (const auto& flavor : aFlavorList) {
1071 auto resultOrError = HasNativeClipboardDataMatchingFlavors(
1072 AutoTArray<nsCString, 1>{flavor}, aWhichClipboard);
1073 if (resultOrError.isOk() && resultOrError.unwrap()) {
1074 results.AppendElement(flavor);
1077 aCallback(std::move(results));
1080 void nsBaseClipboard::AsyncGetNativeClipboardData(
1081 nsITransferable* aTransferable, int32_t aWhichClipboard,
1082 GetDataCallback&& aCallback) {
1083 aCallback(GetNativeClipboardData(aTransferable, aWhichClipboard));
1086 void nsBaseClipboard::ClearClipboardCache(int32_t aClipboardType) {
1087 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
1088 const mozilla::UniquePtr<ClipboardCache>& cache = mCaches[aClipboardType];
1089 MOZ_ASSERT(cache);
1090 cache->Clear();
1093 void nsBaseClipboard::RequestUserConfirmation(
1094 int32_t aClipboardType, const nsTArray<nsCString>& aFlavorList,
1095 mozilla::dom::WindowContext* aWindowContext,
1096 nsIPrincipal* aRequestingPrincipal,
1097 nsIAsyncClipboardGetCallback* aCallback) {
1098 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
1099 MOZ_ASSERT(aCallback);
1101 if (!aWindowContext) {
1102 aCallback->OnError(NS_ERROR_FAILURE);
1103 return;
1106 CanonicalBrowsingContext* cbc =
1107 CanonicalBrowsingContext::Cast(aWindowContext->GetBrowsingContext());
1108 MOZ_ASSERT(
1109 cbc->IsContent(),
1110 "Should not require user confirmation when access from chrome window");
1112 RefPtr<CanonicalBrowsingContext> chromeTop = cbc->TopCrossChromeBoundary();
1113 Document* chromeDoc = chromeTop ? chromeTop->GetDocument() : nullptr;
1114 if (!chromeDoc || !chromeDoc->HasFocus(mozilla::IgnoreErrors())) {
1115 MOZ_CLIPBOARD_LOG("%s: reject due to not in the focused window",
1116 __FUNCTION__);
1117 aCallback->OnError(NS_ERROR_FAILURE);
1118 return;
1121 mozilla::dom::Element* activeElementInChromeDoc =
1122 chromeDoc->GetActiveElement();
1123 if (activeElementInChromeDoc != cbc->Top()->GetEmbedderElement()) {
1124 // Reject if the request is not from web content that is in the focused tab.
1125 MOZ_CLIPBOARD_LOG("%s: reject due to not in the focused tab", __FUNCTION__);
1126 aCallback->OnError(NS_ERROR_FAILURE);
1127 return;
1130 // If there is a pending user confirmation request, check if we could reuse
1131 // it. If not, reject the request.
1132 if (sUserConfirmationRequest) {
1133 if (sUserConfirmationRequest->IsEqual(
1134 aClipboardType, chromeDoc, aRequestingPrincipal, aWindowContext)) {
1135 sUserConfirmationRequest->AddClipboardGetRequest(aFlavorList, aCallback);
1136 return;
1139 aCallback->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR);
1140 return;
1143 nsresult rv = NS_ERROR_FAILURE;
1144 nsCOMPtr<nsIPromptService> promptService =
1145 do_GetService("@mozilla.org/prompter;1", &rv);
1146 if (NS_FAILED(rv)) {
1147 aCallback->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR);
1148 return;
1151 RefPtr<mozilla::dom::Promise> promise;
1152 if (NS_FAILED(promptService->ConfirmUserPaste(aWindowContext->Canonical(),
1153 getter_AddRefs(promise)))) {
1154 aCallback->OnError(NS_ERROR_DOM_NOT_ALLOWED_ERR);
1155 return;
1158 sUserConfirmationRequest = new UserConfirmationRequest(
1159 aClipboardType, chromeDoc, aRequestingPrincipal, this, aWindowContext);
1160 sUserConfirmationRequest->AddClipboardGetRequest(aFlavorList, aCallback);
1161 promise->AppendNativeHandler(sUserConfirmationRequest);
1164 NS_IMPL_ISUPPORTS(nsBaseClipboard::AsyncGetClipboardData,
1165 nsIAsyncGetClipboardData)
1167 nsBaseClipboard::AsyncGetClipboardData::AsyncGetClipboardData(
1168 int32_t aClipboardType, int32_t aSequenceNumber,
1169 nsTArray<nsCString>&& aFlavors, bool aFromCache,
1170 nsBaseClipboard* aClipboard,
1171 mozilla::dom::WindowContext* aRequestingWindowContext)
1172 : mClipboardType(aClipboardType),
1173 mSequenceNumber(aSequenceNumber),
1174 mFlavors(std::move(aFlavors)),
1175 mFromCache(aFromCache),
1176 mClipboard(aClipboard),
1177 mRequestingWindowContext(aRequestingWindowContext) {
1178 MOZ_ASSERT(mClipboard);
1179 MOZ_ASSERT(
1180 mClipboard->nsIClipboard::IsClipboardTypeSupported(mClipboardType));
1183 NS_IMETHODIMP nsBaseClipboard::AsyncGetClipboardData::GetValid(
1184 bool* aOutResult) {
1185 *aOutResult = IsValid();
1186 return NS_OK;
1189 NS_IMETHODIMP nsBaseClipboard::AsyncGetClipboardData::GetFlavorList(
1190 nsTArray<nsCString>& aFlavors) {
1191 aFlavors.AppendElements(mFlavors);
1192 return NS_OK;
1195 NS_IMETHODIMP nsBaseClipboard::AsyncGetClipboardData::GetData(
1196 nsITransferable* aTransferable,
1197 nsIAsyncClipboardRequestCallback* aCallback) {
1198 MOZ_CLIPBOARD_LOG("AsyncGetClipboardData::GetData: %p", this);
1200 if (!aTransferable || !aCallback) {
1201 return NS_ERROR_INVALID_ARG;
1204 nsTArray<nsCString> flavors;
1205 nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
1206 if (NS_FAILED(rv)) {
1207 return rv;
1210 // If the requested flavor is not in the list, throw an error.
1211 for (const auto& flavor : flavors) {
1212 if (!mFlavors.Contains(flavor)) {
1213 return NS_ERROR_FAILURE;
1217 if (!IsValid()) {
1218 aCallback->OnComplete(NS_ERROR_FAILURE);
1219 return NS_OK;
1222 MOZ_ASSERT(mClipboard);
1224 auto contentAnalysisCallback =
1225 mozilla::MakeRefPtr<SafeContentAnalysisResultCallback>(
1226 [transferable = nsCOMPtr{aTransferable},
1227 callback = nsCOMPtr{aCallback}](
1228 RefPtr<nsIContentAnalysisResult>&& aResult) {
1229 if (aResult->GetShouldAllowContent()) {
1230 callback->OnComplete(NS_OK);
1231 } else {
1232 transferable->ClearAllData();
1233 callback->OnComplete(NS_ERROR_CONTENT_BLOCKED);
1237 if (mFromCache) {
1238 const auto* clipboardCache =
1239 mClipboard->GetClipboardCacheIfValid(mClipboardType);
1240 // `IsValid()` above ensures we should get a valid cache and matched
1241 // sequence number here.
1242 MOZ_DIAGNOSTIC_ASSERT(clipboardCache);
1243 MOZ_DIAGNOSTIC_ASSERT(clipboardCache->GetSequenceNumber() ==
1244 mSequenceNumber);
1245 if (NS_SUCCEEDED(clipboardCache->GetData(aTransferable))) {
1246 CheckClipboardContentAnalysis(mRequestingWindowContext
1247 ? mRequestingWindowContext->Canonical()
1248 : nullptr,
1249 aTransferable, contentAnalysisCallback);
1250 return NS_OK;
1253 // At this point we can't satisfy the request from cache data so let's look
1254 // for things other people put on the system clipboard.
1257 // Since this is an async operation, we need to check if the data is still
1258 // valid after we get the result.
1259 mClipboard->AsyncGetNativeClipboardData(
1260 aTransferable, mClipboardType,
1261 [callback = nsCOMPtr{aCallback}, self = RefPtr{this},
1262 transferable = nsCOMPtr{aTransferable},
1263 contentAnalysisCallback =
1264 std::move(contentAnalysisCallback)](nsresult aResult) mutable {
1265 if (NS_FAILED(aResult)) {
1266 callback->OnComplete(aResult);
1267 return;
1269 // `IsValid()` checks the clipboard sequence number to ensure the data
1270 // we are requesting is still valid.
1271 if (!self->IsValid()) {
1272 callback->OnComplete(NS_ERROR_FAILURE);
1273 return;
1275 CheckClipboardContentAnalysis(
1276 self->mRequestingWindowContext
1277 ? self->mRequestingWindowContext->Canonical()
1278 : nullptr,
1279 transferable, contentAnalysisCallback);
1281 return NS_OK;
1284 bool nsBaseClipboard::AsyncGetClipboardData::IsValid() {
1285 if (!mClipboard) {
1286 return false;
1289 // If the data should from cache, check if cache is still valid or the
1290 // sequence numbers are matched.
1291 if (mFromCache) {
1292 const auto* clipboardCache =
1293 mClipboard->GetClipboardCacheIfValid(mClipboardType);
1294 if (!clipboardCache) {
1295 mClipboard = nullptr;
1296 return false;
1299 return mSequenceNumber == clipboardCache->GetSequenceNumber();
1302 auto resultOrError =
1303 mClipboard->GetNativeClipboardSequenceNumber(mClipboardType);
1304 if (resultOrError.isErr()) {
1305 mClipboard = nullptr;
1306 return false;
1309 if (mSequenceNumber != resultOrError.unwrap()) {
1310 mClipboard = nullptr;
1311 return false;
1314 return true;
1317 nsBaseClipboard::ClipboardCache* nsBaseClipboard::GetClipboardCacheIfValid(
1318 int32_t aClipboardType) {
1319 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
1321 const mozilla::UniquePtr<ClipboardCache>& cache = mCaches[aClipboardType];
1322 MOZ_ASSERT(cache);
1324 if (!cache->GetTransferable()) {
1325 MOZ_ASSERT(cache->GetSequenceNumber() == -1);
1326 return nullptr;
1329 auto changeCountOrError = GetNativeClipboardSequenceNumber(aClipboardType);
1330 if (changeCountOrError.isErr()) {
1331 return nullptr;
1334 if (changeCountOrError.unwrap() != cache->GetSequenceNumber()) {
1335 // Clipboard cache is invalid, clear it.
1336 cache->Clear();
1337 return nullptr;
1340 return cache.get();
1343 void nsBaseClipboard::ClipboardCache::Clear() {
1344 if (mClipboardOwner) {
1345 mClipboardOwner->LosingOwnership(mTransferable);
1346 mClipboardOwner = nullptr;
1348 mTransferable = nullptr;
1349 mSequenceNumber = -1;
1352 nsresult nsBaseClipboard::ClipboardCache::GetData(
1353 nsITransferable* aTransferable) const {
1354 MOZ_ASSERT(aTransferable);
1355 MOZ_ASSERT(mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled());
1357 // get flavor list that includes all acceptable flavors (including ones
1358 // obtained through conversion)
1359 nsTArray<nsCString> flavors;
1360 if (NS_FAILED(aTransferable->FlavorsTransferableCanImport(flavors))) {
1361 return NS_ERROR_FAILURE;
1364 MOZ_ASSERT(mTransferable);
1365 for (const auto& flavor : flavors) {
1366 nsCOMPtr<nsISupports> dataSupports;
1367 // XXX Maybe we need special check for image as we always put the image as
1368 // "native" on the clipboard.
1369 if (NS_SUCCEEDED(mTransferable->GetTransferData(
1370 flavor.get(), getter_AddRefs(dataSupports)))) {
1371 MOZ_CLIPBOARD_LOG("%s: getting %s from cache.", __FUNCTION__,
1372 flavor.get());
1373 aTransferable->SetTransferData(flavor.get(), dataSupports);
1374 // XXX we only read the first available type from native clipboard, so
1375 // make cache behave the same.
1376 return NS_OK;
1380 return NS_ERROR_FAILURE;