tor-browser

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

ClipboardItem.cpp (12235B)


      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/ClipboardItem.h"
      8 
      9 #include "mozilla/dom/Clipboard.h"
     10 #include "mozilla/dom/Promise.h"
     11 #include "mozilla/dom/Record.h"
     12 #include "nsComponentManagerUtils.h"
     13 #include "nsIClipboard.h"
     14 #include "nsIInputStream.h"
     15 #include "nsISupportsPrimitives.h"
     16 #include "nsNetUtil.h"
     17 #include "nsServiceManagerUtils.h"
     18 
     19 namespace mozilla::dom {
     20 
     21 NS_IMPL_CYCLE_COLLECTION(ClipboardItem::ItemEntry, mGlobal, mData,
     22                         mPendingGetTypeRequests)
     23 
     24 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ClipboardItem::ItemEntry)
     25  NS_INTERFACE_MAP_ENTRY(nsIAsyncClipboardRequestCallback)
     26  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, PromiseNativeHandler)
     27 NS_INTERFACE_MAP_END
     28 
     29 NS_IMPL_CYCLE_COLLECTING_ADDREF(ClipboardItem::ItemEntry)
     30 NS_IMPL_CYCLE_COLLECTING_RELEASE(ClipboardItem::ItemEntry)
     31 
     32 void ClipboardItem::ItemEntry::ResolvedCallback(JSContext* aCx,
     33                                                JS::Handle<JS::Value> aValue,
     34                                                ErrorResult& aRv) {
     35  MOZ_ASSERT(!mTransferable);
     36  mIsLoadingData = false;
     37  OwningStringOrBlob clipboardData;
     38  if (!clipboardData.Init(aCx, aValue)) {
     39    JS_ClearPendingException(aCx);
     40    RejectPendingPromises(NS_ERROR_DOM_DATA_ERR);
     41    return;
     42  }
     43 
     44  MaybeResolvePendingPromises(std::move(clipboardData));
     45 }
     46 
     47 void ClipboardItem::ItemEntry::RejectedCallback(JSContext* aCx,
     48                                                JS::Handle<JS::Value> aValue,
     49                                                ErrorResult& aRv) {
     50  MOZ_ASSERT(!mTransferable);
     51  mIsLoadingData = false;
     52  RejectPendingPromises(NS_ERROR_DOM_DATA_ERR);
     53 }
     54 
     55 RefPtr<ClipboardItem::ItemEntry::GetDataPromise>
     56 ClipboardItem::ItemEntry::GetData() {
     57  // Data is still being loaded, either from the system clipboard or the data
     58  // promise provided to the ClipboardItem constructor.
     59  if (mIsLoadingData) {
     60    MOZ_ASSERT(!mData.IsString() && !mData.IsBlob(),
     61               "Data should be uninitialized");
     62    MOZ_ASSERT(mLoadResult.isNothing(), "Should have no load result");
     63 
     64    MozPromiseHolder<GetDataPromise> holder;
     65    RefPtr<GetDataPromise> promise = holder.Ensure(__func__);
     66    mPendingGetDataRequests.AppendElement(std::move(holder));
     67    return promise.forget();
     68  }
     69 
     70  if (NS_FAILED(mLoadResult.value())) {
     71    MOZ_ASSERT(!mData.IsString() && !mData.IsBlob(),
     72               "Data should be uninitialized");
     73    // We are not able to load data, so reject the promise directly.
     74    return GetDataPromise::CreateAndReject(mLoadResult.value(), __func__);
     75  }
     76 
     77  MOZ_ASSERT(mData.IsString() || mData.IsBlob(), "Data should be initialized");
     78  OwningStringOrBlob data(mData);
     79  return GetDataPromise::CreateAndResolve(std::move(data), __func__);
     80 }
     81 
     82 NS_IMETHODIMP ClipboardItem::ItemEntry::OnComplete(nsresult aResult) {
     83  MOZ_ASSERT(mIsLoadingData);
     84 
     85  mIsLoadingData = false;
     86  nsCOMPtr<nsITransferable> trans = std::move(mTransferable);
     87 
     88  if (NS_FAILED(aResult)) {
     89    RejectPendingPromises(aResult);
     90    return NS_OK;
     91  }
     92 
     93  MOZ_ASSERT(trans);
     94  nsCOMPtr<nsISupports> data;
     95  nsresult rv = trans->GetTransferData(NS_ConvertUTF16toUTF8(mType).get(),
     96                                       getter_AddRefs(data));
     97  if (NS_WARN_IF(NS_FAILED(rv))) {
     98    RejectPendingPromises(rv);
     99    return NS_OK;
    100  }
    101 
    102  RefPtr<Blob> blob;
    103  if (nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data)) {
    104    nsAutoString str;
    105    supportsstr->GetData(str);
    106 
    107    blob = Blob::CreateStringBlob(mGlobal, NS_ConvertUTF16toUTF8(str), mType);
    108  } else if (nsCOMPtr<nsIInputStream> istream = do_QueryInterface(data)) {
    109    uint64_t available;
    110    void* data = nullptr;
    111    rv = NS_ReadInputStreamToBuffer(istream, &data, -1, &available);
    112    if (NS_WARN_IF(NS_FAILED(rv))) {
    113      RejectPendingPromises(rv);
    114      return NS_OK;
    115    }
    116 
    117    blob = Blob::CreateMemoryBlob(mGlobal, data, available, mType);
    118  } else if (nsCOMPtr<nsISupportsCString> supportscstr =
    119                 do_QueryInterface(data)) {
    120    nsAutoCString str;
    121    supportscstr->GetData(str);
    122 
    123    blob = Blob::CreateStringBlob(mGlobal, str, mType);
    124  }
    125 
    126  if (!blob) {
    127    RejectPendingPromises(NS_ERROR_DOM_DATA_ERR);
    128    return NS_OK;
    129  }
    130 
    131  OwningStringOrBlob clipboardData;
    132  clipboardData.SetAsBlob() = std::move(blob);
    133  MaybeResolvePendingPromises(std::move(clipboardData));
    134  return NS_OK;
    135 }
    136 
    137 void ClipboardItem::ItemEntry::LoadDataFromSystemClipboard(
    138    nsIClipboardDataSnapshot* aDataGetter) {
    139  MOZ_ASSERT(aDataGetter);
    140  // XXX maybe we could consider adding a method to check whether the union
    141  // object is uninitialized or initialized.
    142  MOZ_DIAGNOSTIC_ASSERT(!mData.IsString() && !mData.IsBlob(),
    143                        "Data should be uninitialized.");
    144  MOZ_DIAGNOSTIC_ASSERT(mLoadResult.isNothing(), "Should have no load result");
    145  MOZ_DIAGNOSTIC_ASSERT(!mIsLoadingData && !mTransferable,
    146                        "Should not be in the process of loading data");
    147 
    148  mIsLoadingData = true;
    149 
    150  mTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1");
    151  if (NS_WARN_IF(!mTransferable)) {
    152    OnComplete(NS_ERROR_FAILURE);
    153    return;
    154  }
    155 
    156  mTransferable->Init(nullptr);
    157  mTransferable->AddDataFlavor(NS_ConvertUTF16toUTF8(mType).get());
    158 
    159  nsresult rv = aDataGetter->GetData(mTransferable, this);
    160  if (NS_FAILED(rv)) {
    161    OnComplete(rv);
    162    return;
    163  }
    164 }
    165 
    166 void ClipboardItem::ItemEntry::LoadDataFromDataPromise(Promise& aDataPromise) {
    167  MOZ_DIAGNOSTIC_ASSERT(!mData.IsString() && !mData.IsBlob(),
    168                        "Data should be uninitialized");
    169  MOZ_DIAGNOSTIC_ASSERT(mLoadResult.isNothing(), "Should have no load result");
    170  MOZ_DIAGNOSTIC_ASSERT(!mIsLoadingData && !mTransferable,
    171                        "Should not be in the process of loading data");
    172 
    173  mIsLoadingData = true;
    174  aDataPromise.AppendNativeHandler(this);
    175 }
    176 
    177 void ClipboardItem::ItemEntry::ReactGetTypePromise(Promise& aPromise) {
    178  // Data is still being loaded, either from the system clipboard or the data
    179  // promise provided to the ClipboardItem constructor.
    180  if (mIsLoadingData) {
    181    MOZ_ASSERT(!mData.IsString() && !mData.IsBlob(),
    182               "Data should be uninitialized");
    183    MOZ_ASSERT(mLoadResult.isNothing(), "Should have no load result.");
    184    mPendingGetTypeRequests.AppendElement(&aPromise);
    185    return;
    186  }
    187 
    188  if (NS_FAILED(mLoadResult.value())) {
    189    MOZ_ASSERT(!mData.IsString() && !mData.IsBlob(),
    190               "Data should be uninitialized");
    191    aPromise.MaybeRejectWithDataError("The data for type '"_ns +
    192                                      NS_ConvertUTF16toUTF8(mType) +
    193                                      "' was not found"_ns);
    194    return;
    195  }
    196 
    197  MaybeResolveGetTypePromise(mData, aPromise);
    198 }
    199 
    200 void ClipboardItem::ItemEntry::MaybeResolveGetTypePromise(
    201    const OwningStringOrBlob& aData, Promise& aPromise) {
    202  if (aData.IsBlob()) {
    203    aPromise.MaybeResolve(aData);
    204    return;
    205  }
    206 
    207  // XXX This is for the case that data is from ClipboardItem constructor,
    208  // maybe we should also load that into a Blob earlier. But Safari returns
    209  // different `Blob` instances for each `getTypes` call if the string is from
    210  // ClipboardItem constructor, which is more like our current setup.
    211  if (RefPtr<Blob> blob = Blob::CreateStringBlob(
    212          mGlobal, NS_ConvertUTF16toUTF8(aData.GetAsString()), mType)) {
    213    aPromise.MaybeResolve(blob);
    214    return;
    215  }
    216 
    217  aPromise.MaybeRejectWithDataError("The data for type '"_ns +
    218                                    NS_ConvertUTF16toUTF8(mType) +
    219                                    "' was not found"_ns);
    220 }
    221 
    222 void ClipboardItem::ItemEntry::RejectPendingPromises(nsresult aRv) {
    223  MOZ_ASSERT(NS_FAILED(aRv), "Should have a failure code here");
    224  MOZ_DIAGNOSTIC_ASSERT(!mData.IsString() && !mData.IsBlob(),
    225                        "Data should be uninitialized");
    226  MOZ_DIAGNOSTIC_ASSERT(mLoadResult.isNothing(), "Should not have load result");
    227  MOZ_DIAGNOSTIC_ASSERT(!mIsLoadingData && !mTransferable,
    228                        "Should not be in the process of loading data");
    229  mLoadResult.emplace(aRv);
    230  auto promiseHolders = std::move(mPendingGetDataRequests);
    231  for (auto& promiseHolder : promiseHolders) {
    232    promiseHolder.Reject(aRv, __func__);
    233  }
    234  auto getTypePromises = std::move(mPendingGetTypeRequests);
    235  for (auto& promise : getTypePromises) {
    236    promise->MaybeReject(aRv);
    237  }
    238 }
    239 
    240 void ClipboardItem::ItemEntry::MaybeResolvePendingPromises(
    241    OwningStringOrBlob&& aData) {
    242  MOZ_DIAGNOSTIC_ASSERT(!mData.IsString() && !mData.IsBlob(),
    243                        "Data should be uninitialized");
    244  MOZ_DIAGNOSTIC_ASSERT(mLoadResult.isNothing(), "Should not have load result");
    245  MOZ_DIAGNOSTIC_ASSERT(!mIsLoadingData && !mTransferable,
    246                        "Should not be in the process of loading data");
    247  mLoadResult.emplace(NS_OK);
    248  mData = std::move(aData);
    249  auto getDataPromiseHolders = std::move(mPendingGetDataRequests);
    250  for (auto& promiseHolder : getDataPromiseHolders) {
    251    OwningStringOrBlob data(mData);
    252    promiseHolder.Resolve(std::move(data), __func__);
    253  }
    254  auto promises = std::move(mPendingGetTypeRequests);
    255  for (auto& promise : promises) {
    256    MaybeResolveGetTypePromise(mData, *promise);
    257  }
    258 }
    259 
    260 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ClipboardItem, mOwner, mItems)
    261 
    262 ClipboardItem::ClipboardItem(nsISupports* aOwner,
    263                             const dom::PresentationStyle aPresentationStyle,
    264                             nsTArray<RefPtr<ItemEntry>>&& aItems)
    265    : mOwner(aOwner),
    266      mPresentationStyle(aPresentationStyle),
    267      mItems(std::move(aItems)) {}
    268 
    269 // static
    270 already_AddRefed<ClipboardItem> ClipboardItem::Constructor(
    271    const GlobalObject& aGlobal,
    272    const Record<nsString, OwningNonNull<Promise>>& aItems,
    273    const ClipboardItemOptions& aOptions, ErrorResult& aRv) {
    274  if (aItems.Entries().IsEmpty()) {
    275    aRv.ThrowTypeError("At least one entry required");
    276    return nullptr;
    277  }
    278 
    279  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
    280  MOZ_ASSERT(global);
    281 
    282  nsTArray<RefPtr<ItemEntry>> items;
    283  for (const auto& entry : aItems.Entries()) {
    284    RefPtr<ItemEntry> item = MakeRefPtr<ItemEntry>(global, entry.mKey);
    285    item->LoadDataFromDataPromise(*entry.mValue);
    286    items.AppendElement(std::move(item));
    287  }
    288 
    289  RefPtr<ClipboardItem> item = MakeRefPtr<ClipboardItem>(
    290      global, aOptions.mPresentationStyle, std::move(items));
    291  return item.forget();
    292 }
    293 
    294 // static
    295 bool ClipboardItem::Supports(const GlobalObject& aGlobal,
    296                             const nsAString& aType) {
    297  for (const auto& mandatoryType : Clipboard::MandatoryDataTypes()) {
    298    if (CompareUTF8toUTF16(mandatoryType, aType) == 0) {
    299      return true;
    300    }
    301  }
    302  return false;
    303 }
    304 
    305 void ClipboardItem::GetTypes(nsTArray<nsString>& aTypes) const {
    306  for (const auto& item : mItems) {
    307    aTypes.AppendElement(item->Type());
    308  }
    309 }
    310 
    311 already_AddRefed<Promise> ClipboardItem::GetType(const nsAString& aType,
    312                                                 ErrorResult& aRv) {
    313  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
    314  RefPtr<Promise> p = Promise::Create(global, aRv);
    315  if (aRv.Failed()) {
    316    return nullptr;
    317  }
    318 
    319  for (auto& item : mItems) {
    320    MOZ_ASSERT(item);
    321 
    322    const nsAString& type = item->Type();
    323    if (type == aType) {
    324      nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
    325      if (NS_WARN_IF(!global)) {
    326        p->MaybeReject(NS_ERROR_UNEXPECTED);
    327        return p.forget();
    328      }
    329 
    330      item->ReactGetTypePromise(*p);
    331      return p.forget();
    332    }
    333  }
    334 
    335  p->MaybeRejectWithNotFoundError(
    336      "The type '"_ns + NS_ConvertUTF16toUTF8(aType) + "' was not found"_ns);
    337  return p.forget();
    338 }
    339 
    340 JSObject* ClipboardItem::WrapObject(JSContext* aCx,
    341                                    JS::Handle<JSObject*> aGivenProto) {
    342  return mozilla::dom::ClipboardItem_Binding::Wrap(aCx, this, aGivenProto);
    343 }
    344 
    345 }  // namespace mozilla::dom