tor-browser

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

FileSystemWritableFileStream.cpp (35545B)


      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 "FileSystemWritableFileStream.h"
      8 
      9 #include "fs/FileSystemAsyncCopy.h"
     10 #include "fs/FileSystemShutdownBlocker.h"
     11 #include "fs/FileSystemThreadSafeStreamOwner.h"
     12 #include "mozilla/ErrorResult.h"
     13 #include "mozilla/InputStreamLengthHelper.h"
     14 #include "mozilla/MozPromise.h"
     15 #include "mozilla/SpinEventLoopUntil.h"
     16 #include "mozilla/TaskQueue.h"
     17 #include "mozilla/dom/Blob.h"
     18 #include "mozilla/dom/FileSystemHandle.h"
     19 #include "mozilla/dom/FileSystemLog.h"
     20 #include "mozilla/dom/FileSystemManager.h"
     21 #include "mozilla/dom/FileSystemWritableFileStreamBinding.h"
     22 #include "mozilla/dom/FileSystemWritableFileStreamChild.h"
     23 #include "mozilla/dom/Promise.h"
     24 #include "mozilla/dom/PromiseNativeHandler.h"
     25 #include "mozilla/dom/WorkerCommon.h"
     26 #include "mozilla/dom/WorkerPrivate.h"
     27 #include "mozilla/dom/WorkerRef.h"
     28 #include "mozilla/dom/WritableStreamDefaultController.h"
     29 #include "mozilla/dom/quota/QuotaCommon.h"
     30 #include "mozilla/dom/quota/ResultExtensions.h"
     31 #include "mozilla/dom/quota/TargetPtrHolder.h"
     32 #include "mozilla/ipc/RandomAccessStreamUtils.h"
     33 #include "nsAsyncStreamCopier.h"
     34 #include "nsIInputStream.h"
     35 #include "nsIRequestObserver.h"
     36 #include "nsISupportsImpl.h"
     37 #include "nsNetCID.h"
     38 #include "nsNetUtil.h"
     39 #include "nsStreamUtils.h"
     40 #include "nsStringStream.h"
     41 
     42 namespace mozilla::dom {
     43 
     44 namespace {
     45 
     46 CopyableErrorResult RejectWithConvertedErrors(nsresult aRv) {
     47  CopyableErrorResult err;
     48  switch (aRv) {
     49    case NS_ERROR_DOM_FILE_NOT_FOUND_ERR:
     50      [[fallthrough]];
     51    case NS_ERROR_FILE_NOT_FOUND:
     52      err.ThrowNotFoundError("File not found");
     53      break;
     54    case NS_ERROR_FILE_NO_DEVICE_SPACE:
     55      err.ThrowQuotaExceededError("Quota exceeded");
     56      break;
     57    default:
     58      err.Throw(aRv);
     59  }
     60 
     61  return err;
     62 }
     63 
     64 RefPtr<FileSystemWritableFileStream::WriteDataPromise> ResolvePromise(
     65    const Int64Promise::ResolveOrRejectValue& aValue) {
     66  MOZ_ASSERT(aValue.IsResolve());
     67  return FileSystemWritableFileStream::WriteDataPromise::CreateAndResolve(
     68      Some(aValue.ResolveValue()), __func__);
     69 }
     70 
     71 RefPtr<FileSystemWritableFileStream::WriteDataPromise> ResolvePromise(
     72    const BoolPromise::ResolveOrRejectValue& aValue) {
     73  MOZ_ASSERT(aValue.IsResolve());
     74  return FileSystemWritableFileStream::WriteDataPromise::CreateAndResolve(
     75      Nothing(), __func__);
     76 }
     77 
     78 class WritableFileStreamUnderlyingSinkAlgorithms final
     79    : public UnderlyingSinkAlgorithmsWrapper {
     80  NS_DECL_ISUPPORTS_INHERITED
     81  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(
     82      WritableFileStreamUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase)
     83 
     84  explicit WritableFileStreamUnderlyingSinkAlgorithms(
     85      FileSystemWritableFileStream& aStream)
     86      : mStream(&aStream) {}
     87 
     88  already_AddRefed<Promise> WriteCallbackImpl(
     89      JSContext* aCx, JS::Handle<JS::Value> aChunk,
     90      WritableStreamDefaultController& aController, ErrorResult& aRv) override;
     91 
     92  already_AddRefed<Promise> CloseCallbackImpl(JSContext* aCx,
     93                                              ErrorResult& aRv) override;
     94 
     95  already_AddRefed<Promise> AbortCallbackImpl(
     96      JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
     97      ErrorResult& aRv) override;
     98 
     99  void ReleaseObjects() override;
    100 
    101 private:
    102  ~WritableFileStreamUnderlyingSinkAlgorithms() = default;
    103 
    104  RefPtr<FileSystemWritableFileStream> mStream;
    105 };
    106 
    107 }  // namespace
    108 
    109 class FileSystemWritableFileStream::Command {
    110 public:
    111  explicit Command(RefPtr<FileSystemWritableFileStream> aWritableFileStream)
    112      : mWritableFileStream(std::move(aWritableFileStream)) {
    113    MOZ_ASSERT(mWritableFileStream);
    114  }
    115 
    116  NS_INLINE_DECL_REFCOUNTING(FileSystemWritableFileStream::Command)
    117 
    118 private:
    119  ~Command() { mWritableFileStream->NoteFinishedCommand(); }
    120 
    121  RefPtr<FileSystemWritableFileStream> mWritableFileStream;
    122 };
    123 
    124 class FileSystemWritableFileStream::CloseHandler {
    125  enum struct State : uint8_t { Initial = 0, Open, Closing, Closed };
    126 
    127 public:
    128  CloseHandler()
    129      : mShutdownBlocker(fs::FileSystemShutdownBlocker::CreateForWritable()),
    130        mClosePromiseHolder(),
    131        mState(State::Initial) {}
    132 
    133  NS_INLINE_DECL_REFCOUNTING(FileSystemWritableFileStream::CloseHandler)
    134 
    135  /**
    136   * @brief Are we not yet closing?
    137   */
    138  bool IsOpen() const { return State::Open == mState; }
    139 
    140  /**
    141   * @brief Are we not open and not closed?
    142   */
    143  bool IsClosing() const { return State::Closing == mState; }
    144 
    145  /**
    146   * @brief Are we already fully closed?
    147   */
    148  bool IsClosed() const { return State::Closed == mState; }
    149 
    150  /**
    151   * @brief Transition from open to closing state
    152   *
    153   * @return true if the state was open and became closing after the call
    154   * @return false in all the other cases the previous state is preserved
    155   */
    156  bool SetClosing() {
    157    const bool isOpen = State::Open == mState;
    158 
    159    if (isOpen) {
    160      mState = State::Closing;
    161    }
    162 
    163    return isOpen;
    164  }
    165 
    166  RefPtr<BoolPromise> GetClosePromise() const {
    167    MOZ_ASSERT(State::Open != mState,
    168               "Please call SetClosing before GetClosePromise");
    169 
    170    if (State::Closing == mState) {
    171      return mClosePromiseHolder.Ensure(__func__);
    172    }
    173 
    174    // Instant resolve for initial state due to early shutdown or closed state
    175    return BoolPromise::CreateAndResolve(true, __func__);
    176  }
    177 
    178  /**
    179   * @brief Transition from initial to open state. In initial state
    180   *
    181   */
    182  void Open(std::function<void()>&& aCallback) {
    183    MOZ_ASSERT(State::Initial == mState);
    184 
    185    mShutdownBlocker->SetCallback(std::move(aCallback));
    186    mShutdownBlocker->Block();
    187 
    188    mState = State::Open;
    189  }
    190 
    191  /**
    192   * @brief Transition to closed state and resolve all pending promises.
    193   *
    194   */
    195  void Close() {
    196    mShutdownBlocker->Unblock();
    197    mShutdownBlocker = nullptr;
    198    mState = State::Closed;
    199    mClosePromiseHolder.ResolveIfExists(true, __func__);
    200  }
    201 
    202 protected:
    203  virtual ~CloseHandler() = default;
    204 
    205 private:
    206  RefPtr<fs::FileSystemShutdownBlocker> mShutdownBlocker;
    207 
    208  mutable MozPromiseHolder<BoolPromise> mClosePromiseHolder;
    209 
    210  State mState;
    211 };
    212 
    213 FileSystemWritableFileStream::FileSystemWritableFileStream(
    214    const nsCOMPtr<nsIGlobalObject>& aGlobal,
    215    RefPtr<FileSystemManager>& aManager,
    216    mozilla::ipc::RandomAccessStreamParams&& aStreamParams,
    217    RefPtr<FileSystemWritableFileStreamChild> aActor,
    218    already_AddRefed<TaskQueue> aTaskQueue,
    219    const fs::FileSystemEntryMetadata& aMetadata)
    220    : WritableStream(aGlobal, HoldDropJSObjectsCaller::Explicit),
    221      mManager(aManager),
    222      mActor(std::move(aActor)),
    223      mTaskQueue(aTaskQueue),
    224      mStreamParams(std::move(aStreamParams)),
    225      mMetadata(std::move(aMetadata)),
    226      mCloseHandler(MakeAndAddRef<CloseHandler>()),
    227      mCommandActive(false) {
    228  LOG(("Created WritableFileStream %p", this));
    229 
    230  // Connect with the actor directly in the constructor. This way the actor
    231  // can call `FileSystemWritableFileStream::ClearActor` when we call
    232  // `PFileSystemWritableFileStreamChild::Send__delete__` even when
    233  // FileSystemWritableFileStream::Create fails, in which case the not yet
    234  // fully constructed FileSystemWritableFileStream is being destroyed.
    235  mActor->SetStream(this);
    236 
    237  mozilla::HoldJSObjects(this);
    238 }
    239 
    240 FileSystemWritableFileStream::~FileSystemWritableFileStream() {
    241  MOZ_ASSERT(!mCommandActive);
    242  MOZ_ASSERT(IsDone());
    243 
    244  mozilla::DropJSObjects(this);
    245 }
    246 
    247 // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream
    248 // * This is fallible because of OOM handling of JSAPI. See bug 1762233.
    249 // XXX(krosylight): _BOUNDARY because SetUpNative here can't run script because
    250 // StartCallback here is no-op. Can we let the static check automatically detect
    251 // this situation?
    252 /* static */
    253 MOZ_CAN_RUN_SCRIPT_BOUNDARY
    254 Result<RefPtr<FileSystemWritableFileStream>, nsresult>
    255 FileSystemWritableFileStream::Create(
    256    const nsCOMPtr<nsIGlobalObject>& aGlobal,
    257    RefPtr<FileSystemManager>& aManager,
    258    mozilla::ipc::RandomAccessStreamParams&& aStreamParams,
    259    RefPtr<FileSystemWritableFileStreamChild> aActor,
    260    const fs::FileSystemEntryMetadata& aMetadata) {
    261  MOZ_ASSERT(aGlobal);
    262 
    263  QM_TRY_UNWRAP(auto streamTransportService,
    264                MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIEventTarget>,
    265                                        MOZ_SELECT_OVERLOAD(do_GetService),
    266                                        NS_STREAMTRANSPORTSERVICE_CONTRACTID));
    267 
    268  RefPtr<TaskQueue> taskQueue =
    269      TaskQueue::Create(streamTransportService.forget(), "WritableStreamQueue");
    270  MOZ_ASSERT(taskQueue);
    271 
    272  AutoJSAPI jsapi;
    273  if (!jsapi.Init(aGlobal)) {
    274    return Err(NS_ERROR_FAILURE);
    275  }
    276  JSContext* cx = jsapi.cx();
    277 
    278  // Step 1. Let stream be a new FileSystemWritableFileStream in realm.
    279  // Step 2. Set stream.[[file]] to file. (Covered by the constructor)
    280  RefPtr<FileSystemWritableFileStream> stream =
    281      new FileSystemWritableFileStream(
    282          aGlobal, aManager, std::move(aStreamParams), std::move(aActor),
    283          taskQueue.forget(), aMetadata);
    284 
    285  auto autoClose = MakeScopeExit([stream] {
    286    stream->mCloseHandler->Close();
    287    stream->mActor->SendClose(/* aAbort */ true);
    288  });
    289 
    290  QM_TRY_UNWRAP(
    291      RefPtr<StrongWorkerRef> workerRef,
    292      ([stream]() -> Result<RefPtr<StrongWorkerRef>, nsresult> {
    293        WorkerPrivate* const workerPrivate = GetCurrentThreadWorkerPrivate();
    294        if (!workerPrivate) {
    295          return RefPtr<StrongWorkerRef>();
    296        }
    297 
    298        RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
    299            workerPrivate, "FileSystemWritableFileStream::Create", [stream]() {
    300              if (stream->IsOpen()) {
    301                // We don't need the promise, we just
    302                // begin the closing process.
    303                (void)stream->BeginAbort();
    304              }
    305            });
    306        QM_TRY(MOZ_TO_RESULT(workerRef));
    307 
    308        return workerRef;
    309      }()));
    310 
    311  // Step 3 - 5
    312  auto algorithms =
    313      MakeRefPtr<WritableFileStreamUnderlyingSinkAlgorithms>(*stream);
    314 
    315  // Step 8: Set up stream with writeAlgorithm set to writeAlgorithm,
    316  // closeAlgorithm set to closeAlgorithm, abortAlgorithm set to
    317  // abortAlgorithm, highWaterMark set to highWaterMark, and
    318  // sizeAlgorithm set to sizeAlgorithm.
    319  IgnoredErrorResult rv;
    320  stream->SetUpNative(cx, *algorithms,
    321                      // Step 6. Let highWaterMark be 1.
    322                      Some(1),
    323                      // Step 7. Let sizeAlgorithm be an algorithm
    324                      // that returns 1. (nullptr returns 1, See
    325                      // WritableStream::Constructor for details)
    326                      nullptr, rv);
    327  if (rv.Failed()) {
    328    return Err(rv.StealNSResult());
    329  }
    330 
    331  autoClose.release();
    332 
    333  stream->mWorkerRef = std::move(workerRef);
    334 
    335  // The close handler passes this callback to `FileSystemShutdownBlocker`
    336  // which has separate handling for debug and release builds. Basically,
    337  // there's no content process shutdown blocking in release builds, so the
    338  // callback might be executed in debug builds only.
    339  stream->mCloseHandler->Open([stream]() {
    340    if (stream->IsOpen()) {
    341      // We don't need the promise, we just begin the closing process.
    342      (void)stream->BeginAbort();
    343    }
    344  });
    345 
    346  // Step 9: Return stream.
    347  return stream;
    348 }
    349 
    350 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(FileSystemWritableFileStream,
    351                                               WritableStream)
    352 
    353 NS_IMPL_CYCLE_COLLECTION_CLASS(FileSystemWritableFileStream)
    354 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FileSystemWritableFileStream,
    355                                                WritableStream)
    356  // Per the comment for the FileSystemManager class, don't unlink mManager!
    357  if (tmp->IsOpen()) {
    358    (void)tmp->BeginAbort();
    359  }
    360 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
    361 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FileSystemWritableFileStream,
    362                                                  WritableStream)
    363  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mManager)
    364 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
    365 
    366 void FileSystemWritableFileStream::LastRelease() {
    367  // We can't call `FileSystemWritableFileStream::Close` here because it may
    368  // need to keep FileSystemWritableFileStream object alive which isn't possible
    369  // when the object is about to be deleted. There are other mechanisms which
    370  // ensure that the object is correctly closed before destruction. For example
    371  // the object unlinking and the worker shutdown (we get notified about it via
    372  // the callback passed to `StrongWorkerRef`) are used to close the object if
    373  // it hasn't been closed yet.
    374 
    375  if (mActor) {
    376    PFileSystemWritableFileStreamChild::Send__delete__(mActor);
    377    MOZ_ASSERT(!mActor);
    378  }
    379 }
    380 
    381 RefPtr<FileSystemWritableFileStream::Command>
    382 FileSystemWritableFileStream::CreateCommand() {
    383  MOZ_ASSERT(!mCommandActive);
    384 
    385  mCommandActive = true;
    386 
    387  return MakeRefPtr<Command>(this);
    388 }
    389 
    390 bool FileSystemWritableFileStream::IsCommandActive() const {
    391  return mCommandActive;
    392 }
    393 
    394 void FileSystemWritableFileStream::ClearActor() {
    395  MOZ_ASSERT(mActor);
    396 
    397  mActor = nullptr;
    398 }
    399 
    400 bool FileSystemWritableFileStream::IsOpen() const {
    401  return mCloseHandler->IsOpen();
    402 }
    403 
    404 bool FileSystemWritableFileStream::IsFinishing() const {
    405  return mCloseHandler->IsClosing();
    406 }
    407 
    408 bool FileSystemWritableFileStream::IsDone() const {
    409  return mCloseHandler->IsClosed();
    410 }
    411 
    412 RefPtr<BoolPromise> FileSystemWritableFileStream::BeginFinishing(
    413    bool aShouldAbort) {
    414  using ClosePromise = PFileSystemWritableFileStreamChild::ClosePromise;
    415  MOZ_ASSERT(IsOpen());
    416 
    417  if (mCloseHandler->SetClosing()) {
    418    Finish()
    419        ->Then(mTaskQueue, __func__,
    420               [selfHolder = quota::TargetPtrHolder(this)]() mutable {
    421                 if (selfHolder->mStreamOwner) {
    422                   selfHolder->mStreamOwner->Close();
    423                 } else {
    424                   // If the stream was not deserialized, `mStreamParams` still
    425                   // contains a pre-opened file descriptor which needs to be
    426                   // closed here by moving `mStreamParams` to a local variable
    427                   // (the file descriptor will be closed for real when
    428                   // `streamParams` goes out of scope).
    429 
    430                   mozilla::ipc::RandomAccessStreamParams streamParams(
    431                       std::move(selfHolder->mStreamParams));
    432                 }
    433 
    434                 return BoolPromise::CreateAndResolve(true, __func__);
    435               })
    436        ->Then(GetCurrentSerialEventTarget(), __func__,
    437               [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue&) {
    438                 return self->mTaskQueue->BeginShutdown();
    439               })
    440        ->Then(GetCurrentSerialEventTarget(), __func__,
    441               [aShouldAbort, self = RefPtr(this)](
    442                   const ShutdownPromise::ResolveOrRejectValue& /* aValue */) {
    443                 if (!self->mActor) {
    444                   return ClosePromise::CreateAndResolve(void_t(), __func__);
    445                 }
    446 
    447                 return self->mActor->SendClose(aShouldAbort);
    448               })
    449        ->Then(GetCurrentSerialEventTarget(), __func__,
    450               [self = RefPtr(this)](
    451                   const ClosePromise::ResolveOrRejectValue& aValue) {
    452                 self->mWorkerRef = nullptr;
    453                 self->mCloseHandler->Close();
    454 
    455                 QM_TRY(OkIf(aValue.IsResolve()), QM_VOID);
    456               });
    457  }
    458 
    459  return mCloseHandler->GetClosePromise();
    460 }
    461 
    462 RefPtr<BoolPromise> FileSystemWritableFileStream::BeginClose() {
    463  MOZ_ASSERT(IsOpen());
    464  return BeginFinishing(/* aShouldAbort */ false);
    465 }
    466 
    467 RefPtr<BoolPromise> FileSystemWritableFileStream::BeginAbort() {
    468  MOZ_ASSERT(IsOpen());
    469  return BeginFinishing(/* aShouldAbort */ true);
    470 }
    471 
    472 RefPtr<BoolPromise> FileSystemWritableFileStream::OnDone() {
    473  MOZ_ASSERT(!IsOpen());
    474 
    475  return mCloseHandler->GetClosePromise();
    476 }
    477 
    478 already_AddRefed<Promise> FileSystemWritableFileStream::Write(
    479    JSContext* aCx, JS::Handle<JS::Value> aChunk, ErrorResult& aError) {
    480  // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream
    481  // Step 3. Let writeAlgorithm be an algorithm which takes a chunk argument
    482  // and returns the result of running the write a chunk algorithm with stream
    483  // and chunk.
    484 
    485  aError.MightThrowJSException();
    486 
    487  // https://fs.spec.whatwg.org/#write-a-chunk
    488  // Step 1. Let input be the result of converting chunk to a
    489  // FileSystemWriteChunkType.
    490 
    491  ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams data;
    492  if (!data.Init(aCx, aChunk)) {
    493    aError.StealExceptionFromJSContext(aCx);
    494    // XXX(krosylight): The Streams spec does not provide a way to catch errors
    495    // thrown from the write algorithm, and the File System spec does not try
    496    // catching them either. This is unfortunate, as per the spec the type error
    497    // from write() must immediately return a rejected promise without any file
    498    // handle closure. For now we handle it here manually. See also:
    499    // - https://github.com/whatwg/streams/issues/636
    500    // - https://github.com/whatwg/fs/issues/153
    501    if (IsOpen()) {
    502      (void)BeginAbort();
    503    }
    504    return nullptr;
    505  }
    506 
    507  // Step 2. Let p be a new promise.
    508  RefPtr<Promise> promise = Promise::CreateInfallible(GetParentObject());
    509 
    510  RefPtr<Command> command = CreateCommand();
    511 
    512  // Step 3.3.
    513  // XXX: This should ideally be also handled by the streams but we don't
    514  // currently have the hook. See https://github.com/whatwg/streams/issues/620.
    515  Write(data)->Then(
    516      GetCurrentSerialEventTarget(), __func__,
    517      [self = RefPtr{this}, command,
    518       promise](const WriteDataPromise::ResolveOrRejectValue& aValue) {
    519        MOZ_ASSERT(!aValue.IsNothing());
    520        if (aValue.IsResolve()) {
    521          const Maybe<int64_t>& maybeWritten = aValue.ResolveValue();
    522          if (maybeWritten.isSome()) {
    523            promise->MaybeResolve(maybeWritten.value());
    524            return;
    525          }
    526 
    527          promise->MaybeResolveWithUndefined();
    528          return;
    529        }
    530 
    531        CopyableErrorResult err = aValue.RejectValue();
    532 
    533        if (self->IsOpen()) {
    534          self->BeginAbort()->Then(
    535              GetCurrentSerialEventTarget(), __func__,
    536              [promise, err = std::move(err)](
    537                  const BoolPromise::ResolveOrRejectValue&) mutable {
    538                // Do not capture command to this context:
    539                // close cannot proceed
    540                promise->MaybeReject(std::move(err));
    541              });
    542        } else if (self->IsFinishing()) {
    543          self->OnDone()->Then(
    544              GetCurrentSerialEventTarget(), __func__,
    545              [promise, err = std::move(err)](
    546                  const BoolPromise::ResolveOrRejectValue&) mutable {
    547                // Do not capture command to this context:
    548                // close cannot proceed
    549                promise->MaybeReject(std::move(err));
    550              });
    551 
    552        } else {
    553          promise->MaybeReject(std::move(err));
    554        }
    555      });
    556 
    557  return promise.forget();
    558 }
    559 
    560 RefPtr<FileSystemWritableFileStream::WriteDataPromise>
    561 FileSystemWritableFileStream::Write(
    562    ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams& aData) {
    563  auto rejectWithTypeError = [](const auto& aMessage) {
    564    CopyableErrorResult err;
    565    err.ThrowTypeError(aMessage);
    566    return WriteDataPromise::CreateAndReject(err, __func__);
    567  };
    568 
    569  auto rejectWithSyntaxError = [](const auto& aMessage) {
    570    CopyableErrorResult err;
    571    err.ThrowSyntaxError(aMessage);
    572    return WriteDataPromise::CreateAndReject(err, __func__);
    573  };
    574 
    575  if (!IsOpen()) {
    576    return rejectWithTypeError("WritableFileStream closed");
    577  }
    578 
    579  auto tryResolve = [self = RefPtr{this}](const auto& aValue)
    580      -> RefPtr<FileSystemWritableFileStream::WriteDataPromise> {
    581    MOZ_ASSERT(self->IsCommandActive());
    582 
    583    if (aValue.IsResolve()) {
    584      return ResolvePromise(aValue);
    585    }
    586 
    587    MOZ_ASSERT(aValue.IsReject());
    588    return WriteDataPromise::CreateAndReject(
    589        RejectWithConvertedErrors(aValue.RejectValue()), __func__);
    590  };
    591 
    592  auto tryResolveInt64 =
    593      [tryResolve](const Int64Promise::ResolveOrRejectValue& aValue) {
    594        return tryResolve(aValue);
    595      };
    596 
    597  auto tryResolveBool =
    598      [tryResolve](const BoolPromise::ResolveOrRejectValue& aValue) {
    599        return tryResolve(aValue);
    600      };
    601 
    602  // Step 3.3. Let command be input.type if input is a WriteParams, ...
    603  if (aData.IsWriteParams()) {
    604    const WriteParams& params = aData.GetAsWriteParams();
    605    switch (params.mType) {
    606      // Step 3.4. If command is "write":
    607      case WriteCommandType::Write: {
    608        if (!params.mData.WasPassed()) {
    609          return rejectWithSyntaxError("write() requires data");
    610        }
    611 
    612        // Step 3.4.2. If data is undefined, reject p with a TypeError and
    613        // abort.
    614        if (params.mData.Value().IsNull()) {
    615          return rejectWithTypeError("write() of null data");
    616        }
    617 
    618        Maybe<uint64_t> position;
    619 
    620        if (params.mPosition.WasPassed()) {
    621          if (params.mPosition.Value().IsNull()) {
    622            return rejectWithTypeError("write() with null position");
    623          }
    624 
    625          position = Some(params.mPosition.Value().Value());
    626        }
    627 
    628        return Write(params.mData.Value().Value(), position)
    629            ->Then(GetCurrentSerialEventTarget(), __func__,
    630                   std::move(tryResolveInt64));
    631      }
    632 
    633      // Step 3.5. Otherwise, if command is "seek":
    634      case WriteCommandType::Seek:
    635        if (!params.mPosition.WasPassed()) {
    636          return rejectWithSyntaxError("seek() requires a position");
    637        }
    638 
    639        // Step 3.5.1. If chunk.position is undefined, reject p with a
    640        // TypeError and abort.
    641        if (params.mPosition.Value().IsNull()) {
    642          return rejectWithTypeError("seek() with null position");
    643        }
    644 
    645        return Seek(params.mPosition.Value().Value())
    646            ->Then(GetCurrentSerialEventTarget(), __func__,
    647                   std::move(tryResolveBool));
    648 
    649      // Step 3.6. Otherwise, if command is "truncate":
    650      case WriteCommandType::Truncate:
    651        if (!params.mSize.WasPassed()) {
    652          return rejectWithSyntaxError("truncate() requires a size");
    653        }
    654 
    655        // Step 3.6.1. If chunk.size is undefined, reject p with a TypeError
    656        // and abort.
    657        if (params.mSize.Value().IsNull()) {
    658          return rejectWithTypeError("truncate() with null size");
    659        }
    660 
    661        return Truncate(params.mSize.Value().Value())
    662            ->Then(GetCurrentSerialEventTarget(), __func__,
    663                   std::move(tryResolveBool));
    664 
    665      default:
    666        MOZ_CRASH("Bad WriteParams value!");
    667    }
    668  }
    669 
    670  // Step 3.3. ... and "write" otherwise.
    671  // Step 3.4. If command is "write":
    672  return Write(aData, Nothing())
    673      ->Then(GetCurrentSerialEventTarget(), __func__,
    674             std::move(tryResolveInt64));
    675 }
    676 
    677 // WebIDL Boilerplate
    678 
    679 JSObject* FileSystemWritableFileStream::WrapObject(
    680    JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
    681  return FileSystemWritableFileStream_Binding::Wrap(aCx, this, aGivenProto);
    682 }
    683 
    684 // WebIDL Interface
    685 
    686 already_AddRefed<Promise> FileSystemWritableFileStream::Write(
    687    const ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams& aData,
    688    ErrorResult& aError) {
    689  // https://fs.spec.whatwg.org/#dom-filesystemwritablefilestream-write
    690  // Step 1. Let writer be the result of getting a writer for this.
    691  RefPtr<WritableStreamDefaultWriter> writer = GetWriter(aError);
    692  if (aError.Failed()) {
    693    return nullptr;
    694  }
    695 
    696  // Step 2. Let result be the result of writing a chunk to writer given data.
    697  AutoJSAPI jsapi;
    698  if (!jsapi.Init(GetParentObject())) {
    699    aError.ThrowUnknownError("Internal error");
    700    return nullptr;
    701  }
    702 
    703  JSContext* cx = jsapi.cx();
    704 
    705  JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
    706 
    707  JS::Rooted<JS::Value> val(cx);
    708  if (!aData.ToJSVal(cx, global, &val)) {
    709    aError.ThrowUnknownError("Internal error");
    710    return nullptr;
    711  }
    712 
    713  RefPtr<Promise> promise = writer->Write(cx, val, aError);
    714 
    715  // Step 3. Release writer.
    716  writer->ReleaseLock(cx);
    717 
    718  // Step 4. Return result.
    719  return promise.forget();
    720 }
    721 
    722 already_AddRefed<Promise> FileSystemWritableFileStream::Seek(
    723    uint64_t aPosition, ErrorResult& aError) {
    724  // https://fs.spec.whatwg.org/#dom-filesystemwritablefilestream-seek
    725  // Step 1. Let writer be the result of getting a writer for this.
    726  RefPtr<WritableStreamDefaultWriter> writer = GetWriter(aError);
    727  if (aError.Failed()) {
    728    return nullptr;
    729  }
    730 
    731  // Step 2. Let result be the result of writing a chunk to writer given
    732  // «[ "type" → "seek", "position" → position ]».
    733  AutoJSAPI jsapi;
    734  if (!jsapi.Init(GetParentObject())) {
    735    aError.ThrowUnknownError("Internal error");
    736    return nullptr;
    737  }
    738 
    739  JSContext* cx = jsapi.cx();
    740 
    741  RootedDictionary<WriteParams> writeParams(cx);
    742  writeParams.mType = WriteCommandType::Seek;
    743  writeParams.mPosition.Construct(aPosition);
    744 
    745  JS::Rooted<JS::Value> val(cx);
    746  if (!ToJSValue(cx, writeParams, &val)) {
    747    aError.ThrowUnknownError("Internal error");
    748    return nullptr;
    749  }
    750 
    751  RefPtr<Promise> promise = writer->Write(cx, val, aError);
    752 
    753  // Step 3. Release writer.
    754  writer->ReleaseLock(cx);
    755 
    756  // Step 4. Return result.
    757  return promise.forget();
    758 }
    759 
    760 already_AddRefed<Promise> FileSystemWritableFileStream::Truncate(
    761    uint64_t aSize, ErrorResult& aError) {
    762  // https://fs.spec.whatwg.org/#dom-filesystemwritablefilestream-truncate
    763  // Step 1. Let writer be the result of getting a writer for this.
    764  RefPtr<WritableStreamDefaultWriter> writer = GetWriter(aError);
    765  if (aError.Failed()) {
    766    return nullptr;
    767  }
    768 
    769  // Step 2. Let result be the result of writing a chunk to writer given
    770  // «[ "type" → "truncate", "size" → size ]».
    771  AutoJSAPI jsapi;
    772  if (!jsapi.Init(GetParentObject())) {
    773    aError.ThrowUnknownError("Internal error");
    774    return nullptr;
    775  }
    776 
    777  JSContext* cx = jsapi.cx();
    778 
    779  RootedDictionary<WriteParams> writeParams(cx);
    780  writeParams.mType = WriteCommandType::Truncate;
    781  writeParams.mSize.Construct(aSize);
    782 
    783  JS::Rooted<JS::Value> val(cx);
    784  if (!ToJSValue(cx, writeParams, &val)) {
    785    aError.ThrowUnknownError("Internal error");
    786    return nullptr;
    787  }
    788 
    789  RefPtr<Promise> promise = writer->Write(cx, val, aError);
    790 
    791  // Step 3. Release writer.
    792  writer->ReleaseLock(cx);
    793 
    794  // Step 4. Return result.
    795  return promise.forget();
    796 }
    797 
    798 template <typename T>
    799 RefPtr<Int64Promise> FileSystemWritableFileStream::Write(
    800    const T& aData, const Maybe<uint64_t> aPosition) {
    801  MOZ_ASSERT(IsOpen());
    802 
    803  nsCOMPtr<nsIInputStream> inputStream;
    804 
    805  // https://fs.spec.whatwg.org/#write-a-chunk
    806  // Step 3.4.6 If data is a BufferSource, let dataBytes be a copy of data.
    807  auto vectorFromTypedArray = CreateFromTypedArrayData<Vector<uint8_t>>(aData);
    808  if (vectorFromTypedArray.isSome()) {
    809    Maybe<Vector<uint8_t>>& maybeVector = vectorFromTypedArray.ref();
    810    QM_TRY(MOZ_TO_RESULT(maybeVector.isSome()), CreateAndRejectInt64Promise);
    811 
    812    // Here we copy
    813 
    814    size_t length = maybeVector->length();
    815    QM_TRY(MOZ_TO_RESULT(NS_NewByteInputStream(
    816               getter_AddRefs(inputStream),
    817               AsChars(Span(maybeVector->extractOrCopyRawBuffer(), length)),
    818               NS_ASSIGNMENT_ADOPT)),
    819           CreateAndRejectInt64Promise);
    820 
    821    return WriteImpl(std::move(inputStream), aPosition);
    822  }
    823 
    824  // Step 3.4.7 Otherwise, if data is a Blob ...
    825  if (aData.IsBlob()) {
    826    Blob& blob = aData.GetAsBlob();
    827 
    828    ErrorResult error;
    829    blob.CreateInputStream(getter_AddRefs(inputStream), error);
    830    QM_TRY((MOZ_TO_RESULT(!error.Failed()).mapErr([&error](const nsresult rv) {
    831             return error.StealNSResult();
    832           })),
    833           CreateAndRejectInt64Promise);
    834 
    835    return WriteImpl(std::move(inputStream), aPosition);
    836  }
    837 
    838  // Step 3.4.8 Otherwise ...
    839  MOZ_ASSERT(aData.IsUTF8String());
    840 
    841  // Here we copy
    842  nsCString dataString;
    843  if (!dataString.Assign(aData.GetAsUTF8String(), mozilla::fallible)) {
    844    return Int64Promise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
    845  }
    846 
    847  // Input stream takes ownership
    848  QM_TRY(MOZ_TO_RESULT(NS_NewCStringInputStream(getter_AddRefs(inputStream),
    849                                                std::move(dataString))),
    850         CreateAndRejectInt64Promise);
    851 
    852  return WriteImpl(std::move(inputStream), aPosition);
    853 }
    854 
    855 RefPtr<Int64Promise> FileSystemWritableFileStream::WriteImpl(
    856    nsCOMPtr<nsIInputStream> aInputStream, const Maybe<uint64_t> aPosition) {
    857  return InvokeAsync(
    858      mTaskQueue, __func__,
    859      [selfHolder = quota::TargetPtrHolder(this),
    860       inputStream = std::move(aInputStream), aPosition]() {
    861        QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()),
    862               CreateAndRejectInt64Promise);
    863 
    864        if (aPosition.isSome()) {
    865          LOG(("%p: Seeking to %" PRIu64, selfHolder->mStreamOwner.get(),
    866               aPosition.value()));
    867 
    868          QM_TRY(
    869              MOZ_TO_RESULT(selfHolder->mStreamOwner->Seek(aPosition.value())),
    870              CreateAndRejectInt64Promise);
    871        }
    872 
    873        nsCOMPtr<nsIOutputStream> streamSink =
    874            selfHolder->mStreamOwner->OutputStream();
    875 
    876        auto written = std::make_shared<int64_t>(0);
    877        auto writingProgress = [written](uint32_t aDelta) {
    878          *written += static_cast<int64_t>(aDelta);
    879        };
    880 
    881        auto promiseHolder = MakeUnique<MozPromiseHolder<Int64Promise>>();
    882        RefPtr<Int64Promise> promise = promiseHolder->Ensure(__func__);
    883 
    884        auto writingCompletion =
    885            [written,
    886             promiseHolder = std::move(promiseHolder)](nsresult aStatus) {
    887              if (NS_SUCCEEDED(aStatus)) {
    888                promiseHolder->ResolveIfExists(*written, __func__);
    889                return;
    890              }
    891 
    892              promiseHolder->RejectIfExists(aStatus, __func__);
    893            };
    894 
    895        QM_TRY(MOZ_TO_RESULT(fs::AsyncCopy(
    896                   inputStream, streamSink, selfHolder->mTaskQueue,
    897                   nsAsyncCopyMode::NS_ASYNCCOPY_VIA_READSEGMENTS,
    898                   /* aCloseSource */ true, /* aCloseSink */ false,
    899                   std::move(writingProgress), std::move(writingCompletion))),
    900               CreateAndRejectInt64Promise);
    901 
    902        return promise;
    903      });
    904 }
    905 
    906 RefPtr<BoolPromise> FileSystemWritableFileStream::Seek(uint64_t aPosition) {
    907  MOZ_ASSERT(IsOpen());
    908 
    909  LOG_VERBOSE(("%p: Seeking to %" PRIu64, mStreamOwner.get(), aPosition));
    910 
    911  return InvokeAsync(
    912      mTaskQueue, __func__,
    913      [selfHolder = quota::TargetPtrHolder(this), aPosition]() mutable {
    914        QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()),
    915               CreateAndRejectBoolPromise);
    916 
    917        QM_TRY(MOZ_TO_RESULT(selfHolder->mStreamOwner->Seek(aPosition)),
    918               CreateAndRejectBoolPromise);
    919 
    920        return BoolPromise::CreateAndResolve(true, __func__);
    921      });
    922 }
    923 
    924 RefPtr<BoolPromise> FileSystemWritableFileStream::Truncate(uint64_t aSize) {
    925  MOZ_ASSERT(IsOpen());
    926 
    927  return InvokeAsync(
    928      mTaskQueue, __func__,
    929      [selfHolder = quota::TargetPtrHolder(this), aSize]() mutable {
    930        QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()),
    931               CreateAndRejectBoolPromise);
    932 
    933        QM_TRY(MOZ_TO_RESULT(selfHolder->mStreamOwner->Truncate(aSize)),
    934               CreateAndRejectBoolPromise);
    935 
    936        return BoolPromise::CreateAndResolve(true, __func__);
    937      });
    938 }
    939 
    940 nsresult FileSystemWritableFileStream::EnsureStream() {
    941  if (!mStreamOwner) {
    942    QM_TRY_UNWRAP(MovingNotNull<nsCOMPtr<nsIRandomAccessStream>> stream,
    943                  DeserializeRandomAccessStream(mStreamParams),
    944                  NS_ERROR_FAILURE);
    945 
    946    mozilla::ipc::RandomAccessStreamParams streamParams(
    947        std::move(mStreamParams));
    948 
    949    mStreamOwner = MakeRefPtr<fs::FileSystemThreadSafeStreamOwner>(
    950        this, std::move(stream));
    951  }
    952 
    953  return NS_OK;
    954 }
    955 
    956 void FileSystemWritableFileStream::NoteFinishedCommand() {
    957  MOZ_ASSERT(mCommandActive);
    958 
    959  mCommandActive = false;
    960 
    961  mFinishPromiseHolder.ResolveIfExists(true, __func__);
    962 }
    963 
    964 RefPtr<BoolPromise> FileSystemWritableFileStream::Finish() {
    965  if (!mCommandActive) {
    966    return BoolPromise::CreateAndResolve(true, __func__);
    967  }
    968 
    969  return mFinishPromiseHolder.Ensure(__func__);
    970 }
    971 
    972 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(
    973    WritableFileStreamUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase)
    974 NS_IMPL_CYCLE_COLLECTION_INHERITED(WritableFileStreamUnderlyingSinkAlgorithms,
    975                                   UnderlyingSinkAlgorithmsBase, mStream)
    976 
    977 // Step 3 of
    978 // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream
    979 already_AddRefed<Promise>
    980 WritableFileStreamUnderlyingSinkAlgorithms::WriteCallbackImpl(
    981    JSContext* aCx, JS::Handle<JS::Value> aChunk,
    982    WritableStreamDefaultController& aController, ErrorResult& aRv) {
    983  return mStream->Write(aCx, aChunk, aRv);
    984 }
    985 
    986 // Step 4 of
    987 // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream
    988 already_AddRefed<Promise>
    989 WritableFileStreamUnderlyingSinkAlgorithms::CloseCallbackImpl(
    990    JSContext* aCx, ErrorResult& aRv) {
    991  RefPtr<Promise> promise = Promise::Create(mStream->GetParentObject(), aRv);
    992  if (aRv.Failed()) {
    993    return nullptr;
    994  }
    995 
    996  if (!mStream->IsOpen()) {
    997    promise->MaybeRejectWithTypeError("WritableFileStream closed");
    998    return promise.forget();
    999  }
   1000 
   1001  mStream->BeginClose()->Then(
   1002      GetCurrentSerialEventTarget(), __func__,
   1003      [promise](const BoolPromise::ResolveOrRejectValue& aValue) {
   1004        // Step 2.3. Return a promise resolved with undefined.
   1005        if (aValue.IsResolve()) {
   1006          promise->MaybeResolveWithUndefined();
   1007          return;
   1008        }
   1009        promise->MaybeRejectWithAbortError(
   1010            "Internal error closing file stream");
   1011      });
   1012 
   1013  return promise.forget();
   1014 }
   1015 
   1016 // Step 5 of
   1017 // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream
   1018 already_AddRefed<Promise>
   1019 WritableFileStreamUnderlyingSinkAlgorithms::AbortCallbackImpl(
   1020    JSContext* aCx, const Optional<JS::Handle<JS::Value>>& /* aReason */,
   1021    ErrorResult& aRv) {
   1022  // https://streams.spec.whatwg.org/#writablestream-set-up
   1023  // Step 3. Let abortAlgorithmWrapper be an algorithm that runs these steps:
   1024  // Step 3.3. Return a promise resolved with undefined.
   1025 
   1026  RefPtr<Promise> promise = Promise::Create(mStream->GetParentObject(), aRv);
   1027  if (aRv.Failed()) {
   1028    return nullptr;
   1029  }
   1030 
   1031  if (!mStream->IsOpen()) {
   1032    promise->MaybeRejectWithTypeError("WritableFileStream closed");
   1033    return promise.forget();
   1034  }
   1035 
   1036  mStream->BeginAbort()->Then(
   1037      GetCurrentSerialEventTarget(), __func__,
   1038      [promise](const BoolPromise::ResolveOrRejectValue& aValue) {
   1039        // Step 2.3. Return a promise resolved with undefined.
   1040        if (aValue.IsResolve()) {
   1041          promise->MaybeResolveWithUndefined();
   1042          return;
   1043        }
   1044        promise->MaybeRejectWithAbortError(
   1045            "Internal error closing file stream");
   1046      });
   1047 
   1048  return promise.forget();
   1049 }
   1050 
   1051 void WritableFileStreamUnderlyingSinkAlgorithms::ReleaseObjects() {
   1052  // WritableStream transitions to errored state whenever a rejected promise is
   1053  // returned. At the end of the transition, ReleaseObjects is called.
   1054  // Because there is no way to release the locks synchronously,
   1055  // we assume this has been initiated before the rejected promise is returned.
   1056  MOZ_ASSERT(!mStream->IsOpen());
   1057 }
   1058 
   1059 }  // namespace mozilla::dom