1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/Clipboard.h"
11 #include "mozilla/AbstractThread.h"
12 #include "mozilla/BasePrincipal.h"
13 #include "mozilla/RefPtr.h"
14 #include "mozilla/Result.h"
15 #include "mozilla/ResultVariant.h"
16 #include "mozilla/dom/BlobBinding.h"
17 #include "mozilla/dom/ClipboardItem.h"
18 #include "mozilla/dom/ClipboardBinding.h"
19 #include "mozilla/dom/ContentChild.h"
20 #include "mozilla/dom/Promise.h"
21 #include "mozilla/dom/PromiseNativeHandler.h"
22 #include "mozilla/dom/DataTransfer.h"
23 #include "mozilla/dom/DataTransferItemList.h"
24 #include "mozilla/dom/DataTransferItem.h"
25 #include "mozilla/dom/Document.h"
26 #include "mozilla/StaticPrefs_dom.h"
27 #include "imgIContainer.h"
28 #include "imgITools.h"
29 #include "nsArrayUtils.h"
30 #include "nsComponentManagerUtils.h"
31 #include "nsContentUtils.h"
32 #include "nsGlobalWindowInner.h"
33 #include "nsIClipboard.h"
34 #include "nsIInputStream.h"
35 #include "nsIParserUtils.h"
36 #include "nsISupportsPrimitives.h"
37 #include "nsITransferable.h"
38 #include "nsNetUtil.h"
39 #include "nsServiceManagerUtils.h"
40 #include "nsStringStream.h"
42 #include "nsThreadUtils.h"
43 #include "nsVariant.h"
45 static mozilla::LazyLogModule
gClipboardLog("Clipboard");
47 namespace mozilla::dom
{
49 Clipboard::Clipboard(nsPIDOMWindowInner
* aWindow
)
50 : DOMEventTargetHelper(aWindow
) {}
52 Clipboard::~Clipboard() = default;
55 bool Clipboard::IsTestingPrefEnabledOrHasReadPermission(
56 nsIPrincipal
& aSubjectPrincipal
) {
57 return IsTestingPrefEnabled() ||
58 nsContentUtils::PrincipalHasPermission(aSubjectPrincipal
,
59 nsGkAtoms::clipboardRead
);
65 * This is a base class for ClipboardGetCallbackForRead and
66 * ClipboardGetCallbackForReadText.
68 class ClipboardGetCallback
: public nsIAsyncClipboardGetCallback
{
70 explicit ClipboardGetCallback(RefPtr
<Promise
>&& aPromise
)
71 : mPromise(std::move(aPromise
)) {}
73 // nsIAsyncClipboardGetCallback
74 NS_IMETHOD
OnError(nsresult aResult
) override final
{
76 RefPtr
<Promise
> p(std::move(mPromise
));
77 p
->MaybeRejectWithNotAllowedError(
78 "Clipboard read operation is not allowed.");
83 virtual ~ClipboardGetCallback() { MOZ_ASSERT(!mPromise
); };
85 // Not cycle-collected, because it should be nulled when the request is
86 // answered, rejected or aborted.
87 RefPtr
<Promise
> mPromise
;
90 static nsTArray
<nsCString
> MandatoryDataTypesAsCStrings() {
91 // Mandatory data types defined in
92 // https://w3c.github.io/clipboard-apis/#mandatory-data-types-x. The types
93 // should be in the same order as kNonPlainTextExternalFormats in
95 return nsTArray
<nsCString
>{nsLiteralCString(kHTMLMime
),
96 nsLiteralCString(kTextMime
),
97 nsLiteralCString(kPNGImageMime
)};
100 class ClipboardGetCallbackForRead final
: public ClipboardGetCallback
{
102 explicit ClipboardGetCallbackForRead(nsIGlobalObject
* aGlobal
,
103 RefPtr
<Promise
>&& aPromise
)
104 : ClipboardGetCallback(std::move(aPromise
)), mGlobal(aGlobal
) {}
106 // This object will never be held by a cycle-collected object, so it doesn't
107 // need to be cycle-collected despite holding alive cycle-collected objects.
110 // nsIAsyncClipboardGetCallback
111 NS_IMETHOD
OnSuccess(
112 nsIAsyncGetClipboardData
* aAsyncGetClipboardData
) override
{
113 MOZ_ASSERT(mPromise
);
114 MOZ_ASSERT(aAsyncGetClipboardData
);
116 nsTArray
<nsCString
> flavorList
;
117 nsresult rv
= aAsyncGetClipboardData
->GetFlavorList(flavorList
);
122 AutoTArray
<RefPtr
<ClipboardItem::ItemEntry
>, 3> entries
;
123 // We might reuse the request from DataTransfer created for paste event,
124 // which could contain more types that are not in the mandatory list.
125 for (const auto& format
: MandatoryDataTypesAsCStrings()) {
126 if (flavorList
.Contains(format
)) {
127 auto entry
= MakeRefPtr
<ClipboardItem::ItemEntry
>(
128 mGlobal
, NS_ConvertUTF8toUTF16(format
));
129 entry
->LoadDataFromSystemClipboard(aAsyncGetClipboardData
);
130 entries
.AppendElement(std::move(entry
));
134 RefPtr
<Promise
> p(std::move(mPromise
));
135 if (entries
.IsEmpty()) {
136 p
->MaybeResolve(nsTArray
<RefPtr
<ClipboardItem
>>{});
140 // We currently only support one clipboard item.
142 AutoTArray
<RefPtr
<ClipboardItem
>, 1>{MakeRefPtr
<ClipboardItem
>(
143 mGlobal
, PresentationStyle::Unspecified
, std::move(entries
))});
149 ~ClipboardGetCallbackForRead() = default;
151 nsCOMPtr
<nsIGlobalObject
> mGlobal
;
154 NS_IMPL_ISUPPORTS(ClipboardGetCallbackForRead
, nsIAsyncClipboardGetCallback
)
156 class ClipboardGetCallbackForReadText final
157 : public ClipboardGetCallback
,
158 public nsIAsyncClipboardRequestCallback
{
160 explicit ClipboardGetCallbackForReadText(RefPtr
<Promise
>&& aPromise
)
161 : ClipboardGetCallback(std::move(aPromise
)) {}
163 // This object will never be held by a cycle-collected object, so it doesn't
164 // need to be cycle-collected despite holding alive cycle-collected objects.
167 // nsIAsyncClipboardGetCallback
168 NS_IMETHOD
OnSuccess(
169 nsIAsyncGetClipboardData
* aAsyncGetClipboardData
) override
{
170 MOZ_ASSERT(mPromise
);
171 MOZ_ASSERT(!mTransferable
);
172 MOZ_ASSERT(aAsyncGetClipboardData
);
174 AutoTArray
<nsCString
, 3> flavors
;
175 nsresult rv
= aAsyncGetClipboardData
->GetFlavorList(flavors
);
180 mTransferable
= do_CreateInstance("@mozilla.org/widget/transferable;1");
181 if (NS_WARN_IF(!mTransferable
)) {
182 return OnError(NS_ERROR_UNEXPECTED
);
185 mTransferable
->Init(nullptr);
186 mTransferable
->AddDataFlavor(kTextMime
);
187 if (!flavors
.Contains(kTextMime
)) {
188 return OnComplete(NS_OK
);
191 rv
= aAsyncGetClipboardData
->GetData(mTransferable
, this);
199 // nsIAsyncClipboardRequestCallback
200 NS_IMETHOD
OnComplete(nsresult aResult
) override
{
201 MOZ_ASSERT(mPromise
);
202 MOZ_ASSERT(mTransferable
);
204 if (NS_FAILED(aResult
)) {
205 return OnError(aResult
);
209 nsCOMPtr
<nsISupports
> data
;
211 mTransferable
->GetTransferData(kTextMime
, getter_AddRefs(data
));
212 if (!NS_WARN_IF(NS_FAILED(rv
))) {
213 nsCOMPtr
<nsISupportsString
> supportsstr
= do_QueryInterface(data
);
214 MOZ_ASSERT(supportsstr
);
216 supportsstr
->GetData(str
);
220 RefPtr
<Promise
> p(std::move(mPromise
));
221 p
->MaybeResolve(str
);
227 ~ClipboardGetCallbackForReadText() = default;
229 nsCOMPtr
<nsITransferable
> mTransferable
;
232 NS_IMPL_ISUPPORTS(ClipboardGetCallbackForReadText
, nsIAsyncClipboardGetCallback
,
233 nsIAsyncClipboardRequestCallback
)
237 void Clipboard::RequestRead(Promise
& aPromise
, const ReadRequestType
& aType
,
238 nsPIDOMWindowInner
& aOwner
,
239 nsIPrincipal
& aSubjectPrincipal
,
240 nsIAsyncGetClipboardData
& aRequest
) {
242 bool isValid
= false;
243 MOZ_ASSERT(NS_SUCCEEDED(aRequest
.GetValid(&isValid
)) && isValid
);
246 RefPtr
<ClipboardGetCallback
> callback
;
248 case ReadRequestType::eRead
: {
250 MakeRefPtr
<ClipboardGetCallbackForRead
>(aOwner
.AsGlobal(), &aPromise
);
253 case ReadRequestType::eReadText
: {
254 callback
= MakeRefPtr
<ClipboardGetCallbackForReadText
>(&aPromise
);
258 MOZ_ASSERT_UNREACHABLE("Unknown read type");
263 MOZ_ASSERT(callback
);
264 callback
->OnSuccess(&aRequest
);
267 void Clipboard::RequestRead(Promise
* aPromise
, ReadRequestType aType
,
268 nsPIDOMWindowInner
* aOwner
,
269 nsIPrincipal
& aPrincipal
) {
270 RefPtr
<Promise
> p(aPromise
);
271 nsCOMPtr
<nsPIDOMWindowInner
> owner(aOwner
);
274 nsCOMPtr
<nsIClipboard
> clipboardService(
275 do_GetService("@mozilla.org/widget/clipboard;1", &rv
));
277 p
->MaybeReject(NS_ERROR_UNEXPECTED
);
281 RefPtr
<ClipboardGetCallback
> callback
;
283 case ReadRequestType::eRead
: {
284 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(owner
);
285 if (NS_WARN_IF(!global
)) {
286 p
->MaybeReject(NS_ERROR_UNEXPECTED
);
290 callback
= MakeRefPtr
<ClipboardGetCallbackForRead
>(global
, std::move(p
));
291 rv
= clipboardService
->AsyncGetData(
292 MandatoryDataTypesAsCStrings(), nsIClipboard::kGlobalClipboard
,
293 owner
->GetWindowContext(), &aPrincipal
, callback
);
296 case ReadRequestType::eReadText
: {
297 callback
= MakeRefPtr
<ClipboardGetCallbackForReadText
>(std::move(p
));
298 rv
= clipboardService
->AsyncGetData(
299 AutoTArray
<nsCString
, 1>{nsLiteralCString(kTextMime
)},
300 nsIClipboard::kGlobalClipboard
, owner
->GetWindowContext(),
301 &aPrincipal
, callback
);
305 MOZ_ASSERT_UNREACHABLE("Unknown read type");
311 MOZ_ASSERT(callback
);
312 callback
->OnError(rv
);
317 static bool IsReadTextExposedToContent() {
318 return StaticPrefs::dom_events_asyncClipboard_readText_DoNotUseDirectly();
321 already_AddRefed
<Promise
> Clipboard::ReadHelper(nsIPrincipal
& aSubjectPrincipal
,
322 ReadRequestType aType
,
324 // Create a new promise
325 RefPtr
<Promise
> p
= dom::Promise::Create(GetOwnerGlobal(), aRv
);
326 if (aRv
.Failed() || !p
) {
330 nsPIDOMWindowInner
* owner
= GetOwner();
332 p
->MaybeRejectWithUndefined();
336 // If a "paste" clipboard event is actively being processed, we're
337 // intentionally skipping permission/user-activation checks and giving the
338 // webpage access to the clipboard.
339 if (RefPtr
<DataTransfer
> dataTransfer
=
340 nsGlobalWindowInner::Cast(owner
)->GetCurrentPasteDataTransfer()) {
341 // If there is valid nsIAsyncGetClipboardData, use it directly.
342 if (nsCOMPtr
<nsIAsyncGetClipboardData
> asyncGetClipboardData
=
343 dataTransfer
->GetAsyncGetClipboardData()) {
344 bool isValid
= false;
345 asyncGetClipboardData
->GetValid(&isValid
);
347 RequestRead(*p
, aType
, *owner
, aSubjectPrincipal
,
348 *asyncGetClipboardData
);
354 if (IsTestingPrefEnabledOrHasReadPermission(aSubjectPrincipal
)) {
355 MOZ_LOG(GetClipboardLog(), LogLevel::Debug
,
356 ("%s: testing pref enabled or has read permission", __FUNCTION__
));
358 // Testing pref is not enabled and no read permission (for extension), so
359 // need to check user activation.
360 WindowContext
* windowContext
= owner
->GetWindowContext();
361 if (!windowContext
) {
362 MOZ_ASSERT_UNREACHABLE("There should be a WindowContext.");
363 p
->MaybeRejectWithUndefined();
367 // If no transient user activation, reject the promise and return.
368 if (!windowContext
->HasValidTransientUserGestureActivation()) {
369 p
->MaybeRejectWithNotAllowedError(
370 "Clipboard read request was blocked due to lack of "
376 RequestRead(p
, aType
, owner
, aSubjectPrincipal
);
380 already_AddRefed
<Promise
> Clipboard::Read(nsIPrincipal
& aSubjectPrincipal
,
382 return ReadHelper(aSubjectPrincipal
, ReadRequestType::eRead
, aRv
);
385 already_AddRefed
<Promise
> Clipboard::ReadText(nsIPrincipal
& aSubjectPrincipal
,
387 return ReadHelper(aSubjectPrincipal
, ReadRequestType::eReadText
, aRv
);
394 nsCOMPtr
<nsIVariant
> mData
;
396 NativeEntry(const nsAString
& aType
, nsIVariant
* aData
)
397 : mType(aType
), mData(aData
) {}
399 using NativeEntryPromise
= MozPromise
<NativeEntry
, CopyableErrorResult
, false>;
401 class BlobTextHandler final
: public PromiseNativeHandler
{
403 NS_DECL_THREADSAFE_ISUPPORTS
405 explicit BlobTextHandler(const nsAString
& aType
) : mType(aType
) {}
407 RefPtr
<NativeEntryPromise
> Promise() { return mHolder
.Ensure(__func__
); }
410 CopyableErrorResult rv
;
411 rv
.ThrowUnknownError("Unable to read blob for '"_ns
+
412 NS_ConvertUTF16toUTF8(mType
) + "' as text."_ns
);
413 mHolder
.Reject(rv
, __func__
);
416 void ResolvedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
,
417 ErrorResult
& aRv
) override
{
418 AssertIsOnMainThread();
421 if (!ConvertJSValueToUSVString(aCx
, aValue
, "ClipboardItem text", text
)) {
426 RefPtr
<nsVariantCC
> variant
= new nsVariantCC();
427 variant
->SetAsAString(text
);
429 NativeEntry
native(mType
, variant
);
430 mHolder
.Resolve(std::move(native
), __func__
);
433 void RejectedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
,
434 ErrorResult
& aRv
) override
{
439 ~BlobTextHandler() = default;
442 MozPromiseHolder
<NativeEntryPromise
> mHolder
;
445 NS_IMPL_ISUPPORTS0(BlobTextHandler
)
447 static RefPtr
<NativeEntryPromise
> GetStringNativeEntry(
448 const nsAString
& aType
, const OwningStringOrBlob
& aData
) {
449 if (aData
.IsString()) {
450 RefPtr
<nsVariantCC
> variant
= new nsVariantCC();
451 variant
->SetAsAString(aData
.GetAsString());
452 NativeEntry
native(aType
, variant
);
453 return NativeEntryPromise::CreateAndResolve(native
, __func__
);
456 RefPtr
<BlobTextHandler
> handler
= new BlobTextHandler(aType
);
457 IgnoredErrorResult ignored
;
458 RefPtr
<Promise
> promise
= aData
.GetAsBlob()->Text(ignored
);
459 if (ignored
.Failed()) {
460 CopyableErrorResult rv
;
461 rv
.ThrowUnknownError("Unable to read blob for '"_ns
+
462 NS_ConvertUTF16toUTF8(aType
) + "' as text."_ns
);
463 return NativeEntryPromise::CreateAndReject(rv
, __func__
);
465 promise
->AppendNativeHandler(handler
);
466 return handler
->Promise();
469 class ImageDecodeCallback final
: public imgIContainerCallback
{
473 explicit ImageDecodeCallback(const nsAString
& aType
) : mType(aType
) {}
475 RefPtr
<NativeEntryPromise
> Promise() { return mHolder
.Ensure(__func__
); }
477 NS_IMETHOD
OnImageReady(imgIContainer
* aImage
, nsresult aStatus
) override
{
478 // Request the image's width to force decoding the image header.
480 if (NS_FAILED(aStatus
) || NS_FAILED(aImage
->GetWidth(&ignored
))) {
481 CopyableErrorResult rv
;
482 rv
.ThrowDataError("Unable to decode blob for '"_ns
+
483 NS_ConvertUTF16toUTF8(mType
) + "' as image."_ns
);
484 mHolder
.Reject(rv
, __func__
);
488 RefPtr
<nsVariantCC
> variant
= new nsVariantCC();
489 variant
->SetAsISupports(aImage
);
491 // Note: We always put the image as "native" on the clipboard.
492 NativeEntry
native(NS_LITERAL_STRING_FROM_CSTRING(kNativeImageMime
),
494 mHolder
.Resolve(std::move(native
), __func__
);
499 ~ImageDecodeCallback() = default;
502 MozPromiseHolder
<NativeEntryPromise
> mHolder
;
505 NS_IMPL_ISUPPORTS(ImageDecodeCallback
, imgIContainerCallback
)
507 static RefPtr
<NativeEntryPromise
> GetImageNativeEntry(
508 const nsAString
& aType
, const OwningStringOrBlob
& aData
) {
509 if (aData
.IsString()) {
510 CopyableErrorResult rv
;
511 rv
.ThrowTypeError("DOMString not supported for '"_ns
+
512 NS_ConvertUTF16toUTF8(aType
) + "' as image data."_ns
);
513 return NativeEntryPromise::CreateAndReject(rv
, __func__
);
516 IgnoredErrorResult ignored
;
517 nsCOMPtr
<nsIInputStream
> stream
;
518 aData
.GetAsBlob()->CreateInputStream(getter_AddRefs(stream
), ignored
);
519 if (ignored
.Failed()) {
520 CopyableErrorResult rv
;
521 rv
.ThrowUnknownError("Unable to read blob for '"_ns
+
522 NS_ConvertUTF16toUTF8(aType
) + "' as image."_ns
);
523 return NativeEntryPromise::CreateAndReject(rv
, __func__
);
526 RefPtr
<ImageDecodeCallback
> callback
= new ImageDecodeCallback(aType
);
527 nsCOMPtr
<imgITools
> imgtool
= do_CreateInstance("@mozilla.org/image/tools;1");
528 imgtool
->DecodeImageAsync(stream
, NS_ConvertUTF16toUTF8(aType
), callback
,
529 GetMainThreadSerialEventTarget());
530 return callback
->Promise();
533 static Result
<NativeEntry
, ErrorResult
> SanitizeNativeEntry(
534 const NativeEntry
& aEntry
) {
535 MOZ_ASSERT(aEntry
.mType
.EqualsLiteral(kHTMLMime
));
538 aEntry
.mData
->GetAsAString(string
);
540 nsCOMPtr
<nsIParserUtils
> parserUtils
=
541 do_GetService(NS_PARSERUTILS_CONTRACTID
);
544 rv
.ThrowUnknownError("Error while processing '"_ns
+
545 NS_ConvertUTF16toUTF8(aEntry
.mType
) + "'."_ns
);
546 return Err(std::move(rv
));
549 uint32_t flags
= nsIParserUtils::SanitizerAllowStyle
|
550 nsIParserUtils::SanitizerAllowComments
;
551 nsAutoString sanitized
;
552 if (NS_FAILED(parserUtils
->Sanitize(string
, flags
, sanitized
))) {
554 rv
.ThrowUnknownError("Error while processing '"_ns
+
555 NS_ConvertUTF16toUTF8(aEntry
.mType
) + "'."_ns
);
556 return Err(std::move(rv
));
559 RefPtr
<nsVariantCC
> variant
= new nsVariantCC();
560 variant
->SetAsAString(sanitized
);
561 return NativeEntry(aEntry
.mType
, variant
);
564 static RefPtr
<NativeEntryPromise
> GetNativeEntry(
565 const nsAString
& aType
, const OwningStringOrBlob
& aData
) {
566 if (aType
.EqualsLiteral(kPNGImageMime
)) {
567 return GetImageNativeEntry(aType
, aData
);
570 RefPtr
<NativeEntryPromise
> promise
= GetStringNativeEntry(aType
, aData
);
571 if (aType
.EqualsLiteral(kHTMLMime
)) {
572 promise
= promise
->Then(
573 GetMainThreadSerialEventTarget(), __func__
,
574 [](const NativeEntryPromise::ResolveOrRejectValue
& aValue
)
575 -> RefPtr
<NativeEntryPromise
> {
576 if (aValue
.IsReject()) {
577 return NativeEntryPromise::CreateAndReject(aValue
.RejectValue(),
581 auto sanitized
= SanitizeNativeEntry(aValue
.ResolveValue());
582 if (sanitized
.isErr()) {
583 return NativeEntryPromise::CreateAndReject(
584 CopyableErrorResult(sanitized
.unwrapErr()), __func__
);
586 return NativeEntryPromise::CreateAndResolve(sanitized
.unwrap(),
593 // Restrict to types allowed by Chrome
594 // SVG is still disabled by default in Chrome.
595 static bool IsValidType(const nsAString
& aType
) {
596 return aType
.EqualsLiteral(kPNGImageMime
) || aType
.EqualsLiteral(kTextMime
) ||
597 aType
.EqualsLiteral(kHTMLMime
);
600 using NativeItemPromise
= NativeEntryPromise::AllPromiseType
;
601 static RefPtr
<NativeItemPromise
> GetClipboardNativeItem(
602 const ClipboardItem
& aItem
) {
603 nsTArray
<RefPtr
<NativeEntryPromise
>> promises
;
604 for (const auto& entry
: aItem
.Entries()) {
605 const nsAString
& type
= entry
->Type();
606 if (!IsValidType(type
)) {
607 CopyableErrorResult rv
;
608 rv
.ThrowNotAllowedError("Type '"_ns
+ NS_ConvertUTF16toUTF8(type
) +
609 "' not supported for write"_ns
);
610 return NativeItemPromise::CreateAndReject(rv
, __func__
);
613 using GetDataPromise
= ClipboardItem::ItemEntry::GetDataPromise
;
614 promises
.AppendElement(entry
->GetData()->Then(
615 GetMainThreadSerialEventTarget(), __func__
,
616 [t
= nsString(type
)](const GetDataPromise::ResolveOrRejectValue
& aValue
)
617 -> RefPtr
<NativeEntryPromise
> {
618 if (aValue
.IsReject()) {
619 return NativeEntryPromise::CreateAndReject(
620 CopyableErrorResult(aValue
.RejectValue()), __func__
);
623 return GetNativeEntry(t
, aValue
.ResolveValue());
626 return NativeEntryPromise::All(GetCurrentSerialEventTarget(), promises
);
629 class ClipboardWriteCallback final
: public nsIAsyncClipboardRequestCallback
{
631 // This object will never be held by a cycle-collected object, so it doesn't
632 // need to be cycle-collected despite holding alive cycle-collected objects.
635 explicit ClipboardWriteCallback(Promise
* aPromise
,
636 ClipboardItem
* aClipboardItem
)
637 : mPromise(aPromise
), mClipboardItem(aClipboardItem
) {}
639 // nsIAsyncClipboardRequestCallback
640 NS_IMETHOD
OnComplete(nsresult aResult
) override
{
641 MOZ_ASSERT(mPromise
);
643 RefPtr
<Promise
> promise
= std::move(mPromise
);
644 // XXX We need to check state here is because the promise might be rejected
645 // before the callback is called, we probably could wrap the promise into a
646 // structure to make it less confused.
647 if (promise
->State() == Promise::PromiseState::Pending
) {
648 if (NS_FAILED(aResult
)) {
649 promise
->MaybeRejectWithNotAllowedError(
650 "Clipboard write is not allowed.");
654 promise
->MaybeResolveWithUndefined();
661 ~ClipboardWriteCallback() {
662 // Callback should be notified.
663 MOZ_ASSERT(!mPromise
);
666 // It will be reset to nullptr once callback is notified.
667 RefPtr
<Promise
> mPromise
;
668 // Keep ClipboardItem alive until clipboard write is done.
669 RefPtr
<ClipboardItem
> mClipboardItem
;
672 NS_IMPL_ISUPPORTS(ClipboardWriteCallback
, nsIAsyncClipboardRequestCallback
)
676 already_AddRefed
<Promise
> Clipboard::Write(
677 const Sequence
<OwningNonNull
<ClipboardItem
>>& aData
,
678 nsIPrincipal
& aSubjectPrincipal
, ErrorResult
& aRv
) {
680 RefPtr
<Promise
> p
= dom::Promise::Create(GetOwnerGlobal(), aRv
);
685 RefPtr
<nsPIDOMWindowInner
> owner
= GetOwner();
686 Document
* doc
= owner
? owner
->GetDoc() : nullptr;
688 p
->MaybeRejectWithUndefined();
692 // We want to disable security check for automated tests that have the pref
693 // dom.events.testing.asyncClipboard set to true
694 if (!IsTestingPrefEnabled() &&
695 !nsContentUtils::IsCutCopyAllowed(doc
, aSubjectPrincipal
)) {
696 MOZ_LOG(GetClipboardLog(), LogLevel::Debug
,
697 ("Clipboard, Write, Not allowed to write to clipboard\n"));
698 p
->MaybeRejectWithNotAllowedError(
699 "Clipboard write was blocked due to lack of user activation.");
703 // Get the clipboard service
704 nsCOMPtr
<nsIClipboard
> clipboard(
705 do_GetService("@mozilla.org/widget/clipboard;1"));
707 p
->MaybeRejectWithUndefined();
711 nsCOMPtr
<nsILoadContext
> context
= doc
->GetLoadContext();
713 p
->MaybeRejectWithUndefined();
717 if (aData
.Length() > 1) {
718 p
->MaybeRejectWithNotAllowedError(
719 "Clipboard write is only supported with one ClipboardItem at the "
724 if (aData
.Length() == 0) {
725 // Nothing needs to be written to the clipboard.
726 p
->MaybeResolveWithUndefined();
730 nsCOMPtr
<nsIAsyncSetClipboardData
> request
;
731 RefPtr
<ClipboardWriteCallback
> callback
=
732 MakeRefPtr
<ClipboardWriteCallback
>(p
, aData
[0]);
733 nsresult rv
= clipboard
->AsyncSetData(nsIClipboard::kGlobalClipboard
,
734 owner
->GetWindowContext(), callback
,
735 getter_AddRefs(request
));
741 GetClipboardNativeItem(aData
[0])->Then(
742 GetMainThreadSerialEventTarget(), __func__
,
743 [owner
, request
, context
, principal
= RefPtr
{&aSubjectPrincipal
}](
744 const nsTArray
<NativeEntry
>& aEntries
) {
745 RefPtr
<DataTransfer
> dataTransfer
=
746 new DataTransfer(owner
, eCopy
,
747 /* is external */ true,
748 /* clipboard type */ -1);
750 for (const auto& entry
: aEntries
) {
751 nsresult rv
= dataTransfer
->SetDataWithPrincipal(
752 entry
.mType
, entry
.mData
, 0, principal
);
760 // Get the transferable
761 RefPtr
<nsITransferable
> transferable
=
762 dataTransfer
->GetTransferable(0, context
);
764 request
->Abort(NS_ERROR_FAILURE
);
768 // Finally write data to clipboard
769 request
->SetData(transferable
, /* clipboard owner */ nullptr);
771 [p
, request
](const CopyableErrorResult
& aErrorResult
) {
772 p
->MaybeReject(CopyableErrorResult(aErrorResult
));
773 request
->Abort(NS_ERROR_ABORT
);
779 already_AddRefed
<Promise
> Clipboard::WriteText(const nsAString
& aData
,
780 nsIPrincipal
& aSubjectPrincipal
,
782 nsCOMPtr
<nsIGlobalObject
> global
= GetOwnerGlobal();
784 aRv
.ThrowInvalidStateError("Unable to get global.");
788 // Create a single-element Sequence to reuse Clipboard::Write.
789 nsTArray
<RefPtr
<ClipboardItem::ItemEntry
>> items
;
790 items
.AppendElement(MakeRefPtr
<ClipboardItem::ItemEntry
>(
791 global
, NS_LITERAL_STRING_FROM_CSTRING(kTextMime
), aData
));
793 nsTArray
<OwningNonNull
<ClipboardItem
>> sequence
;
794 RefPtr
<ClipboardItem
> item
= MakeRefPtr
<ClipboardItem
>(
795 GetOwner(), PresentationStyle::Unspecified
, std::move(items
));
796 sequence
.AppendElement(*item
);
798 return Write(std::move(sequence
), aSubjectPrincipal
, aRv
);
801 JSObject
* Clipboard::WrapObject(JSContext
* aCx
,
802 JS::Handle
<JSObject
*> aGivenProto
) {
803 return Clipboard_Binding::Wrap(aCx
, this, aGivenProto
);
807 LogModule
* Clipboard::GetClipboardLog() { return gClipboardLog
; }
810 bool Clipboard::ReadTextEnabled(JSContext
* aCx
, JSObject
* aGlobal
) {
811 nsIPrincipal
* prin
= nsContentUtils::SubjectPrincipal(aCx
);
812 return IsReadTextExposedToContent() ||
813 prin
->GetIsAddonOrExpandedAddonPrincipal() ||
814 prin
->IsSystemPrincipal();
818 bool Clipboard::IsTestingPrefEnabled() {
819 bool clipboardTestingEnabled
=
820 StaticPrefs::dom_events_testing_asyncClipboard_DoNotUseDirectly();
821 MOZ_LOG(GetClipboardLog(), LogLevel::Debug
,
822 ("Clipboard, Is testing enabled? %d\n", clipboardTestingEnabled
));
823 return clipboardTestingEnabled
;
826 NS_IMPL_CYCLE_COLLECTION_CLASS(Clipboard
)
828 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Clipboard
,
829 DOMEventTargetHelper
)
830 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
832 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Clipboard
, DOMEventTargetHelper
)
833 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
835 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Clipboard
)
836 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
838 NS_IMPL_ADDREF_INHERITED(Clipboard
, DOMEventTargetHelper
)
839 NS_IMPL_RELEASE_INHERITED(Clipboard
, DOMEventTargetHelper
)
841 } // namespace mozilla::dom