tor-browser

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

DataTransferItem.cpp (19077B)


      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 "DataTransferItem.h"
      8 
      9 #include "DataTransferItemList.h"
     10 #include "imgIContainer.h"
     11 #include "imgITools.h"
     12 #include "mozilla/Attributes.h"
     13 #include "mozilla/BasePrincipal.h"
     14 #include "mozilla/ContentEvents.h"
     15 #include "mozilla/EventForwards.h"
     16 #include "mozilla/StaticPrefs_dom.h"
     17 #include "mozilla/dom/BlobImpl.h"
     18 #include "mozilla/dom/DataTransferItemBinding.h"
     19 #include "mozilla/dom/Directory.h"
     20 #include "mozilla/dom/FileSystem.h"
     21 #include "mozilla/dom/FileSystemDirectoryEntry.h"
     22 #include "mozilla/dom/FileSystemFileEntry.h"
     23 #include "nsComponentManagerUtils.h"
     24 #include "nsContentUtils.h"
     25 #include "nsGlobalWindowInner.h"
     26 #include "nsIClipboard.h"
     27 #include "nsIFile.h"
     28 #include "nsIInputStream.h"
     29 #include "nsIScriptObjectPrincipal.h"
     30 #include "nsISupportsPrimitives.h"
     31 #include "nsNetUtil.h"
     32 #include "nsQueryObject.h"
     33 #include "nsThreadUtils.h"
     34 #include "nsVariant.h"
     35 
     36 namespace {
     37 
     38 struct FileMimeNameData {
     39  const char* mMimeName;
     40  const char* mFileName;
     41 };
     42 
     43 FileMimeNameData kFileMimeNameMap[] = {
     44    {kFileMime, "GenericFileName"},
     45    {kPNGImageMime, "GenericImageNamePNG"},
     46 };
     47 
     48 }  // anonymous namespace
     49 
     50 namespace mozilla::dom {
     51 
     52 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DataTransferItem, mData, mPrincipal,
     53                                      mDataTransfer, mCachedFile)
     54 NS_IMPL_CYCLE_COLLECTING_ADDREF(DataTransferItem)
     55 NS_IMPL_CYCLE_COLLECTING_RELEASE(DataTransferItem)
     56 
     57 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DataTransferItem)
     58  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
     59  NS_INTERFACE_MAP_ENTRY(nsISupports)
     60 NS_INTERFACE_MAP_END
     61 
     62 JSObject* DataTransferItem::WrapObject(JSContext* aCx,
     63                                       JS::Handle<JSObject*> aGivenProto) {
     64  return DataTransferItem_Binding::Wrap(aCx, this, aGivenProto);
     65 }
     66 
     67 already_AddRefed<DataTransferItem> DataTransferItem::Clone(
     68    DataTransfer* aDataTransfer) const {
     69  MOZ_ASSERT(aDataTransfer);
     70 
     71  RefPtr<DataTransferItem> it = new DataTransferItem(aDataTransfer, mType);
     72 
     73  // Copy over all of the fields
     74  it->mKind = mKind;
     75  it->mIndex = mIndex;
     76  it->mData = mData;
     77  it->mPrincipal = mPrincipal;
     78  it->mChromeOnly = mChromeOnly;
     79  it->mDoNotAttemptToLoadData = mDoNotAttemptToLoadData;
     80 
     81  return it.forget();
     82 }
     83 
     84 void DataTransferItem::SetData(nsIVariant* aData) {
     85  // Invalidate our file cache, we will regenerate it with the new data
     86  mCachedFile = nullptr;
     87 
     88  if (!aData) {
     89    // We are holding a temporary null which will later be filled.
     90    // These are provided by the system, and have guaranteed properties about
     91    // their kind based on their type.
     92    MOZ_ASSERT(!mType.IsEmpty());
     93    // This type should not be provided by the OS.
     94    MOZ_ASSERT(!mType.EqualsASCII(kNativeImageMime));
     95 
     96    mKind = KIND_STRING;
     97    for (uint32_t i = 0; i < std::size(kFileMimeNameMap); ++i) {
     98      if (mType.EqualsASCII(kFileMimeNameMap[i].mMimeName)) {
     99        mKind = KIND_FILE;
    100        break;
    101      }
    102    }
    103 
    104    mData = nullptr;
    105    return;
    106  }
    107 
    108  mData = aData;
    109  mKind = KindFromData(mData);
    110 }
    111 
    112 /* static */ DataTransferItem::eKind DataTransferItem::KindFromData(
    113    nsIVariant* aData) {
    114  nsCOMPtr<nsISupports> supports;
    115  nsresult rv = aData->GetAsISupports(getter_AddRefs(supports));
    116  if (NS_SUCCEEDED(rv) && supports) {
    117    // Check if we have one of the supported file data formats
    118    if (RefPtr<Blob>(do_QueryObject(supports)) ||
    119        nsCOMPtr<BlobImpl>(do_QueryInterface(supports)) ||
    120        nsCOMPtr<nsIFile>(do_QueryInterface(supports))) {
    121      return KIND_FILE;
    122    }
    123 
    124    if (StaticPrefs::dom_events_dataTransfer_imageAsFile_enabled()) {
    125      // Firefox internally uses imgIContainer to represent images being
    126      // copied/dragged. These need to be encoded to PNG files.
    127      if (nsCOMPtr<imgIContainer>(do_QueryInterface(supports))) {
    128        return KIND_FILE;
    129      }
    130    }
    131  }
    132 
    133  nsAutoString string;
    134  // If we can't get the data type as a string, that means that the object
    135  // should be considered to be of the "other" type. This is impossible
    136  // through the APIs defined by the spec, but we provide extra Moz* APIs,
    137  // which allow setting of non-string data. We determine whether we can
    138  // consider it a string, by calling GetAsAString, and checking for success.
    139  rv = aData->GetAsAString(string);
    140  if (NS_SUCCEEDED(rv)) {
    141    return KIND_STRING;
    142  }
    143 
    144  return KIND_OTHER;
    145 }
    146 
    147 void DataTransferItem::FillInExternalData() {
    148  if (mData || mDoNotAttemptToLoadData) {
    149    return;
    150  }
    151 
    152  NS_ConvertUTF16toUTF8 utf8format(mType);
    153  const char* format = utf8format.get();
    154  if (strcmp(format, "text/uri-list") == 0) {
    155    format = kURLDataMime;
    156  }
    157 
    158  nsCOMPtr<nsITransferable> trans = mDataTransfer->GetTransferable();
    159  if (!trans) {
    160    trans = do_CreateInstance("@mozilla.org/widget/transferable;1");
    161    if (NS_WARN_IF(!trans)) {
    162      return;
    163    }
    164 
    165    trans->Init(nullptr);
    166    trans->AddDataFlavor(format);
    167 
    168    if (mDataTransfer->GetEventMessage() == ePaste) {
    169      MOZ_ASSERT(mIndex == 0, "index in clipboard must be 0");
    170 
    171      if (mDataTransfer->ClipboardType().isNothing()) {
    172        return;
    173      }
    174 
    175      nsCOMPtr<nsIClipboardDataSnapshot> clipboardDataSnapshot =
    176          mDataTransfer->GetClipboardDataSnapshot();
    177      if (!clipboardDataSnapshot) {
    178        return;
    179      }
    180      nsresult rv = clipboardDataSnapshot->GetDataSync(trans);
    181      if (NS_WARN_IF(NS_FAILED(rv))) {
    182        if (rv == NS_ERROR_CONTENT_BLOCKED) {
    183          // If the load of this content was blocked by Content Analysis,
    184          // do not attempt to load it again.
    185          mDoNotAttemptToLoadData = true;
    186        }
    187        return;
    188      }
    189    } else {
    190      nsCOMPtr<nsIDragSession> dragSession =
    191          mDataTransfer->GetOwnerDragSession();
    192      if (!dragSession) {
    193        return;
    194      }
    195 
    196      nsresult rv = dragSession->GetData(trans, mIndex);
    197      if (NS_WARN_IF(NS_FAILED(rv))) {
    198        return;
    199      }
    200    }
    201  }
    202 
    203  nsCOMPtr<nsISupports> data;
    204  nsresult rv = trans->GetTransferData(format, getter_AddRefs(data));
    205  if (NS_WARN_IF(NS_FAILED(rv) || !data)) {
    206    return;
    207  }
    208 
    209  // Fill the variant
    210  RefPtr<nsVariantCC> variant = new nsVariantCC();
    211 
    212  eKind oldKind = Kind();
    213  if (oldKind == KIND_FILE) {
    214    // Because this is an external piece of data, mType is one of kFileMime,
    215    // kPNGImageMime, kJPEGImageMime, or kGIFImageMime. Some of these types
    216    // are passed in as a nsIInputStream which must be converted to a
    217    // dom::File before storing.
    218    if (nsCOMPtr<nsIInputStream> istream = do_QueryInterface(data)) {
    219      RefPtr<File> file = CreateFileFromInputStream(istream);
    220      if (NS_WARN_IF(!file)) {
    221        return;
    222      }
    223      data = do_QueryObject(file);
    224    }
    225    variant->SetAsISupports(data);
    226  } else {
    227    // We have an external piece of string data. Extract it and store it in the
    228    // variant
    229    MOZ_ASSERT(oldKind == KIND_STRING);
    230 
    231    nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data);
    232    if (supportsstr) {
    233      nsAutoString str;
    234      supportsstr->GetData(str);
    235      variant->SetAsAString(str);
    236    } else {
    237      nsCOMPtr<nsISupportsCString> supportscstr = do_QueryInterface(data);
    238      if (supportscstr) {
    239        nsAutoCString str;
    240        supportscstr->GetData(str);
    241        variant->SetAsACString(str);
    242      }
    243    }
    244  }
    245 
    246  SetData(variant);
    247 
    248  if (oldKind != Kind()) {
    249    NS_WARNING(
    250        "Clipboard data provided by the OS does not match predicted kind");
    251    mDataTransfer->TypesListMayHaveChanged();
    252  }
    253 }
    254 
    255 void DataTransferItem::GetType(nsAString& aType) {
    256  // If we don't have a File, we can just put whatever our recorded internal
    257  // type is.
    258  if (Kind() != KIND_FILE) {
    259    aType = mType;
    260    return;
    261  }
    262 
    263  // If we do have a File, then we need to look at our File object to discover
    264  // what its mime type is. We can use the System Principal here, as this
    265  // information should be avaliable even if the data is currently inaccessible
    266  // (for example during a dragover).
    267  //
    268  // XXX: This seems inefficient, as it seems like we should be able to get this
    269  // data without getting the entire File object, which may require talking to
    270  // the OS.
    271  ErrorResult rv;
    272  RefPtr<File> file = GetAsFile(*nsContentUtils::GetSystemPrincipal(), rv);
    273  MOZ_ASSERT(!rv.Failed(), "Failed to get file data with system principal");
    274 
    275  // If we don't actually have a file, fall back to returning the internal type.
    276  if (NS_WARN_IF(!file)) {
    277    aType = mType;
    278    return;
    279  }
    280 
    281  file->GetType(aType);
    282 }
    283 
    284 already_AddRefed<File> DataTransferItem::GetAsFile(
    285    nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
    286  // This is done even if we have an mCachedFile, as it performs the necessary
    287  // permissions checks to ensure that we are allowed to access this type.
    288  nsCOMPtr<nsIVariant> data = Data(&aSubjectPrincipal, aRv);
    289  if (NS_WARN_IF(!data || aRv.Failed())) {
    290    return nullptr;
    291  }
    292 
    293  // We have to check our kind after getting the data, because if we have
    294  // external data and the OS lied to us (which unfortunately does happen
    295  // sometimes), then we might not have the same type of data as we did coming
    296  // into this function.
    297  if (NS_WARN_IF(mKind != KIND_FILE)) {
    298    return nullptr;
    299  }
    300 
    301  // Generate the dom::File from the stored data, caching it so that the
    302  // same object is returned in the future.
    303  if (!mCachedFile) {
    304    nsCOMPtr<nsISupports> supports;
    305    aRv = data->GetAsISupports(getter_AddRefs(supports));
    306    MOZ_ASSERT(!aRv.Failed() && supports,
    307               "File objects should be stored as nsISupports variants");
    308    if (aRv.Failed() || !supports) {
    309      return nullptr;
    310    }
    311 
    312    if (RefPtr<Blob> blob = do_QueryObject(supports)) {
    313      mCachedFile = blob->ToFile();
    314    } else {
    315      nsCOMPtr<nsIGlobalObject> global = mDataTransfer->GetGlobal();
    316      if (NS_WARN_IF(!global)) {
    317        return nullptr;
    318      }
    319 
    320      if (nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(supports)) {
    321        MOZ_ASSERT(blobImpl->IsFile());
    322        mCachedFile = File::Create(global, blobImpl);
    323        if (NS_WARN_IF(!mCachedFile)) {
    324          return nullptr;
    325        }
    326      } else if (nsCOMPtr<nsIFile> ifile = do_QueryInterface(supports)) {
    327        mCachedFile = File::CreateFromFile(global, ifile);
    328        if (NS_WARN_IF(!mCachedFile)) {
    329          return nullptr;
    330        }
    331      } else if (nsCOMPtr<imgIContainer> img = do_QueryInterface(supports)) {
    332        nsCOMPtr<imgITools> imgTools =
    333            do_CreateInstance("@mozilla.org/image/tools;1");
    334 
    335        nsCOMPtr<nsIInputStream> inputStream;
    336        nsresult rv = imgTools->EncodeImage(img, "image/png"_ns, u""_ns,
    337                                            getter_AddRefs(inputStream));
    338        if (NS_WARN_IF(NS_FAILED(rv))) {
    339          return nullptr;
    340        }
    341 
    342        mCachedFile = CreateFileFromInputStream(
    343            inputStream, "GenericImageNamePNG", u"image/png"_ns);
    344        if (NS_WARN_IF(!mCachedFile)) {
    345          return nullptr;
    346        }
    347      } else {
    348        MOZ_ASSERT(false, "One of the above code paths should be taken");
    349        return nullptr;
    350      }
    351    }
    352  }
    353 
    354  RefPtr<File> file = mCachedFile;
    355  return file.forget();
    356 }
    357 
    358 already_AddRefed<FileSystemEntry> DataTransferItem::GetAsEntry(
    359    nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
    360  RefPtr<File> file = GetAsFile(aSubjectPrincipal, aRv);
    361  if (NS_WARN_IF(aRv.Failed()) || !file) {
    362    return nullptr;
    363  }
    364 
    365  nsCOMPtr<nsIGlobalObject> global = mDataTransfer->GetGlobal();
    366  if (NS_WARN_IF(!global)) {
    367    return nullptr;
    368  }
    369 
    370  RefPtr<FileSystem> fs = FileSystem::Create(global);
    371  RefPtr<FileSystemEntry> entry;
    372  BlobImpl* impl = file->Impl();
    373  MOZ_ASSERT(impl);
    374 
    375  if (impl->IsDirectory()) {
    376    nsAutoString fullpath;
    377    impl->GetMozFullPathInternal(fullpath, aRv);
    378    if (aRv.Failed()) {
    379      aRv.SuppressException();
    380      return nullptr;
    381    }
    382 
    383    nsCOMPtr<nsIFile> directoryFile;
    384    // fullPath is already in unicode, we don't have to use
    385    // NS_NewNativeLocalFile.
    386    nsresult rv = NS_NewLocalFile(fullpath, getter_AddRefs(directoryFile));
    387    if (NS_WARN_IF(NS_FAILED(rv))) {
    388      return nullptr;
    389    }
    390 
    391    RefPtr<Directory> directory = Directory::Create(global, directoryFile);
    392    entry = new FileSystemDirectoryEntry(global, directory, nullptr, fs);
    393  } else {
    394    entry = new FileSystemFileEntry(global, file, nullptr, fs);
    395  }
    396 
    397  Sequence<RefPtr<FileSystemEntry>> entries;
    398  if (!entries.AppendElement(entry, fallible)) {
    399    return nullptr;
    400  }
    401 
    402  fs->CreateRoot(entries);
    403  return entry.forget();
    404 }
    405 
    406 already_AddRefed<File> DataTransferItem::CreateFileFromInputStream(
    407    nsIInputStream* aStream) {
    408  const char* key = nullptr;
    409  for (uint32_t i = 0; i < std::size(kFileMimeNameMap); ++i) {
    410    if (mType.EqualsASCII(kFileMimeNameMap[i].mMimeName)) {
    411      key = kFileMimeNameMap[i].mFileName;
    412      break;
    413    }
    414  }
    415  if (!key) {
    416    MOZ_ASSERT_UNREACHABLE("Unsupported mime type");
    417    key = "GenericFileName";
    418  }
    419 
    420  return CreateFileFromInputStream(aStream, key, mType);
    421 }
    422 
    423 already_AddRefed<File> DataTransferItem::CreateFileFromInputStream(
    424    nsIInputStream* aStream, const char* aFileNameKey,
    425    const nsAString& aContentType) {
    426  nsAutoString fileName;
    427  nsresult rv = nsContentUtils::GetLocalizedString(
    428      nsContentUtils::eDOM_PROPERTIES, aFileNameKey, fileName);
    429  if (NS_WARN_IF(NS_FAILED(rv))) {
    430    return nullptr;
    431  }
    432 
    433  uint64_t available;
    434  void* data = nullptr;
    435  rv = NS_ReadInputStreamToBuffer(aStream, &data, -1, &available);
    436  if (NS_WARN_IF(NS_FAILED(rv))) {
    437    return nullptr;
    438  }
    439 
    440  nsCOMPtr<nsIGlobalObject> global = mDataTransfer->GetGlobal();
    441  if (NS_WARN_IF(!global)) {
    442    return nullptr;
    443  }
    444 
    445  return File::CreateMemoryFileWithLastModifiedNow(global, data, available,
    446                                                   fileName, aContentType);
    447 }
    448 
    449 void DataTransferItem::GetAsString(FunctionStringCallback* aCallback,
    450                                   nsIPrincipal& aSubjectPrincipal,
    451                                   ErrorResult& aRv) {
    452  if (!aCallback) {
    453    return;
    454  }
    455 
    456  // Theoretically this should be done inside of the runnable, as it might be an
    457  // expensive operation on some systems, however we wouldn't get access to the
    458  // NS_ERROR_DOM_SECURITY_ERROR messages which may be raised by this method.
    459  nsCOMPtr<nsIVariant> data = Data(&aSubjectPrincipal, aRv);
    460  if (NS_WARN_IF(!data || aRv.Failed())) {
    461    return;
    462  }
    463 
    464  // We have to check our kind after getting the data, because if we have
    465  // external data and the OS lied to us (which unfortunately does happen
    466  // sometimes), then we might not have the same type of data as we did coming
    467  // into this function.
    468  if (NS_WARN_IF(mKind != KIND_STRING)) {
    469    return;
    470  }
    471 
    472  nsAutoString stringData;
    473  nsresult rv = data->GetAsAString(stringData);
    474  if (NS_WARN_IF(NS_FAILED(rv))) {
    475    return;
    476  }
    477 
    478  // Dispatch the callback to the main thread
    479  class GASRunnable final : public Runnable {
    480   public:
    481    GASRunnable(FunctionStringCallback* aCallback, const nsAString& aStringData)
    482        : mozilla::Runnable("GASRunnable"),
    483          mCallback(aCallback),
    484          mStringData(aStringData) {}
    485 
    486    // MOZ_CAN_RUN_SCRIPT_BOUNDARY until runnables are opted into
    487    // MOZ_CAN_RUN_SCRIPT.  See bug 1535398.
    488    MOZ_CAN_RUN_SCRIPT_BOUNDARY
    489    NS_IMETHOD Run() override {
    490      ErrorResult rv;
    491      mCallback->Call(mStringData, rv);
    492      NS_WARNING_ASSERTION(!rv.Failed(), "callback failed");
    493      return rv.StealNSResult();
    494    }
    495 
    496   private:
    497    const RefPtr<FunctionStringCallback> mCallback;
    498    nsString mStringData;
    499  };
    500 
    501  RefPtr<GASRunnable> runnable = new GASRunnable(aCallback, stringData);
    502 
    503  if (nsCOMPtr<nsIGlobalObject> global = mDataTransfer->GetGlobal()) {
    504    rv = global->Dispatch(runnable.forget());
    505  } else {
    506    rv = NS_DispatchToMainThread(runnable);
    507  }
    508  if (NS_FAILED(rv)) {
    509    NS_WARNING(
    510        "Dispatch to main thread Failed in "
    511        "DataTransferItem::GetAsString!");
    512  }
    513 }
    514 
    515 already_AddRefed<nsIVariant> DataTransferItem::DataNoSecurityCheck() {
    516  if (!mData) {
    517    FillInExternalData();
    518  }
    519  nsCOMPtr<nsIVariant> data = mData;
    520  return data.forget();
    521 }
    522 
    523 already_AddRefed<nsIVariant> DataTransferItem::Data(nsIPrincipal* aPrincipal,
    524                                                    ErrorResult& aRv) {
    525  MOZ_ASSERT(aPrincipal);
    526 
    527  // If the inbound principal is system, we can skip the below checks, as
    528  // they will trivially succeed.
    529  if (aPrincipal->IsSystemPrincipal()) {
    530    return DataNoSecurityCheck();
    531  }
    532 
    533  // We should not allow raw data to be accessed from a Protected DataTransfer.
    534  // We don't prevent this access if the accessing document is Chrome.
    535  if (mDataTransfer->IsProtected()) {
    536    return nullptr;
    537  }
    538 
    539  nsCOMPtr<nsIVariant> variant = DataNoSecurityCheck();
    540 
    541  MOZ_ASSERT(!ChromeOnly(),
    542             "Non-chrome code shouldn't see a ChromeOnly DataTransferItem");
    543  if (ChromeOnly()) {
    544    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
    545    return nullptr;
    546  }
    547 
    548  bool checkItemPrincipal = mDataTransfer->IsCrossDomainSubFrameDrop() ||
    549                            (mDataTransfer->GetEventMessage() != eDrop &&
    550                             mDataTransfer->GetEventMessage() != ePaste &&
    551                             mDataTransfer->GetEventMessage() != eEditorInput);
    552 
    553  // Check if the caller is allowed to access the drag data. Callers with
    554  // chrome privileges can always read the data. During the
    555  // drop event, allow retrieving the data except in the case where the
    556  // source of the drag is in a child frame of the caller. In that case,
    557  // we only allow access to data of the same principal. During other events,
    558  // only allow access to the data with the same principal.
    559  //
    560  // We don't want to fail with an exception in this siutation, rather we want
    561  // to just pretend as though the stored data is "nullptr". This is consistent
    562  // with Chrome's behavior and is less surprising for web applications which
    563  // don't expect execptions to be raised when performing certain operations.
    564  if (Principal() && checkItemPrincipal && !aPrincipal->Subsumes(Principal())) {
    565    return nullptr;
    566  }
    567 
    568  if (!variant) {
    569    return nullptr;
    570  }
    571 
    572  nsCOMPtr<nsISupports> data;
    573  nsresult rv = variant->GetAsISupports(getter_AddRefs(data));
    574  if (NS_SUCCEEDED(rv) && data) {
    575    nsCOMPtr<EventTarget> pt = do_QueryInterface(data);
    576    if (pt) {
    577      nsIGlobalObject* go = pt->GetOwnerGlobal();
    578      if (NS_WARN_IF(!go)) {
    579        return nullptr;
    580      }
    581 
    582      nsCOMPtr<nsIScriptObjectPrincipal> sp = do_QueryInterface(go);
    583      MOZ_ASSERT(sp, "This cannot fail on the main thread.");
    584 
    585      nsIPrincipal* dataPrincipal = sp->GetPrincipal();
    586      if (NS_WARN_IF(!dataPrincipal || !aPrincipal->Equals(dataPrincipal))) {
    587        return nullptr;
    588      }
    589    }
    590  }
    591 
    592  return variant.forget();
    593 }
    594 
    595 }  // namespace mozilla::dom