/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "FileSystemWritableFileStream.h" #include "fs/FileSystemThreadSafeStreamOwner.h" #include "mozilla/Buffer.h" #include "mozilla/ErrorResult.h" #include "mozilla/InputStreamLengthHelper.h" #include "mozilla/MozPromise.h" #include "mozilla/SpinEventLoopUntil.h" #include "mozilla/TaskQueue.h" #include "mozilla/dom/Blob.h" #include "mozilla/dom/FileSystemHandle.h" #include "mozilla/dom/FileSystemLog.h" #include "mozilla/dom/FileSystemManager.h" #include "mozilla/dom/FileSystemWritableFileStreamBinding.h" #include "mozilla/dom/FileSystemWritableFileStreamChild.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/PromiseNativeHandler.h" #include "mozilla/dom/WorkerCommon.h" #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/dom/WorkerRef.h" #include "mozilla/dom/WritableStreamDefaultController.h" #include "mozilla/dom/quota/QuotaCommon.h" #include "mozilla/dom/quota/ResultExtensions.h" #include "mozilla/ipc/RandomAccessStreamUtils.h" #include "nsAsyncStreamCopier.h" #include "nsIInputStream.h" #include "nsIRequestObserver.h" #include "nsISupportsImpl.h" #include "nsNetCID.h" #include "nsNetUtil.h" #include "nsStreamUtils.h" #include "nsStringStream.h" namespace mozilla::dom { namespace { constexpr bool IsFileNotFoundError(const nsresult aRv) { return NS_ERROR_DOM_FILE_NOT_FOUND_ERR == aRv || NS_ERROR_FILE_NOT_FOUND == aRv; } class WritableFileStreamUnderlyingSinkAlgorithms final : public UnderlyingSinkAlgorithmsWrapper { NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED( WritableFileStreamUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase) explicit WritableFileStreamUnderlyingSinkAlgorithms( FileSystemWritableFileStream& aStream) : mStream(&aStream) {} already_AddRefed WriteCallback( JSContext* aCx, JS::Handle aChunk, WritableStreamDefaultController& aController, ErrorResult& aRv) override; already_AddRefed CloseCallbackImpl(JSContext* aCx, ErrorResult& aRv) override; already_AddRefed AbortCallbackImpl( JSContext* aCx, const Optional>& aReason, ErrorResult& aRv) override; void ReleaseObjects() override; private: ~WritableFileStreamUnderlyingSinkAlgorithms() = default; RefPtr mStream; }; class CopyPromiseResolver final : public nsIRequestObserver { RefPtr mPromise; nsCOMPtr mResolveTarget; public: explicit CopyPromiseResolver(already_AddRefed aPromise, nsISerialEventTarget* aResolveTarget) : mPromise(aPromise), mResolveTarget(aResolveTarget) {} NS_DECL_ISUPPORTS NS_DECL_NSIREQUESTOBSERVER private: ~CopyPromiseResolver() = default; }; NS_IMPL_ISUPPORTS(CopyPromiseResolver, nsIRequestObserver) NS_IMETHODIMP CopyPromiseResolver::OnStartRequest(nsIRequest* aRequest) { return NS_OK; } NS_IMETHODIMP CopyPromiseResolver::OnStopRequest(nsIRequest* /*aRequest*/, nsresult aStatus) { InvokeAsync(mResolveTarget, __func__, [promise = std::move(mPromise), aStatus]() { if (NS_SUCCEEDED(aStatus)) { promise->MaybeResolve(0); // Written amount? } else if (IsFileNotFoundError(aStatus)) { promise->MaybeRejectWithNotFoundError("File not found"); } else { promise->MaybeReject(aStatus); } return BoolPromise::CreateAndResolve(true, __func__); }); return NS_OK; } // Same value as in FileSystemSyncAccessHandle const uint32_t kWritableStreamCopyBlockSize = 1024 * 1024; // TODO: Refactor this function, see Bug 1804614 void WriteImpl(const RefPtr& aTaskQueue, already_AddRefed aInputStream, RefPtr& aOutStreamOwner, const Maybe aPosition, const RefPtr& aPromise) { auto rejectAndReturn = [&aPromise](const nsresult rv) { if (IsFileNotFoundError(rv)) { aPromise->MaybeRejectWithNotFoundError("File not found"); return; } aPromise->MaybeReject(rv); }; nsresult rv = NS_ERROR_UNEXPECTED; nsCOMPtr copier = do_CreateInstance("@mozilla.org/network/async-stream-copier;1", &rv); QM_TRY(MOZ_TO_RESULT(rv), rejectAndReturn); MOZ_ASSERT(copier); nsCOMPtr bufferedSource; QM_TRY(MOZ_TO_RESULT(NS_NewBufferedInputStream(getter_AddRefs(bufferedSource), std::move(aInputStream), kWritableStreamCopyBlockSize)), rejectAndReturn); MOZ_ASSERT(bufferedSource); if (aPosition.isSome()) { LOG(("%p: Seeking to %" PRIu64, aOutStreamOwner.get(), aPosition.value())); QM_TRY(MOZ_TO_RESULT(aOutStreamOwner->Seek(aPosition.value())), rejectAndReturn); } nsCOMPtr streamSink = aOutStreamOwner->OutputStream(); MOZ_ASSERT(streamSink); QM_TRY(MOZ_TO_RESULT(copier->Init( bufferedSource, streamSink, aTaskQueue, /* sourceBuffered */ true, /* sinkBuffered */ false, kWritableStreamCopyBlockSize, /* closeSource */ true, /* closeSink */ false)), rejectAndReturn); nsCOMPtr resolveTarget = GetCurrentSerialEventTarget(); // Observer will live on main thread, copier dispatches work on taskQueue InvokeAsync(GetMainThreadSerialEventTarget(), __func__, [copier = std::move(copier), promise = aPromise, resolveTarget = std::move(resolveTarget)]() mutable { RefPtr callbacks = new CopyPromiseResolver(promise.forget(), resolveTarget); // Error through callback promise QM_TRY(MOZ_TO_RESULT(copier->AsyncCopy(callbacks, nullptr)), CreateAndRejectBoolPromise); return BoolPromise::CreateAndResolve(true, __func__); }); } } // namespace class FileSystemWritableFileStream::CloseHandler { enum struct State : uint8_t { Initial = 0, Open, Closing, Closed }; public: NS_INLINE_DECL_REFCOUNTING(FileSystemWritableFileStream::CloseHandler) /** * @brief Are we not yet closing? */ bool IsOpen() const { return State::Open == mState; } /** * @brief Are we already fully closed? */ bool IsClosed() const { return State::Closed == mState; } /** * @brief Transition from open to closing state * * @return true if the state was open and became closing after the call * @return false in all the other cases the previous state is preserved */ bool TestAndSetClosing() { const bool isOpen = State::Open == mState; if (isOpen) { mState = State::Closing; } return isOpen; } RefPtr GetClosePromise() const { MOZ_ASSERT(State::Open != mState, "Please call TestAndSetClosing before GetClosePromise"); if (State::Closing == mState) { return mClosePromiseHolder.Ensure(__func__); } // Instant resolve for initial state due to early shutdown or closed state return BoolPromise::CreateAndResolve(true, __func__); } /** * @brief Transition from initial to open state. In initial state * */ void Open() { MOZ_ASSERT(State::Initial == mState); mState = State::Open; } /** * @brief Transition to closed state and resolve all pending promises. * */ void Close() { mState = State::Closed; mClosePromiseHolder.ResolveIfExists(true, __func__); } protected: virtual ~CloseHandler() = default; private: mutable MozPromiseHolder mClosePromiseHolder; State mState = State::Initial; }; FileSystemWritableFileStream::FileSystemWritableFileStream( const nsCOMPtr& aGlobal, RefPtr& aManager, RefPtr aActor, already_AddRefed aTaskQueue, nsCOMPtr aStream, fs::FileSystemEntryMetadata&& aMetadata) : WritableStream(aGlobal, HoldDropJSObjectsCaller::Explicit), mManager(aManager), mActor(std::move(aActor)), mTaskQueue(aTaskQueue), mStreamOwner(MakeAndAddRef( std::move(aStream))), mWorkerRef(), mMetadata(std::move(aMetadata)), mCloseHandler(MakeAndAddRef()) { LOG(("Created WritableFileStream %p for fd %p", this, mStreamOwner.get())); // Connect with the actor directly in the constructor. This way the actor // can call `FileSystemWritableFileStream::ClearActor` when we call // `PFileSystemWritableFileStreamChild::Send__delete__` even when // FileSystemWritableFileStream::Create fails, in which case the not yet // fully constructed FileSystemWritableFileStream is being destroyed. mActor->SetStream(this); mozilla::HoldJSObjects(this); } FileSystemWritableFileStream::~FileSystemWritableFileStream() { MOZ_ASSERT(IsClosed()); mozilla::DropJSObjects(this); } // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream // * This is fallible because of OOM handling of JSAPI. See bug 1762233. // XXX(krosylight): _BOUNDARY because SetUpNative here can't run script because // StartCallback here is no-op. Can we let the static check automatically detect // this situation? /* static */ MOZ_CAN_RUN_SCRIPT_BOUNDARY RefPtr FileSystemWritableFileStream::Create( const nsCOMPtr& aGlobal, RefPtr& aManager, RefPtr aActor, mozilla::ipc::RandomAccessStreamParams&& aStreamParams, fs::FileSystemEntryMetadata&& aMetadata) { using StreamPromise = MozPromise>, nsresult, /* IsExclusive */ true>; MOZ_ASSERT(aGlobal); QM_TRY_UNWRAP(auto streamTransportService, MOZ_TO_RESULT_GET_TYPED(nsCOMPtr, MOZ_SELECT_OVERLOAD(do_GetService), NS_STREAMTRANSPORTSERVICE_CONTRACTID), [](const nsresult aRv) { return CreatePromise::CreateAndReject(aRv, __func__); }); RefPtr taskQueue = TaskQueue::Create(streamTransportService.forget(), "WritableStreamQueue"); MOZ_ASSERT(taskQueue); auto streamSetup = [aGlobal, aManager, actor = std::move(aActor), taskQueue, metadata = std::move(aMetadata)](StreamPromise::ResolveOrRejectValue&& aValue) MOZ_CAN_RUN_SCRIPT mutable { auto rejectAndReturn = [](const nsresult aRv) { return CreatePromise::CreateAndReject(aRv, __func__); }; if (!aValue.IsResolve()) { if (aValue.IsReject()) { return rejectAndReturn(aValue.RejectValue()); } return rejectAndReturn(NS_ERROR_DOM_UNKNOWN_ERR); } AutoJSAPI jsapi; if (!jsapi.Init(aGlobal)) { return rejectAndReturn(NS_ERROR_FAILURE); } JSContext* cx = jsapi.cx(); // Step 5. Perform ! InitializeWritableStream(stream). // (Done by the constructor) nsCOMPtr inputOutputStream = aValue.ResolveValue(); RefPtr stream = new FileSystemWritableFileStream( aGlobal, aManager, std::move(actor), taskQueue.forget(), /* random access stream */ inputOutputStream, std::move(metadata)); auto autoClose = MakeScopeExit([stream] { stream->mCloseHandler->Close(); stream->mActor->SendClose(); }); // Step 3 - 5 auto algorithms = MakeRefPtr(*stream); // Step 8: Set up stream with writeAlgorithm set to writeAlgorithm, // closeAlgorithm set to closeAlgorithm, abortAlgorithm set to // abortAlgorithm, highWaterMark set to highWaterMark, and // sizeAlgorithm set to sizeAlgorithm. IgnoredErrorResult rv; stream->SetUpNative(cx, *algorithms, // Step 6. Let highWaterMark be 1. Some(1), // Step 7. Let sizeAlgorithm be an algorithm // that returns 1. (nullptr returns 1, See // WritableStream::Constructor for details) nullptr, rv); if (rv.Failed()) { return CreatePromise::CreateAndReject(rv.StealNSResult(), __func__); } autoClose.release(); stream->mCloseHandler->Open(); // Step 9: Return stream. return CreatePromise::CreateAndResolve(stream.forget(), __func__); }; return InvokeAsync(taskQueue, __func__, [streamParams = std::move(aStreamParams)]() mutable { // Step 1. Let stream be a new // FileSystemWritableFileStream in realm. Step 2. Set // stream.[[file]] to file. (Covered by the constructor) mozilla::ipc::RandomAccessStreamParams params( std::move(streamParams)); QM_TRY_UNWRAP( MovingNotNull> inputOutputStream, mozilla::ipc::DeserializeRandomAccessStream(params), [](const bool) { return StreamPromise::CreateAndReject( NS_ERROR_DOM_UNKNOWN_ERR, __func__); }); NotNull> unwrapped( std::move(inputOutputStream).unwrap()); return StreamPromise::CreateAndResolve(unwrapped, __func__); }) ->Then(GetCurrentSerialEventTarget(), __func__, std::move(streamSetup)); } NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(FileSystemWritableFileStream, WritableStream) NS_IMPL_CYCLE_COLLECTION_CLASS(FileSystemWritableFileStream) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FileSystemWritableFileStream, WritableStream) // Per the comment for the FileSystemManager class, don't unlink mManager! if (tmp->IsOpen()) { Unused << tmp->BeginClose(); } NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FileSystemWritableFileStream, WritableStream) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END void FileSystemWritableFileStream::LastRelease() { // We can't call `FileSystemWritableFileStream::Close` here because it may // need to keep FileSystemWritableFileStream object alive which isn't possible // when the object is about to be deleted. There are other mechanisms which // ensure that the object is correctly closed before destruction. For example // the object unlinking and the worker shutdown (we get notified about it via // the callback passed to `StrongWorkerRef`) are used to close the object if // it hasn't been closed yet. if (mActor) { PFileSystemWritableFileStreamChild::Send__delete__(mActor); MOZ_ASSERT(!mActor); } } void FileSystemWritableFileStream::ClearActor() { MOZ_ASSERT(mActor); mActor = nullptr; } bool FileSystemWritableFileStream::IsOpen() const { return mCloseHandler->IsOpen(); } bool FileSystemWritableFileStream::IsClosed() const { return mCloseHandler->IsClosed(); } RefPtr FileSystemWritableFileStream::BeginClose() { if (mCloseHandler->TestAndSetClosing()) { InvokeAsync(mTaskQueue, __func__, [streamOwner = mStreamOwner]() mutable { streamOwner->Close(); return BoolPromise::CreateAndResolve(true, __func__); }) ->Then(GetCurrentSerialEventTarget(), __func__, [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue&) { return self->mTaskQueue->BeginShutdown(); }) ->Then(GetCurrentSerialEventTarget(), __func__, [self = RefPtr(this)]( const ShutdownPromise::ResolveOrRejectValue& /* aValue */) { if (self->mActor) { self->mActor->SendClose(); } self->mWorkerRef = nullptr; self->mCloseHandler->Close(); }); } return mCloseHandler->GetClosePromise(); } void FileSystemWritableFileStream::SetWorkerRef( RefPtr&& aWorkerRef) { mWorkerRef = std::move(aWorkerRef); } already_AddRefed FileSystemWritableFileStream::Write( JSContext* aCx, JS::Handle aChunk, ErrorResult& aError) { MOZ_ASSERT(IsOpen()); // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream // Step 3. Let writeAlgorithm be an algorithm which takes a chunk argument // and returns the result of running the write a chunk algorithm with stream // and chunk. // https://fs.spec.whatwg.org/#write-a-chunk // Step 1. Let input be the result of converting chunk to a // FileSystemWriteChunkType. aError.MightThrowJSException(); ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams data; if (!data.Init(aCx, aChunk)) { aError.StealExceptionFromJSContext(aCx); return nullptr; } // Step 2. Let p be a new promise. RefPtr promise = Promise::Create(GetParentObject(), aError); if (aError.Failed()) { return nullptr; } // Step 3.3. Let command be input.type if input is a WriteParams, ... if (data.IsWriteParams()) { const WriteParams& params = data.GetAsWriteParams(); switch (params.mType) { // Step 3.4. If command is "write": case WriteCommandType::Write: { if (!params.mData.WasPassed()) { promise->MaybeRejectWithSyntaxError("write() requires data"); return promise.forget(); } // Step 3.4.2. If data is undefined, reject p with a TypeError and // abort. if (params.mData.Value().IsNull()) { promise->MaybeRejectWithTypeError("write() of null data"); return promise.forget(); } Maybe position; if (params.mPosition.WasPassed()) { if (params.mPosition.Value().IsNull()) { promise->MaybeRejectWithTypeError("write() with null position"); return promise.forget(); } position = Some(params.mPosition.Value().Value()); } Write(params.mData.Value().Value(), position, promise); return promise.forget(); } // Step 3.5. Otherwise, if command is "seek": case WriteCommandType::Seek: if (!params.mPosition.WasPassed()) { promise->MaybeRejectWithSyntaxError("seek() requires a position"); return promise.forget(); } // Step 3.5.1. If chunk.position is undefined, reject p with a // TypeError and abort. if (params.mPosition.Value().IsNull()) { promise->MaybeRejectWithTypeError("seek() with null position"); return promise.forget(); } Seek(params.mPosition.Value().Value(), promise); return promise.forget(); // Step 3.6. Otherwise, if command is "truncate": case WriteCommandType::Truncate: if (!params.mSize.WasPassed()) { promise->MaybeRejectWithSyntaxError("truncate() requires a size"); return promise.forget(); } // Step 3.6.1. If chunk.size is undefined, reject p with a TypeError // and abort. if (params.mSize.Value().IsNull()) { promise->MaybeRejectWithTypeError("truncate() with null size"); return promise.forget(); } Truncate(params.mSize.Value().Value(), promise); return promise.forget(); default: MOZ_CRASH("Bad WriteParams value!"); } } // Step 3.3. ... and "write" otherwise. // Step 3.4. If command is "write": Write(data, Nothing(), promise); return promise.forget(); } // WebIDL Boilerplate JSObject* FileSystemWritableFileStream::WrapObject( JSContext* aCx, JS::Handle aGivenProto) { return FileSystemWritableFileStream_Binding::Wrap(aCx, this, aGivenProto); } // WebIDL Interface already_AddRefed FileSystemWritableFileStream::Write( const ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams& aData, ErrorResult& aError) { // https://fs.spec.whatwg.org/#dom-filesystemwritablefilestream-write // Step 1. Let writer be the result of getting a writer for this. RefPtr writer = GetWriter(aError); if (aError.Failed()) { return nullptr; } // Step 2. Let result be the result of writing a chunk to writer given data. AutoJSAPI jsapi; if (!jsapi.Init(GetParentObject())) { aError.ThrowUnknownError("Internal error"); return nullptr; } JSContext* cx = jsapi.cx(); JS::Rooted global(cx, JS::CurrentGlobalOrNull(cx)); JS::Rooted val(cx); if (!aData.ToJSVal(cx, global, &val)) { aError.ThrowUnknownError("Internal error"); return nullptr; } RefPtr promise = writer->Write(cx, val, aError); // Step 3. Release writer. writer->ReleaseLock(cx); // Step 4. Return result. return promise.forget(); } already_AddRefed FileSystemWritableFileStream::Seek( uint64_t aPosition, ErrorResult& aError) { // https://fs.spec.whatwg.org/#dom-filesystemwritablefilestream-seek // Step 1. Let writer be the result of getting a writer for this. RefPtr writer = GetWriter(aError); if (aError.Failed()) { return nullptr; } // Step 2. Let result be the result of writing a chunk to writer given // «[ "type" → "seek", "position" → position ]». AutoJSAPI jsapi; if (!jsapi.Init(GetParentObject())) { aError.ThrowUnknownError("Internal error"); return nullptr; } JSContext* cx = jsapi.cx(); RootedDictionary writeParams(cx); writeParams.mType = WriteCommandType::Seek; writeParams.mPosition.Construct(aPosition); JS::Rooted val(cx); if (!ToJSValue(cx, writeParams, &val)) { aError.ThrowUnknownError("Internal error"); return nullptr; } RefPtr promise = writer->Write(cx, val, aError); // Step 3. Release writer. writer->ReleaseLock(cx); // Step 4. Return result. return promise.forget(); } already_AddRefed FileSystemWritableFileStream::Truncate( uint64_t aSize, ErrorResult& aError) { // https://fs.spec.whatwg.org/#dom-filesystemwritablefilestream-truncate // Step 1. Let writer be the result of getting a writer for this. RefPtr writer = GetWriter(aError); if (aError.Failed()) { return nullptr; } // Step 2. Let result be the result of writing a chunk to writer given // «[ "type" → "truncate", "size" → size ]». AutoJSAPI jsapi; if (!jsapi.Init(GetParentObject())) { aError.ThrowUnknownError("Internal error"); return nullptr; } JSContext* cx = jsapi.cx(); RootedDictionary writeParams(cx); writeParams.mType = WriteCommandType::Truncate; writeParams.mSize.Construct(aSize); JS::Rooted val(cx); if (!ToJSValue(cx, writeParams, &val)) { aError.ThrowUnknownError("Internal error"); return nullptr; } RefPtr promise = writer->Write(cx, val, aError); // Step 3. Release writer. writer->ReleaseLock(cx); // Step 4. Return result. return promise.forget(); } template void FileSystemWritableFileStream::Write(const T& aData, const Maybe aPosition, const RefPtr& aPromise) { auto rejectAndReturn = [&aPromise](const nsresult rv) { if (IsFileNotFoundError(rv)) { aPromise->MaybeRejectWithNotFoundError("File not found"); return; } aPromise->MaybeReject(rv); }; nsCOMPtr inputStream; // https://fs.spec.whatwg.org/#write-a-chunk // Step 3.4.6 If data is a BufferSource, let dataBytes be a copy of data. if (aData.IsArrayBuffer() || aData.IsArrayBufferView()) { const auto dataSpan = [&aData]() -> mozilla::Span { if (aData.IsArrayBuffer()) { const ArrayBuffer& buffer = aData.GetAsArrayBuffer(); buffer.ComputeState(); return Span{buffer.Data(), buffer.Length()}; } const ArrayBufferView& buffer = aData.GetAsArrayBufferView(); buffer.ComputeState(); return Span{buffer.Data(), buffer.Length()}; }(); // Here we copy QM_TRY(MOZ_TO_RESULT(NS_NewByteInputStream(getter_AddRefs(inputStream), AsChars(dataSpan), NS_ASSIGNMENT_COPY)), rejectAndReturn); WriteImpl(mTaskQueue, inputStream.forget(), mStreamOwner, aPosition, aPromise); return; } // Step 3.4.7 Otherwise, if data is a Blob ... if (aData.IsBlob()) { Blob& blob = aData.GetAsBlob(); ErrorResult error; blob.CreateInputStream(getter_AddRefs(inputStream), error); QM_TRY((MOZ_TO_RESULT(!error.Failed()).mapErr([&error](const nsresult rv) { return error.StealNSResult(); })), rejectAndReturn); WriteImpl(mTaskQueue, inputStream.forget(), mStreamOwner, aPosition, aPromise); return; } // Step 3.4.8 Otherwise ... MOZ_ASSERT(aData.IsUTF8String()); // Here we copy nsCString dataString; if (!dataString.Assign(aData.GetAsUTF8String(), mozilla::fallible)) { rejectAndReturn(NS_ERROR_OUT_OF_MEMORY); return; } // Input stream takes ownership QM_TRY(MOZ_TO_RESULT(NS_NewCStringInputStream(getter_AddRefs(inputStream), std::move(dataString))), rejectAndReturn); WriteImpl(mTaskQueue, inputStream.forget(), mStreamOwner, aPosition, aPromise); } void FileSystemWritableFileStream::Seek(uint64_t aPosition, const RefPtr& aPromise) { LOG_VERBOSE(("%p: Seeking to %" PRIu64, mStreamOwner.get(), aPosition)); InvokeAsync(mTaskQueue, __func__, [aPosition, streamOwner = mStreamOwner]() mutable { QM_TRY(MOZ_TO_RESULT(streamOwner->Seek(aPosition)), CreateAndRejectBoolPromise); return BoolPromise::CreateAndResolve(true, __func__); }) ->Then(GetCurrentSerialEventTarget(), __func__, [aPromise](const BoolPromise::ResolveOrRejectValue& aValue) { if (aValue.IsReject()) { auto rv = aValue.RejectValue(); if (IsFileNotFoundError(rv)) { aPromise->MaybeRejectWithNotFoundError("File not found"); return; } aPromise->MaybeReject(rv); return; } MOZ_ASSERT(aValue.IsResolve()); aPromise->MaybeResolveWithUndefined(); }); } void FileSystemWritableFileStream::Truncate(uint64_t aSize, const RefPtr& aPromise) { InvokeAsync(mTaskQueue, __func__, [aSize, streamOwner = mStreamOwner]() mutable { QM_TRY(MOZ_TO_RESULT(streamOwner->Truncate(aSize)), CreateAndRejectBoolPromise); return BoolPromise::CreateAndResolve(true, __func__); }) ->Then(GetCurrentSerialEventTarget(), __func__, [aPromise](const BoolPromise::ResolveOrRejectValue& aValue) { if (aValue.IsReject()) { aPromise->MaybeReject(aValue.RejectValue()); return; } aPromise->MaybeResolveWithUndefined(); }); } NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0( WritableFileStreamUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase) NS_IMPL_CYCLE_COLLECTION_INHERITED(WritableFileStreamUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase, mStream) // Step 3 of // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream already_AddRefed WritableFileStreamUnderlyingSinkAlgorithms::WriteCallback( JSContext* aCx, JS::Handle aChunk, WritableStreamDefaultController& aController, ErrorResult& aRv) { return mStream->Write(aCx, aChunk, aRv); } // Step 4 of // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream already_AddRefed WritableFileStreamUnderlyingSinkAlgorithms::CloseCallbackImpl( JSContext* aCx, ErrorResult& aRv) { RefPtr promise = Promise::Create(mStream->GetParentObject(), aRv); if (aRv.Failed()) { return nullptr; } if (!mStream->IsOpen()) { promise->MaybeRejectWithTypeError("WritableFileStream closed"); return promise.forget(); } mStream->BeginClose()->Then( GetCurrentSerialEventTarget(), __func__, [promise](const BoolPromise::ResolveOrRejectValue& aValue) { // Step 2.3. Return a promise resolved with undefined. if (aValue.IsResolve()) { promise->MaybeResolveWithUndefined(); return; } promise->MaybeRejectWithAbortError( "Internal error closing file stream"); }); return promise.forget(); } // Step 5 of // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream already_AddRefed WritableFileStreamUnderlyingSinkAlgorithms::AbortCallbackImpl( JSContext* aCx, const Optional>& /* aReason */, ErrorResult& aRv) { // https://streams.spec.whatwg.org/#writablestream-set-up // Step 3. Let abortAlgorithmWrapper be an algorithm that runs these steps: // Step 3.3. Return a promise resolved with undefined. return CloseCallbackImpl(aCx, aRv); } void WritableFileStreamUnderlyingSinkAlgorithms::ReleaseObjects() { // XXX We shouldn't be calling close here. We should just release the lock. // However, calling close here is not a big issue for now because we don't // write to a temporary file which would atomically replace the real file // during close. if (mStream->IsOpen()) { Unused << mStream->BeginClose(); } } } // namespace mozilla::dom