Clipboard.cpp (27645B)
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/. */ 6 7 #include "mozilla/dom/Clipboard.h" 8 9 #include <algorithm> 10 11 #include "imgIContainer.h" 12 #include "imgITools.h" 13 #include "mozilla/AbstractThread.h" 14 #include "mozilla/BasePrincipal.h" 15 #include "mozilla/RefPtr.h" 16 #include "mozilla/Result.h" 17 #include "mozilla/ResultVariant.h" 18 #include "mozilla/StaticPrefs_dom.h" 19 #include "mozilla/dom/BlobBinding.h" 20 #include "mozilla/dom/ClipboardBinding.h" 21 #include "mozilla/dom/ClipboardItem.h" 22 #include "mozilla/dom/ContentChild.h" 23 #include "mozilla/dom/DataTransfer.h" 24 #include "mozilla/dom/DataTransferItem.h" 25 #include "mozilla/dom/DataTransferItemList.h" 26 #include "mozilla/dom/Document.h" 27 #include "mozilla/dom/Promise.h" 28 #include "mozilla/dom/PromiseNativeHandler.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" 41 #include "nsTArray.h" 42 #include "nsThreadUtils.h" 43 #include "nsVariant.h" 44 45 static mozilla::LazyLogModule gClipboardLog("Clipboard"); 46 47 namespace mozilla::dom { 48 49 Clipboard::Clipboard(nsPIDOMWindowInner* aWindow) 50 : DOMEventTargetHelper(aWindow) {} 51 52 Clipboard::~Clipboard() = default; 53 54 // static 55 bool Clipboard::IsTestingPrefEnabledOrHasReadPermission( 56 nsIPrincipal& aSubjectPrincipal) { 57 return IsTestingPrefEnabled() || 58 nsContentUtils::PrincipalHasPermission(aSubjectPrincipal, 59 nsGkAtoms::clipboardRead); 60 } 61 62 // Mandatory data types defined in 63 // https://w3c.github.io/clipboard-apis/#mandatory-data-types-x. The types 64 // should be in the same order as kNonPlainTextExternalFormats in 65 // DataTransfer. 66 static const nsLiteralCString kMandatoryDataTypes[] = { 67 nsLiteralCString(kHTMLMime), nsLiteralCString(kTextMime), 68 nsLiteralCString(kPNGImageMime)}; 69 70 // static 71 Span<const nsLiteralCString> Clipboard::MandatoryDataTypes() { 72 return Span<const nsLiteralCString>(kMandatoryDataTypes); 73 } 74 75 namespace { 76 77 /** 78 * This is a base class for ClipboardGetCallbackForRead and 79 * ClipboardGetCallbackForReadText. 80 */ 81 class ClipboardGetCallback : public nsIClipboardGetDataSnapshotCallback { 82 public: 83 explicit ClipboardGetCallback(RefPtr<Promise>&& aPromise) 84 : mPromise(std::move(aPromise)) {} 85 86 // nsIClipboardGetDataSnapshotCallback 87 NS_IMETHOD OnError(nsresult aResult) override final { 88 MOZ_ASSERT(mPromise); 89 RefPtr<Promise> p(std::move(mPromise)); 90 p->MaybeRejectWithNotAllowedError( 91 "Clipboard read operation is not allowed."); 92 return NS_OK; 93 } 94 95 protected: 96 virtual ~ClipboardGetCallback() { MOZ_ASSERT(!mPromise); }; 97 98 // Not cycle-collected, because it should be nulled when the request is 99 // answered, rejected or aborted. 100 RefPtr<Promise> mPromise; 101 }; 102 103 class ClipboardGetCallbackForRead final : public ClipboardGetCallback { 104 public: 105 explicit ClipboardGetCallbackForRead(nsIGlobalObject* aGlobal, 106 RefPtr<Promise>&& aPromise) 107 : ClipboardGetCallback(std::move(aPromise)), mGlobal(aGlobal) {} 108 109 // This object will never be held by a cycle-collected object, so it doesn't 110 // need to be cycle-collected despite holding alive cycle-collected objects. 111 NS_DECL_ISUPPORTS 112 113 // nsIClipboardGetDataSnapshotCallback 114 NS_IMETHOD OnSuccess( 115 nsIClipboardDataSnapshot* aClipboardDataSnapshot) override { 116 MOZ_ASSERT(mPromise); 117 MOZ_ASSERT(aClipboardDataSnapshot); 118 119 nsTArray<nsCString> flavorList; 120 nsresult rv = aClipboardDataSnapshot->GetFlavorList(flavorList); 121 if (NS_FAILED(rv)) { 122 return OnError(rv); 123 } 124 125 AutoTArray<RefPtr<ClipboardItem::ItemEntry>, 3> entries; 126 // We might reuse the request from DataTransfer created for paste event, 127 // which could contain more types that are not in the mandatory list. 128 for (const auto& format : kMandatoryDataTypes) { 129 if (flavorList.Contains(format)) { 130 auto entry = MakeRefPtr<ClipboardItem::ItemEntry>( 131 mGlobal, NS_ConvertUTF8toUTF16(format)); 132 entry->LoadDataFromSystemClipboard(aClipboardDataSnapshot); 133 entries.AppendElement(std::move(entry)); 134 } 135 } 136 137 RefPtr<Promise> p(std::move(mPromise)); 138 if (entries.IsEmpty()) { 139 p->MaybeResolve(nsTArray<RefPtr<ClipboardItem>>{}); 140 return NS_OK; 141 } 142 143 // We currently only support one clipboard item. 144 p->MaybeResolve( 145 AutoTArray<RefPtr<ClipboardItem>, 1>{MakeRefPtr<ClipboardItem>( 146 mGlobal, PresentationStyle::Unspecified, std::move(entries))}); 147 148 return NS_OK; 149 } 150 151 protected: 152 ~ClipboardGetCallbackForRead() = default; 153 154 nsCOMPtr<nsIGlobalObject> mGlobal; 155 }; 156 157 NS_IMPL_ISUPPORTS(ClipboardGetCallbackForRead, 158 nsIClipboardGetDataSnapshotCallback) 159 160 class ClipboardGetCallbackForReadText final 161 : public ClipboardGetCallback, 162 public nsIAsyncClipboardRequestCallback { 163 public: 164 explicit ClipboardGetCallbackForReadText(RefPtr<Promise>&& aPromise) 165 : ClipboardGetCallback(std::move(aPromise)) {} 166 167 // This object will never be held by a cycle-collected object, so it doesn't 168 // need to be cycle-collected despite holding alive cycle-collected objects. 169 NS_DECL_ISUPPORTS 170 171 // nsIClipboardGetDataSnapshotCallback 172 NS_IMETHOD OnSuccess( 173 nsIClipboardDataSnapshot* aClipboardDataSnapshot) override { 174 MOZ_ASSERT(mPromise); 175 MOZ_ASSERT(!mTransferable); 176 MOZ_ASSERT(aClipboardDataSnapshot); 177 178 AutoTArray<nsCString, 3> flavors; 179 nsresult rv = aClipboardDataSnapshot->GetFlavorList(flavors); 180 if (NS_FAILED(rv)) { 181 return OnError(rv); 182 } 183 184 mTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1"); 185 if (NS_WARN_IF(!mTransferable)) { 186 return OnError(NS_ERROR_UNEXPECTED); 187 } 188 189 mTransferable->Init(nullptr); 190 mTransferable->AddDataFlavor(kTextMime); 191 if (!flavors.Contains(kTextMime)) { 192 return OnComplete(NS_OK); 193 } 194 195 rv = aClipboardDataSnapshot->GetData(mTransferable, this); 196 if (NS_FAILED(rv)) { 197 return OnError(rv); 198 } 199 200 return NS_OK; 201 } 202 203 // nsIAsyncClipboardRequestCallback 204 NS_IMETHOD OnComplete(nsresult aResult) override { 205 MOZ_ASSERT(mPromise); 206 MOZ_ASSERT(mTransferable); 207 208 if (NS_FAILED(aResult)) { 209 return OnError(aResult); 210 } 211 212 nsAutoString str; 213 nsCOMPtr<nsISupports> data; 214 nsresult rv = 215 mTransferable->GetTransferData(kTextMime, getter_AddRefs(data)); 216 if (!NS_WARN_IF(NS_FAILED(rv))) { 217 nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data); 218 MOZ_ASSERT(supportsstr); 219 if (supportsstr) { 220 supportsstr->GetData(str); 221 } 222 } 223 224 RefPtr<Promise> p(std::move(mPromise)); 225 p->MaybeResolve(str); 226 227 return NS_OK; 228 } 229 230 protected: 231 ~ClipboardGetCallbackForReadText() = default; 232 233 nsCOMPtr<nsITransferable> mTransferable; 234 }; 235 236 NS_IMPL_ISUPPORTS(ClipboardGetCallbackForReadText, 237 nsIClipboardGetDataSnapshotCallback, 238 nsIAsyncClipboardRequestCallback) 239 240 } // namespace 241 242 void Clipboard::RequestRead(Promise& aPromise, const ReadRequestType& aType, 243 nsPIDOMWindowInner& aOwner, 244 nsIPrincipal& aSubjectPrincipal, 245 nsIClipboardDataSnapshot& aRequest) { 246 #ifdef DEBUG 247 bool isValid = false; 248 MOZ_ASSERT(NS_SUCCEEDED(aRequest.GetValid(&isValid)) && isValid); 249 #endif 250 251 RefPtr<ClipboardGetCallback> callback; 252 switch (aType) { 253 case ReadRequestType::eRead: { 254 callback = 255 MakeRefPtr<ClipboardGetCallbackForRead>(aOwner.AsGlobal(), &aPromise); 256 break; 257 } 258 case ReadRequestType::eReadText: { 259 callback = MakeRefPtr<ClipboardGetCallbackForReadText>(&aPromise); 260 break; 261 } 262 default: { 263 MOZ_ASSERT_UNREACHABLE("Unknown read type"); 264 return; 265 } 266 } 267 268 MOZ_ASSERT(callback); 269 callback->OnSuccess(&aRequest); 270 } 271 272 void Clipboard::RequestRead(Promise* aPromise, ReadRequestType aType, 273 nsPIDOMWindowInner* aOwner, 274 nsIPrincipal& aPrincipal) { 275 RefPtr<Promise> p(aPromise); 276 nsCOMPtr<nsPIDOMWindowInner> owner(aOwner); 277 278 nsresult rv; 279 nsCOMPtr<nsIClipboard> clipboardService( 280 do_GetService("@mozilla.org/widget/clipboard;1", &rv)); 281 if (NS_FAILED(rv)) { 282 p->MaybeReject(NS_ERROR_UNEXPECTED); 283 return; 284 } 285 286 RefPtr<ClipboardGetCallback> callback; 287 switch (aType) { 288 case ReadRequestType::eRead: { 289 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(owner); 290 if (NS_WARN_IF(!global)) { 291 p->MaybeReject(NS_ERROR_UNEXPECTED); 292 return; 293 } 294 295 AutoTArray<nsCString, std::size(kMandatoryDataTypes)> types; 296 types.AppendElements(Span<const nsLiteralCString>(kMandatoryDataTypes)); 297 298 callback = MakeRefPtr<ClipboardGetCallbackForRead>(global, std::move(p)); 299 rv = clipboardService->GetDataSnapshot( 300 types, nsIClipboard::kGlobalClipboard, owner->GetWindowContext(), 301 &aPrincipal, callback); 302 break; 303 } 304 case ReadRequestType::eReadText: { 305 callback = MakeRefPtr<ClipboardGetCallbackForReadText>(std::move(p)); 306 rv = clipboardService->GetDataSnapshot( 307 AutoTArray<nsCString, 1>{nsLiteralCString(kTextMime)}, 308 nsIClipboard::kGlobalClipboard, owner->GetWindowContext(), 309 &aPrincipal, callback); 310 break; 311 } 312 default: { 313 MOZ_ASSERT_UNREACHABLE("Unknown read type"); 314 break; 315 } 316 } 317 318 if (NS_FAILED(rv)) { 319 MOZ_ASSERT(callback); 320 callback->OnError(rv); 321 return; 322 } 323 } 324 325 already_AddRefed<Promise> Clipboard::ReadHelper(nsIPrincipal& aSubjectPrincipal, 326 ReadRequestType aType, 327 ErrorResult& aRv) { 328 // Create a new promise 329 nsGlobalWindowInner* owner = GetOwnerWindow(); 330 RefPtr<Promise> p = dom::Promise::Create(owner, aRv); 331 if (aRv.Failed()) { 332 return nullptr; 333 } 334 335 // If a "paste" clipboard event is actively being processed, we're 336 // intentionally skipping permission/user-activation checks and giving the 337 // webpage access to the clipboard. 338 if (RefPtr<DataTransfer> dataTransfer = 339 owner->GetCurrentPasteDataTransfer()) { 340 // If there is valid nsIClipboardDataSnapshot, use it directly. 341 if (nsCOMPtr<nsIClipboardDataSnapshot> clipboardDataSnapshot = 342 dataTransfer->GetClipboardDataSnapshot()) { 343 bool isValid = false; 344 clipboardDataSnapshot->GetValid(&isValid); 345 if (isValid) { 346 RequestRead(*p, aType, *owner, aSubjectPrincipal, 347 *clipboardDataSnapshot); 348 return p.forget(); 349 } 350 } 351 } 352 353 if (IsTestingPrefEnabledOrHasReadPermission(aSubjectPrincipal)) { 354 MOZ_LOG(GetClipboardLog(), LogLevel::Debug, 355 ("%s: testing pref enabled or has read permission", __FUNCTION__)); 356 } else { 357 // Testing pref is not enabled and no read permission (for extension), so 358 // need to check user activation. 359 WindowContext* windowContext = owner->GetWindowContext(); 360 if (!windowContext) { 361 MOZ_ASSERT_UNREACHABLE("There should be a WindowContext."); 362 p->MaybeRejectWithUndefined(); 363 return p.forget(); 364 } 365 366 // If no transient user activation, reject the promise and return. 367 if (!windowContext->HasValidTransientUserGestureActivation()) { 368 p->MaybeRejectWithNotAllowedError( 369 "Clipboard read request was blocked due to lack of " 370 "user activation."); 371 return p.forget(); 372 } 373 } 374 375 RequestRead(p, aType, owner, aSubjectPrincipal); 376 return p.forget(); 377 } 378 379 already_AddRefed<Promise> Clipboard::Read(nsIPrincipal& aSubjectPrincipal, 380 ErrorResult& aRv) { 381 return ReadHelper(aSubjectPrincipal, ReadRequestType::eRead, aRv); 382 } 383 384 already_AddRefed<Promise> Clipboard::ReadText(nsIPrincipal& aSubjectPrincipal, 385 ErrorResult& aRv) { 386 return ReadHelper(aSubjectPrincipal, ReadRequestType::eReadText, aRv); 387 } 388 389 namespace { 390 391 struct NativeEntry { 392 nsString mType; 393 nsCOMPtr<nsIVariant> mData; 394 395 NativeEntry(const nsAString& aType, nsIVariant* aData) 396 : mType(aType), mData(aData) {} 397 }; 398 using NativeEntryPromise = MozPromise<NativeEntry, CopyableErrorResult, false>; 399 400 class BlobTextHandler final : public PromiseNativeHandler { 401 public: 402 NS_DECL_THREADSAFE_ISUPPORTS 403 404 explicit BlobTextHandler(const nsAString& aType) : mType(aType) {} 405 406 RefPtr<NativeEntryPromise> Promise() { return mHolder.Ensure(__func__); } 407 408 void Reject() { 409 CopyableErrorResult rv; 410 rv.ThrowUnknownError("Unable to read blob for '"_ns + 411 NS_ConvertUTF16toUTF8(mType) + "' as text."_ns); 412 mHolder.Reject(rv, __func__); 413 } 414 415 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, 416 ErrorResult& aRv) override { 417 AssertIsOnMainThread(); 418 419 nsString text; 420 if (!ConvertJSValueToUSVString(aCx, aValue, "ClipboardItem text", text)) { 421 Reject(); 422 return; 423 } 424 425 RefPtr<nsVariantCC> variant = new nsVariantCC(); 426 variant->SetAsAString(text); 427 428 NativeEntry native(mType, variant); 429 mHolder.Resolve(std::move(native), __func__); 430 } 431 432 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, 433 ErrorResult& aRv) override { 434 Reject(); 435 } 436 437 private: 438 ~BlobTextHandler() = default; 439 440 nsString mType; 441 MozPromiseHolder<NativeEntryPromise> mHolder; 442 }; 443 444 NS_IMPL_ISUPPORTS0(BlobTextHandler) 445 446 static RefPtr<NativeEntryPromise> GetStringNativeEntry( 447 const nsAString& aType, const OwningStringOrBlob& aData) { 448 if (aData.IsString()) { 449 RefPtr<nsVariantCC> variant = new nsVariantCC(); 450 variant->SetAsAString(aData.GetAsString()); 451 NativeEntry native(aType, variant); 452 return NativeEntryPromise::CreateAndResolve(native, __func__); 453 } 454 455 RefPtr<BlobTextHandler> handler = new BlobTextHandler(aType); 456 IgnoredErrorResult ignored; 457 RefPtr<Promise> promise = aData.GetAsBlob()->Text(ignored); 458 if (ignored.Failed()) { 459 CopyableErrorResult rv; 460 rv.ThrowUnknownError("Unable to read blob for '"_ns + 461 NS_ConvertUTF16toUTF8(aType) + "' as text."_ns); 462 return NativeEntryPromise::CreateAndReject(rv, __func__); 463 } 464 promise->AppendNativeHandler(handler); 465 return handler->Promise(); 466 } 467 468 class ImageDecodeCallback final : public imgIContainerCallback { 469 public: 470 NS_DECL_ISUPPORTS 471 472 explicit ImageDecodeCallback(const nsAString& aType) : mType(aType) {} 473 474 RefPtr<NativeEntryPromise> Promise() { return mHolder.Ensure(__func__); } 475 476 NS_IMETHOD OnImageReady(imgIContainer* aImage, nsresult aStatus) override { 477 // Request the image's width to force decoding the image header. 478 int32_t ignored; 479 if (NS_FAILED(aStatus) || NS_FAILED(aImage->GetWidth(&ignored))) { 480 CopyableErrorResult rv; 481 rv.ThrowDataError("Unable to decode blob for '"_ns + 482 NS_ConvertUTF16toUTF8(mType) + "' as image."_ns); 483 mHolder.Reject(rv, __func__); 484 return NS_OK; 485 } 486 487 RefPtr<nsVariantCC> variant = new nsVariantCC(); 488 variant->SetAsISupports(aImage); 489 490 // Note: We always put the image as "native" on the clipboard. 491 NativeEntry native(NS_LITERAL_STRING_FROM_CSTRING(kNativeImageMime), 492 variant); 493 mHolder.Resolve(std::move(native), __func__); 494 return NS_OK; 495 }; 496 497 private: 498 ~ImageDecodeCallback() = default; 499 500 nsString mType; 501 MozPromiseHolder<NativeEntryPromise> mHolder; 502 }; 503 504 NS_IMPL_ISUPPORTS(ImageDecodeCallback, imgIContainerCallback) 505 506 static RefPtr<NativeEntryPromise> GetImageNativeEntry( 507 const nsAString& aType, const OwningStringOrBlob& aData) { 508 if (aData.IsString()) { 509 CopyableErrorResult rv; 510 rv.ThrowTypeError("DOMString not supported for '"_ns + 511 NS_ConvertUTF16toUTF8(aType) + "' as image data."_ns); 512 return NativeEntryPromise::CreateAndReject(rv, __func__); 513 } 514 515 IgnoredErrorResult ignored; 516 nsCOMPtr<nsIInputStream> stream; 517 aData.GetAsBlob()->CreateInputStream(getter_AddRefs(stream), ignored); 518 if (ignored.Failed()) { 519 CopyableErrorResult rv; 520 rv.ThrowUnknownError("Unable to read blob for '"_ns + 521 NS_ConvertUTF16toUTF8(aType) + "' as image."_ns); 522 return NativeEntryPromise::CreateAndReject(rv, __func__); 523 } 524 525 RefPtr<ImageDecodeCallback> callback = new ImageDecodeCallback(aType); 526 nsCOMPtr<imgITools> imgtool = do_CreateInstance("@mozilla.org/image/tools;1"); 527 imgtool->DecodeImageAsync(stream, NS_ConvertUTF16toUTF8(aType), callback, 528 GetMainThreadSerialEventTarget()); 529 return callback->Promise(); 530 } 531 532 static Result<NativeEntry, ErrorResult> SanitizeNativeEntry( 533 const NativeEntry& aEntry) { 534 MOZ_ASSERT(aEntry.mType.EqualsLiteral(kHTMLMime)); 535 536 nsAutoString string; 537 aEntry.mData->GetAsAString(string); 538 539 nsCOMPtr<nsIParserUtils> parserUtils = 540 do_GetService(NS_PARSERUTILS_CONTRACTID); 541 if (!parserUtils) { 542 ErrorResult rv; 543 rv.ThrowUnknownError("Error while processing '"_ns + 544 NS_ConvertUTF16toUTF8(aEntry.mType) + "'."_ns); 545 return Err(std::move(rv)); 546 } 547 548 uint32_t flags = nsIParserUtils::SanitizerAllowStyle | 549 nsIParserUtils::SanitizerAllowComments; 550 nsAutoString sanitized; 551 if (NS_FAILED(parserUtils->Sanitize(string, flags, sanitized))) { 552 ErrorResult rv; 553 rv.ThrowUnknownError("Error while processing '"_ns + 554 NS_ConvertUTF16toUTF8(aEntry.mType) + "'."_ns); 555 return Err(std::move(rv)); 556 } 557 558 RefPtr<nsVariantCC> variant = new nsVariantCC(); 559 variant->SetAsAString(sanitized); 560 return NativeEntry(aEntry.mType, variant); 561 } 562 563 static RefPtr<NativeEntryPromise> GetNativeEntry( 564 const nsAString& aType, const OwningStringOrBlob& aData) { 565 if (aType.EqualsLiteral(kPNGImageMime)) { 566 return GetImageNativeEntry(aType, aData); 567 } 568 569 RefPtr<NativeEntryPromise> promise = GetStringNativeEntry(aType, aData); 570 if (aType.EqualsLiteral(kHTMLMime)) { 571 promise = promise->Then( 572 GetMainThreadSerialEventTarget(), __func__, 573 [](const NativeEntryPromise::ResolveOrRejectValue& aValue) 574 -> RefPtr<NativeEntryPromise> { 575 if (aValue.IsReject()) { 576 return NativeEntryPromise::CreateAndReject(aValue.RejectValue(), 577 __func__); 578 } 579 580 auto sanitized = SanitizeNativeEntry(aValue.ResolveValue()); 581 if (sanitized.isErr()) { 582 return NativeEntryPromise::CreateAndReject( 583 CopyableErrorResult(sanitized.unwrapErr()), __func__); 584 } 585 return NativeEntryPromise::CreateAndResolve(sanitized.unwrap(), 586 __func__); 587 }); 588 } 589 return promise; 590 } 591 592 // Restrict to types allowed by Chrome 593 // SVG is still disabled by default in Chrome. 594 static bool IsValidType(const nsAString& aType) { 595 return aType.EqualsLiteral(kPNGImageMime) || aType.EqualsLiteral(kTextMime) || 596 aType.EqualsLiteral(kHTMLMime); 597 } 598 599 using NativeItemPromise = NativeEntryPromise::AllPromiseType; 600 static RefPtr<NativeItemPromise> GetClipboardNativeItem( 601 const ClipboardItem& aItem) { 602 nsTArray<RefPtr<NativeEntryPromise>> promises; 603 for (const auto& entry : aItem.Entries()) { 604 const nsAString& type = entry->Type(); 605 if (!IsValidType(type)) { 606 CopyableErrorResult rv; 607 rv.ThrowNotAllowedError("Type '"_ns + NS_ConvertUTF16toUTF8(type) + 608 "' not supported for write"_ns); 609 return NativeItemPromise::CreateAndReject(rv, __func__); 610 } 611 612 using GetDataPromise = ClipboardItem::ItemEntry::GetDataPromise; 613 promises.AppendElement(entry->GetData()->Then( 614 GetMainThreadSerialEventTarget(), __func__, 615 [t = nsString(type)](const GetDataPromise::ResolveOrRejectValue& aValue) 616 -> RefPtr<NativeEntryPromise> { 617 if (aValue.IsReject()) { 618 return NativeEntryPromise::CreateAndReject( 619 CopyableErrorResult(aValue.RejectValue()), __func__); 620 } 621 622 return GetNativeEntry(t, aValue.ResolveValue()); 623 })); 624 } 625 return NativeEntryPromise::All(GetCurrentSerialEventTarget(), promises); 626 } 627 628 class ClipboardWriteCallback final : public nsIAsyncClipboardRequestCallback { 629 public: 630 // This object will never be held by a cycle-collected object, so it doesn't 631 // need to be cycle-collected despite holding alive cycle-collected objects. 632 NS_DECL_ISUPPORTS 633 634 explicit ClipboardWriteCallback(Promise* aPromise, 635 ClipboardItem* aClipboardItem) 636 : mPromise(aPromise), mClipboardItem(aClipboardItem) {} 637 638 // nsIAsyncClipboardRequestCallback 639 NS_IMETHOD OnComplete(nsresult aResult) override { 640 MOZ_ASSERT(mPromise); 641 642 RefPtr<Promise> promise = std::move(mPromise); 643 // XXX We need to check state here is because the promise might be rejected 644 // before the callback is called, we probably could wrap the promise into a 645 // structure to make it less confused. 646 if (promise->State() == Promise::PromiseState::Pending) { 647 if (NS_FAILED(aResult)) { 648 promise->MaybeRejectWithNotAllowedError( 649 "Clipboard write is not allowed."); 650 return NS_OK; 651 } 652 653 promise->MaybeResolveWithUndefined(); 654 } 655 656 return NS_OK; 657 } 658 659 protected: 660 ~ClipboardWriteCallback() { 661 // Callback should be notified. 662 MOZ_ASSERT(!mPromise); 663 }; 664 665 // It will be reset to nullptr once callback is notified. 666 RefPtr<Promise> mPromise; 667 // Keep ClipboardItem alive until clipboard write is done. 668 RefPtr<ClipboardItem> mClipboardItem; 669 }; 670 671 NS_IMPL_ISUPPORTS(ClipboardWriteCallback, nsIAsyncClipboardRequestCallback) 672 673 } // namespace 674 675 already_AddRefed<Promise> Clipboard::Write( 676 const Sequence<OwningNonNull<ClipboardItem>>& aData, 677 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { 678 // Create a promise 679 RefPtr<nsGlobalWindowInner> owner = GetOwnerWindow(); 680 RefPtr<Promise> p = dom::Promise::Create(owner, aRv); 681 if (aRv.Failed()) { 682 return nullptr; 683 } 684 685 Document* doc = owner->GetDoc(); 686 if (!doc) { 687 p->MaybeRejectWithUndefined(); 688 return p.forget(); 689 } 690 691 // We want to disable security check for automated tests that have the pref 692 // dom.events.testing.asyncClipboard set to true 693 if (!IsTestingPrefEnabled() && 694 !nsContentUtils::IsCutCopyAllowed(doc, aSubjectPrincipal)) { 695 MOZ_LOG(GetClipboardLog(), LogLevel::Debug, 696 ("Clipboard, Write, Not allowed to write to clipboard\n")); 697 p->MaybeRejectWithNotAllowedError( 698 "Clipboard write was blocked due to lack of user activation."); 699 return p.forget(); 700 } 701 702 // Get the clipboard service 703 nsCOMPtr<nsIClipboard> clipboard( 704 do_GetService("@mozilla.org/widget/clipboard;1")); 705 if (!clipboard) { 706 p->MaybeRejectWithUndefined(); 707 return p.forget(); 708 } 709 710 nsCOMPtr<nsILoadContext> context = doc->GetLoadContext(); 711 if (!context) { 712 p->MaybeRejectWithUndefined(); 713 return p.forget(); 714 } 715 716 if (aData.Length() > 1) { 717 p->MaybeRejectWithNotAllowedError( 718 "Clipboard write is only supported with one ClipboardItem at the " 719 "moment"); 720 return p.forget(); 721 } 722 723 if (aData.Length() == 0) { 724 // Nothing needs to be written to the clipboard. 725 p->MaybeResolveWithUndefined(); 726 return p.forget(); 727 } 728 729 nsCOMPtr<nsIAsyncSetClipboardData> request; 730 RefPtr<ClipboardWriteCallback> callback = 731 MakeRefPtr<ClipboardWriteCallback>(p, aData[0]); 732 nsresult rv = clipboard->AsyncSetData(nsIClipboard::kGlobalClipboard, 733 owner->GetWindowContext(), callback, 734 getter_AddRefs(request)); 735 if (NS_FAILED(rv)) { 736 p->MaybeReject(rv); 737 return p.forget(); 738 } 739 740 GetClipboardNativeItem(aData[0])->Then( 741 GetMainThreadSerialEventTarget(), __func__, 742 [owner, request, context, principal = RefPtr{&aSubjectPrincipal}]( 743 const nsTArray<NativeEntry>& aEntries) { 744 RefPtr<DataTransfer> dataTransfer = 745 new DataTransfer(ToSupports(owner), eCopy, 746 /* is external */ true, 747 /* clipboard type */ Nothing()); 748 749 for (const auto& entry : aEntries) { 750 nsresult rv = dataTransfer->SetDataWithPrincipal( 751 entry.mType, entry.mData, 0, principal); 752 753 if (NS_FAILED(rv)) { 754 request->Abort(rv); 755 return; 756 } 757 } 758 759 // Get the transferable 760 RefPtr<nsITransferable> transferable = 761 dataTransfer->GetTransferable(0, context); 762 if (!transferable) { 763 request->Abort(NS_ERROR_FAILURE); 764 return; 765 } 766 767 // Finally write data to clipboard 768 request->SetData(transferable, /* clipboard owner */ nullptr); 769 }, 770 [p, request](const CopyableErrorResult& aErrorResult) { 771 p->MaybeReject(CopyableErrorResult(aErrorResult)); 772 request->Abort(NS_ERROR_ABORT); 773 }); 774 775 return p.forget(); 776 } 777 778 already_AddRefed<Promise> Clipboard::WriteText(const nsAString& aData, 779 nsIPrincipal& aSubjectPrincipal, 780 ErrorResult& aRv) { 781 nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal(); 782 if (!global) { 783 aRv.ThrowInvalidStateError("Unable to get global."); 784 return nullptr; 785 } 786 787 // Create a single-element Sequence to reuse Clipboard::Write. 788 nsTArray<RefPtr<ClipboardItem::ItemEntry>> items; 789 items.AppendElement(MakeRefPtr<ClipboardItem::ItemEntry>( 790 global, NS_LITERAL_STRING_FROM_CSTRING(kTextMime), aData)); 791 792 nsTArray<OwningNonNull<ClipboardItem>> sequence; 793 RefPtr<ClipboardItem> item = MakeRefPtr<ClipboardItem>( 794 ToSupports(GetOwnerWindow()), PresentationStyle::Unspecified, 795 std::move(items)); 796 sequence.AppendElement(*item); 797 798 return Write(std::move(sequence), aSubjectPrincipal, aRv); 799 } 800 801 JSObject* Clipboard::WrapObject(JSContext* aCx, 802 JS::Handle<JSObject*> aGivenProto) { 803 return Clipboard_Binding::Wrap(aCx, this, aGivenProto); 804 } 805 806 /* static */ 807 LogModule* Clipboard::GetClipboardLog() { return gClipboardLog; } 808 809 /* static */ 810 bool Clipboard::IsTestingPrefEnabled() { 811 bool clipboardTestingEnabled = 812 StaticPrefs::dom_events_testing_asyncClipboard_DoNotUseDirectly(); 813 MOZ_LOG(GetClipboardLog(), LogLevel::Debug, 814 ("Clipboard, Is testing enabled? %d\n", clipboardTestingEnabled)); 815 return clipboardTestingEnabled; 816 } 817 818 NS_IMPL_CYCLE_COLLECTION_CLASS(Clipboard) 819 820 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Clipboard, 821 DOMEventTargetHelper) 822 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 823 824 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Clipboard, DOMEventTargetHelper) 825 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 826 827 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Clipboard) 828 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 829 830 NS_IMPL_ADDREF_INHERITED(Clipboard, DOMEventTargetHelper) 831 NS_IMPL_RELEASE_INHERITED(Clipboard, DOMEventTargetHelper) 832 833 } // namespace mozilla::dom