tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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