diff options
Diffstat (limited to '')
-rw-r--r-- | dom/fs/api/FileSystemWritableFileStream.cpp | 706 |
1 files changed, 706 insertions, 0 deletions
diff --git a/dom/fs/api/FileSystemWritableFileStream.cpp b/dom/fs/api/FileSystemWritableFileStream.cpp new file mode 100644 index 0000000000..69d1181c16 --- /dev/null +++ b/dom/fs/api/FileSystemWritableFileStream.cpp @@ -0,0 +1,706 @@ +/* -*- 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 "mozilla/Buffer.h" +#include "mozilla/ErrorResult.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/WritableStreamDefaultController.h" +#include "mozilla/dom/quota/QuotaCommon.h" +#include "mozilla/dom/quota/ResultExtensions.h" +#include "nsIInputStream.h" +#include "nsNetUtil.h" +#include "private/pprio.h" + +namespace mozilla::dom { + +namespace { + +class WritableFileStreamUnderlyingSinkAlgorithms final + : public UnderlyingSinkAlgorithmsBase { + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED( + WritableFileStreamUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase) + + explicit WritableFileStreamUnderlyingSinkAlgorithms( + FileSystemWritableFileStream& aStream) + : mStream(&aStream) {} + + // Streams algorithms + void StartCallback(JSContext* aCx, + WritableStreamDefaultController& aController, + JS::MutableHandle<JS::Value> aRetVal, + ErrorResult& aRv) override; + + MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> WriteCallback( + JSContext* aCx, JS::Handle<JS::Value> aChunk, + WritableStreamDefaultController& aController, ErrorResult& aRv) override; + + MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CloseCallback( + JSContext* aCx, ErrorResult& aRv) override; + + MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> AbortCallback( + JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason, + ErrorResult& aRv) override; + + void ReleaseObjects() override; + + private: + ~WritableFileStreamUnderlyingSinkAlgorithms() = default; + + RefPtr<FileSystemWritableFileStream> mStream; +}; + +/** + * TODO: Duplicated from netwerk/cache2/CacheFileIOManager.cpp + * Please remove after bug 1286601 is fixed, + * https://bugzilla.mozilla.org/show_bug.cgi?id=1286601 + */ +nsresult TruncFile(PRFileDesc* aFD, int64_t aEOF) { +#if defined(XP_UNIX) + if (ftruncate(PR_FileDesc2NativeHandle(aFD), aEOF) != 0) { + NS_ERROR("ftruncate failed"); + return NS_ERROR_FAILURE; + } +#elif defined(XP_WIN) + const int64_t currentOffset = PR_Seek64(aFD, 0, PR_SEEK_CUR); + if (currentOffset == -1) { + return NS_ERROR_FAILURE; + } + + int64_t cnt = PR_Seek64(aFD, aEOF, PR_SEEK_SET); + if (cnt == -1) { + return NS_ERROR_FAILURE; + } + + if (!SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(aFD))) { + NS_ERROR("SetEndOfFile failed"); + return NS_ERROR_FAILURE; + } + + if (PR_Seek64(aFD, currentOffset, PR_SEEK_SET) == -1) { + NS_ERROR("Restoring seek offset failed"); + return NS_ERROR_FAILURE; + } + +#else + MOZ_ASSERT(false, "Not implemented!"); + return NS_ERROR_NOT_IMPLEMENTED; +#endif + + return NS_OK; +} + +} // namespace + +FileSystemWritableFileStream::FileSystemWritableFileStream( + nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager, + RefPtr<FileSystemWritableFileStreamChild> aActor, + const ::mozilla::ipc::FileDescriptor& aFileDescriptor, + const fs::FileSystemEntryMetadata& aMetadata) + : WritableStream(aGlobal, HoldDropJSObjectsCaller::Explicit), + mManager(aManager), + mActor(std::move(aActor)), + mFileDesc(nullptr), + mMetadata(aMetadata), + mClosed(false) { + auto rawFD = aFileDescriptor.ClonePlatformHandle(); + mFileDesc = PR_ImportFile(PROsfd(rawFD.release())); + + LOG(("Created WritableFileStream %p for fd %p", this, mFileDesc)); + + // 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(!mActor); + MOZ_ASSERT(mClosed); + + mozilla::DropJSObjects(this); +} + +// https://streams.spec.whatwg.org/#writablestream-set-up +// * This is fallible because of OOM handling of JSAPI. See bug 1762233. +// * Consider extracting this as UnderlyingSinkAlgorithmsWrapper if more classes +// start subclassing WritableStream. +// For now this skips step 2 - 4 as they are not required here. +// XXX(krosylight): _BOUNDARY because SetUpWritableStreamDefaultController 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 already_AddRefed<FileSystemWritableFileStream> +FileSystemWritableFileStream::Create( + nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager, + RefPtr<FileSystemWritableFileStreamChild> aActor, + const ::mozilla::ipc::FileDescriptor& aFileDescriptor, + const fs::FileSystemEntryMetadata& aMetadata) { + AutoJSAPI jsapi; + if (!jsapi.Init(aGlobal)) { + return nullptr; + } + JSContext* cx = jsapi.cx(); + + // Step 5. Perform ! InitializeWritableStream(stream). + // (Done by the constructor) + RefPtr<FileSystemWritableFileStream> stream = + new FileSystemWritableFileStream(aGlobal, aManager, std::move(aActor), + aFileDescriptor, aMetadata); + + // Step 1 - 3 + auto algorithms = + MakeRefPtr<WritableFileStreamUnderlyingSinkAlgorithms>(*stream); + + // Step 6. Let controller be a new WritableStreamDefaultController. + auto controller = + MakeRefPtr<WritableStreamDefaultController>(aGlobal, *stream); + + // Step 7. Perform ! SetUpWritableStreamDefaultController(stream, controller, + // startAlgorithm, writeAlgorithm, closeAlgorithmWrapper, + // abortAlgorithmWrapper, highWaterMark, sizeAlgorithm). + IgnoredErrorResult rv; + SetUpWritableStreamDefaultController( + cx, stream, controller, algorithms, + // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream + // Step 6. Let highWaterMark be 1. + 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 nullptr; + } + return stream.forget(); +} + +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! + tmp->Close(); +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() { + Close(); + + if (mActor) { + PFileSystemWritableFileStreamChild::Send__delete__(mActor); + MOZ_ASSERT(!mActor); + } +} + +void FileSystemWritableFileStream::ClearActor() { + MOZ_ASSERT(mActor); + + mActor = nullptr; +} + +void FileSystemWritableFileStream::Close() { + // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream + // Step 4. Let closeAlgorithm be these steps: + + if (mClosed) { + return; + } + + LOG(("%p: Closing", mFileDesc)); + + mClosed = true; + + PR_Close(mFileDesc); + mFileDesc = nullptr; + + if (mActor) { + mActor->SendClose(); + } +} + +already_AddRefed<Promise> FileSystemWritableFileStream::Write( + JSContext* aCx, JS::Handle<JS::Value> aChunk, ErrorResult& aError) { + MOZ_ASSERT(!mClosed); + + // 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 = 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<uint64_t> 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<JSObject*> aGivenProto) { + return FileSystemWritableFileStream_Binding::Wrap(aCx, this, aGivenProto); +} + +// WebIDL Interface + +already_AddRefed<Promise> 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<WritableStreamDefaultWriter> 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<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx)); + + JS::Rooted<JS::Value> val(cx); + if (!aData.ToJSVal(cx, global, &val)) { + aError.ThrowUnknownError("Internal error"); + return nullptr; + } + + RefPtr<Promise> promise = writer->Write(cx, val, aError); + + // Step 3. Release writer. + { + IgnoredErrorResult error; + writer->ReleaseLock(cx, error); + } + + // Step 4. Return result. + return promise.forget(); +} + +already_AddRefed<Promise> 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<WritableStreamDefaultWriter> 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> writeParams(cx); + writeParams.mType = WriteCommandType::Seek; + writeParams.mPosition.Construct(aPosition); + + JS::Rooted<JS::Value> val(cx); + if (!ToJSValue(cx, writeParams, &val)) { + aError.ThrowUnknownError("Internal error"); + return nullptr; + } + + RefPtr<Promise> promise = writer->Write(cx, val, aError); + + // Step 3. Release writer. + { + IgnoredErrorResult error; + writer->ReleaseLock(cx, error); + } + + // Step 4. Return result. + return promise.forget(); +} + +already_AddRefed<Promise> 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<WritableStreamDefaultWriter> 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> writeParams(cx); + writeParams.mType = WriteCommandType::Truncate; + writeParams.mSize.Construct(aSize); + + JS::Rooted<JS::Value> val(cx); + if (!ToJSValue(cx, writeParams, &val)) { + aError.ThrowUnknownError("Internal error"); + return nullptr; + } + + RefPtr<Promise> promise = writer->Write(cx, val, aError); + + // Step 3. Release writer. + { + IgnoredErrorResult error; + writer->ReleaseLock(cx, error); + } + + // Step 4. Return result. + return promise.forget(); +} + +template <typename T> +void FileSystemWritableFileStream::Write(const T& aData, + const Maybe<uint64_t> aPosition, + RefPtr<Promise> aPromise) { + MOZ_ASSERT(!mClosed); + + auto rejectAndReturn = [&aPromise](const nsresult rv) { + if (rv == NS_ERROR_FILE_NOT_FOUND) { + aPromise->MaybeRejectWithNotFoundError("File not found"); + } else { + aPromise->MaybeReject(rv); + } + }; + + // 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]() { + if (aData.IsArrayBuffer()) { + const ArrayBuffer& buffer = aData.GetAsArrayBuffer(); + buffer.ComputeState(); + return Span{buffer.Data(), buffer.Length()}; + } + MOZ_ASSERT(aData.IsArrayBufferView()); + const ArrayBufferView& buffer = aData.GetAsArrayBufferView(); + buffer.ComputeState(); + return Span{buffer.Data(), buffer.Length()}; + }(); + + nsCString dataBuffer; + QM_TRY(MOZ_TO_RESULT(dataBuffer.Assign( + AsChars(dataSpan).data(), dataSpan.Length(), mozilla::fallible)), + rejectAndReturn); + QM_TRY_INSPECT(const auto& written, WriteBuffer(dataBuffer, aPosition), + rejectAndReturn); + + LOG_VERBOSE(("WritableFileStream: Wrote %" PRId64, written)); + aPromise->MaybeResolve(written); + return; + } + + // Step 3.4.7 Otherwise, if data is a Blob ... + if (aData.IsBlob()) { + Blob& blob = aData.GetAsBlob(); + + nsCOMPtr<nsIInputStream> stream; + ErrorResult error; + blob.CreateInputStream(getter_AddRefs(stream), error); + QM_TRY((MOZ_TO_RESULT(!error.Failed()).mapErr([&error](const nsresult rv) { + return error.StealNSResult(); + })), + rejectAndReturn); + + QM_TRY_INSPECT(const auto& written, + WriteStream(std::move(stream), aPosition), rejectAndReturn); + + LOG_VERBOSE(("WritableFileStream: Wrote %" PRId64, written)); + aPromise->MaybeResolve(written); + return; + } + + // Step 3.4.8 Otherwise ... + MOZ_ASSERT(aData.IsUTF8String()); + + QM_TRY_INSPECT(const auto& written, + WriteBuffer(aData.GetAsUTF8String(), aPosition), + rejectAndReturn); + + LOG_VERBOSE(("WritableFileStream: Wrote %" PRId64, written)); + aPromise->MaybeResolve(written); +} + +void FileSystemWritableFileStream::Seek(uint64_t aPosition, + RefPtr<Promise> aPromise) { + MOZ_ASSERT(!mClosed); + + LOG_VERBOSE(("%p: Seeking to %" PRIu64, mFileDesc, aPosition)); + + QM_TRY(SeekPosition(aPosition), [&aPromise](const nsresult rv) { + aPromise->MaybeReject(rv); + return; + }); + + aPromise->MaybeResolveWithUndefined(); +} + +void FileSystemWritableFileStream::Truncate(uint64_t aSize, + RefPtr<Promise> aPromise) { + MOZ_ASSERT(!mClosed); + + // truncate filehandle (can extend with 0's) + LOG_VERBOSE(("%p: Truncate to %" PRIu64, mFileDesc, aSize)); + if (NS_WARN_IF(NS_FAILED(TruncFile(mFileDesc, aSize)))) { + aPromise->MaybeReject(NS_ErrorAccordingToNSPR()); + return; + } + + // We truncated; per non-normative text in the spec (2.5.3) we should + // adjust the cursor position to be within the new file size + int64_t where = PR_Seek(mFileDesc, 0, PR_SEEK_CUR); + if (where == -1) { + aPromise->MaybeReject(NS_ErrorAccordingToNSPR()); + return; + } + + if (where > (int64_t)aSize) { + where = PR_Seek(mFileDesc, 0, PR_SEEK_END); + if (where == -1) { + aPromise->MaybeReject(NS_ErrorAccordingToNSPR()); + return; + } + } + + aPromise->MaybeResolveWithUndefined(); +} + +Result<uint64_t, nsresult> FileSystemWritableFileStream::WriteBuffer( + const nsACString& aBuffer, const Maybe<uint64_t> aPosition) { + MOZ_ASSERT(!mClosed); + + const auto checkedLength = CheckedInt<PRInt32>(aBuffer.Length()); + QM_TRY(MOZ_TO_RESULT(checkedLength.isValid())); + + if (aPosition) { + QM_TRY(SeekPosition(*aPosition)); + } + + return PR_Write(mFileDesc, aBuffer.BeginReading(), checkedLength.value()); +} + +Result<uint64_t, nsresult> FileSystemWritableFileStream::WriteStream( + nsCOMPtr<nsIInputStream> aStream, const Maybe<uint64_t> aPosition) { + MOZ_ASSERT(aStream); + MOZ_ASSERT(!mClosed); + + nsCString rawBuffer; + QM_TRY(MOZ_TO_RESULT(NS_ReadInputStreamToString(aStream, rawBuffer, -1))); + QM_TRY_RETURN(WriteBuffer(rawBuffer, aPosition)); +} + +Result<Ok, nsresult> FileSystemWritableFileStream::SeekPosition( + uint64_t aPosition) { + MOZ_ASSERT(!mClosed); + + const auto checkedPosition = CheckedInt<int64_t>(aPosition); + QM_TRY(MOZ_TO_RESULT(checkedPosition.isValid())); + + int64_t cnt = PR_Seek64(mFileDesc, checkedPosition.value(), PR_SEEK_SET); + if (cnt == int64_t(-1)) { + LOG(("Failed to seek to %" PRIu64 " (errno %d)", aPosition, errno)); + return Err(NS_ErrorAccordingToNSPR()); + } + + if (cnt != checkedPosition.value()) { + LOG(("Failed to seek to %" PRIu64 " (errno %d), ended up at %" PRId64, + aPosition, errno, cnt)); + return Err(NS_ERROR_FAILURE); + } + + return Ok{}; +} + +NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0( + WritableFileStreamUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase) +NS_IMPL_CYCLE_COLLECTION_INHERITED(WritableFileStreamUnderlyingSinkAlgorithms, + UnderlyingSinkAlgorithmsBase, mStream) + +void WritableFileStreamUnderlyingSinkAlgorithms::StartCallback( + JSContext* aCx, WritableStreamDefaultController& aController, + JS::MutableHandle<JS::Value> aRetVal, ErrorResult& aRv) { + // https://streams.spec.whatwg.org/#writablestream-set-up + // Step 1. Let startAlgorithm be an algorithm that returns undefined. + aRetVal.setUndefined(); +} + +already_AddRefed<Promise> +WritableFileStreamUnderlyingSinkAlgorithms::WriteCallback( + JSContext* aCx, JS::Handle<JS::Value> aChunk, + WritableStreamDefaultController& aController, ErrorResult& aRv) { + return mStream->Write(aCx, aChunk, aRv); +} + +already_AddRefed<Promise> +WritableFileStreamUnderlyingSinkAlgorithms::CloseCallback(JSContext* aCx, + ErrorResult& aRv) { + // https://streams.spec.whatwg.org/#writablestream-set-up + // Step 2. Let closeAlgorithmWrapper be an algorithm that runs these steps: + + RefPtr<Promise> promise = Promise::Create(mStream->GetParentObject(), aRv); + if (aRv.Failed()) { + return nullptr; + } + + if (mStream->IsClosed()) { + promise->MaybeRejectWithTypeError("WritableFileStream closed"); + return promise.forget(); + } + + // XXX The close should be async. For now we have to always fallback to the + // Step 2.3 below. + mStream->Close(); + + // Step 2.3. Return a promise resolved with undefined. + promise->MaybeResolveWithUndefined(); + return promise.forget(); +} + +already_AddRefed<Promise> +WritableFileStreamUnderlyingSinkAlgorithms::AbortCallback( + JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason, + ErrorResult& aRv) { + // https://streams.spec.whatwg.org/#writablestream-set-up + // Step 3. Let abortAlgorithmWrapper be an algorithm that runs these steps: + + // XXX The close or rather a dedicated abort should be async. For now we have + // to always fall back to the Step 3.3 below. + mStream->Close(); + + // Step 3.3. Return a promise resolved with undefined. + return Promise::CreateResolvedWithUndefined(mStream->GetParentObject(), 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. + mStream->Close(); +} + +} // namespace mozilla::dom |