gecko-dev/dom/streams/WritableStreamDefaultWriter.cpp

545 lines
20 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "mozilla/dom/WritableStreamDefaultWriter.h"
#include "js/Array.h"
#include "js/TypeDecls.h"
#include "js/Value.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/dom/WritableStream.h"
#include "mozilla/dom/WritableStreamDefaultWriterBinding.h"
#include "nsCOMPtr.h"
#include "mozilla/dom/Promise-inl.h"
#include "nsIGlobalObject.h"
#include "nsISupports.h"
namespace mozilla::dom {
using namespace streams_abstract;
NS_IMPL_CYCLE_COLLECTION_CLASS(WritableStreamDefaultWriter)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WritableStreamDefaultWriter)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal, mStream, mReadyPromise,
mClosedPromise)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WritableStreamDefaultWriter)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal, mStream, mReadyPromise,
mClosedPromise)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(WritableStreamDefaultWriter)
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(WritableStreamDefaultWriter)
NS_IMPL_CYCLE_COLLECTING_RELEASE(WritableStreamDefaultWriter)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WritableStreamDefaultWriter)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
WritableStreamDefaultWriter::WritableStreamDefaultWriter(
nsIGlobalObject* aGlobal)
: mGlobal(aGlobal) {
mozilla::HoldJSObjects(this);
}
WritableStreamDefaultWriter::~WritableStreamDefaultWriter() {
mozilla::DropJSObjects(this);
}
void WritableStreamDefaultWriter::SetReadyPromise(Promise* aPromise) {
MOZ_ASSERT(aPromise);
mReadyPromise = aPromise;
}
void WritableStreamDefaultWriter::SetClosedPromise(Promise* aPromise) {
MOZ_ASSERT(aPromise);
mClosedPromise = aPromise;
}
JSObject* WritableStreamDefaultWriter::WrapObject(
JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
return WritableStreamDefaultWriter_Binding::Wrap(aCx, this, aGivenProto);
}
/* static */
already_AddRefed<WritableStreamDefaultWriter>
WritableStreamDefaultWriter::Constructor(const GlobalObject& aGlobal,
WritableStream& aStream,
ErrorResult& aRv) {
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
RefPtr<WritableStreamDefaultWriter> writer =
new WritableStreamDefaultWriter(global);
SetUpWritableStreamDefaultWriter(writer, &aStream, aRv);
if (aRv.Failed()) {
return nullptr;
}
return writer.forget();
}
already_AddRefed<Promise> WritableStreamDefaultWriter::Closed() {
RefPtr<Promise> closedPromise = mClosedPromise;
return closedPromise.forget();
}
already_AddRefed<Promise> WritableStreamDefaultWriter::Ready() {
RefPtr<Promise> readyPromise = mReadyPromise;
return readyPromise.forget();
}
namespace streams_abstract {
// https://streams.spec.whatwg.org/#writable-stream-default-writer-get-desired-size
Nullable<double> WritableStreamDefaultWriterGetDesiredSize(
WritableStreamDefaultWriter* aWriter) {
// Step 1. Let stream be writer.[[stream]].
RefPtr<WritableStream> stream = aWriter->GetStream();
// Step 2. Let state be stream.[[state]].
WritableStream::WriterState state = stream->State();
// Step 3. If state is "errored" or "erroring", return null.
if (state == WritableStream::WriterState::Errored ||
state == WritableStream::WriterState::Erroring) {
return nullptr;
}
// Step 4. If state is "closed", return 0.
if (state == WritableStream::WriterState::Closed) {
return 0.0;
}
// Step 5. Return
// ! WritableStreamDefaultControllerGetDesiredSize(stream.[[controller]]).
return stream->Controller()->GetDesiredSize();
}
} // namespace streams_abstract
// https://streams.spec.whatwg.org/#default-writer-desired-size
Nullable<double> WritableStreamDefaultWriter::GetDesiredSize(ErrorResult& aRv) {
// Step 1. If this.[[stream]] is undefined, throw a TypeError exception.
if (!mStream) {
aRv.ThrowTypeError("Missing stream");
return nullptr;
}
// Step 2. Return ! WritableStreamDefaultWriterGetDesiredSize(this).
RefPtr<WritableStreamDefaultWriter> thisRefPtr = this;
return WritableStreamDefaultWriterGetDesiredSize(thisRefPtr);
}
// https://streams.spec.whatwg.org/#writable-stream-default-writer-abort
MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> WritableStreamDefaultWriterAbort(
JSContext* aCx, WritableStreamDefaultWriter* aWriter,
JS::Handle<JS::Value> aReason, ErrorResult& aRv) {
// Step 1. Let stream be writer.[[stream]].
RefPtr<WritableStream> stream = aWriter->GetStream();
// Step 2. Assert: stream is not undefined.
MOZ_ASSERT(stream);
// Step 3. Return ! WritableStreamAbort(stream, reason).
return WritableStreamAbort(aCx, stream, aReason, aRv);
}
// https://streams.spec.whatwg.org/#default-writer-abort
already_AddRefed<Promise> WritableStreamDefaultWriter::Abort(
JSContext* aCx, JS::Handle<JS::Value> aReason, ErrorResult& aRv) {
// Step 1. If this.[[stream]] is undefined, return a promise rejected with a
// TypeError exception.
if (!mStream) {
aRv.ThrowTypeError("Missing stream");
return nullptr;
}
// Step 2. Return ! WritableStreamDefaultWriterAbort(this, reason).
RefPtr<WritableStreamDefaultWriter> thisRefPtr = this;
return WritableStreamDefaultWriterAbort(aCx, thisRefPtr, aReason, aRv);
}
// https://streams.spec.whatwg.org/#writable-stream-default-writer-close
MOZ_CAN_RUN_SCRIPT static already_AddRefed<Promise>
WritableStreamDefaultWriterClose(JSContext* aCx,
WritableStreamDefaultWriter* aWriter,
ErrorResult& aRv) {
// Step 1. Let stream be writer.[[stream]].
RefPtr<WritableStream> stream = aWriter->GetStream();
// Step 2. Assert: stream is not undefined.
MOZ_ASSERT(stream);
// Step 3. Return ! WritableStreamClose(stream).
return WritableStreamClose(aCx, stream, aRv);
}
// https://streams.spec.whatwg.org/#default-writer-close
already_AddRefed<Promise> WritableStreamDefaultWriter::Close(JSContext* aCx,
ErrorResult& aRv) {
// Step 1. Let stream be this.[[stream]].
RefPtr<WritableStream> stream = mStream;
// Step 2. If stream is undefined, return a promise rejected with a TypeError
// exception.
if (!stream) {
aRv.ThrowTypeError("Missing stream");
return nullptr;
}
// Step 3. If ! WritableStreamCloseQueuedOrInFlight(stream) is true,
// return a promise rejected with a TypeError exception.
if (stream->CloseQueuedOrInFlight()) {
aRv.ThrowTypeError("Stream is closing");
return nullptr;
}
// Step 3. Return ! WritableStreamDefaultWriterClose(this).
RefPtr<WritableStreamDefaultWriter> thisRefPtr = this;
return WritableStreamDefaultWriterClose(aCx, thisRefPtr, aRv);
}
namespace streams_abstract {
// https://streams.spec.whatwg.org/#writable-stream-default-writer-release
void WritableStreamDefaultWriterRelease(JSContext* aCx,
WritableStreamDefaultWriter* aWriter) {
// Step 1. Let stream be writer.[[stream]].
RefPtr<WritableStream> stream = aWriter->GetStream();
// Step 2. Assert: stream is not undefined.
MOZ_ASSERT(stream);
// Step 3. Assert: stream.[[writer]] is writer.
MOZ_ASSERT(stream->GetWriter() == aWriter);
// Step 4. Let releasedError be a new TypeError.
JS::Rooted<JS::Value> releasedError(RootingCx(), JS::UndefinedValue());
{
ErrorResult rv;
rv.ThrowTypeError("Releasing lock");
bool ok = ToJSValue(aCx, std::move(rv), &releasedError);
MOZ_RELEASE_ASSERT(ok, "must be ok");
}
// Step 5. Perform !
// WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer,
// releasedError).
WritableStreamDefaultWriterEnsureReadyPromiseRejected(aWriter, releasedError);
// Step 6. Perform !
// WritableStreamDefaultWriterEnsureClosedPromiseRejected(writer,
// releasedError).
WritableStreamDefaultWriterEnsureClosedPromiseRejected(aWriter,
releasedError);
// Step 7. Set stream.[[writer]] to undefined.
stream->SetWriter(nullptr);
// Step 8. Set writer.[[stream]] to undefined.
aWriter->SetStream(nullptr);
}
} // namespace streams_abstract
// https://streams.spec.whatwg.org/#default-writer-release-lock
void WritableStreamDefaultWriter::ReleaseLock(JSContext* aCx) {
// Step 1. Let stream be this.[[stream]].
RefPtr<WritableStream> stream = mStream;
// Step 2. If stream is undefined, return.
if (!stream) {
return;
}
// Step 3. Assert: stream.[[writer]] is not undefined.
MOZ_ASSERT(stream->GetWriter());
// Step 4. Perform ! WritableStreamDefaultWriterRelease(this).
RefPtr<WritableStreamDefaultWriter> thisRefPtr = this;
return WritableStreamDefaultWriterRelease(aCx, thisRefPtr);
}
namespace streams_abstract {
// https://streams.spec.whatwg.org/#writable-stream-default-writer-write
already_AddRefed<Promise> WritableStreamDefaultWriterWrite(
JSContext* aCx, WritableStreamDefaultWriter* aWriter,
JS::Handle<JS::Value> aChunk, ErrorResult& aRv) {
// Step 1. Let stream be writer.[[stream]].
RefPtr<WritableStream> stream = aWriter->GetStream();
// Step 2. Assert: stream is not undefined.
MOZ_ASSERT(stream);
// Step 3. Let controller be stream.[[controller]].
RefPtr<WritableStreamDefaultController> controller = stream->Controller();
// Step 4. Let chunkSize be !
// WritableStreamDefaultControllerGetChunkSize(controller, chunk).
double chunkSize =
WritableStreamDefaultControllerGetChunkSize(aCx, controller, aChunk, aRv);
if (aRv.Failed()) {
return nullptr;
}
// Step 5. If stream is not equal to writer.[[stream]], return a promise
// rejected with a TypeError exception.
if (stream != aWriter->GetStream()) {
aRv.ThrowTypeError(
"Can not write on WritableStream owned by another writer.");
return nullptr;
}
// Step 6. Let state be stream.[[state]].
WritableStream::WriterState state = stream->State();
// Step 7. If state is "errored", return a promise rejected with
// stream.[[storedError]].
if (state == WritableStream::WriterState::Errored) {
JS::Rooted<JS::Value> error(aCx, stream->StoredError());
return Promise::CreateRejected(aWriter->GetParentObject(), error, aRv);
}
// Step 8. If ! WritableStreamCloseQueuedOrInFlight(stream) is true or state
// is "closed", return a promise rejected with a TypeError exception
// indicating that the stream is closing or closed.
if (stream->CloseQueuedOrInFlight() ||
state == WritableStream::WriterState::Closed) {
return Promise::CreateRejectedWithTypeError(
aWriter->GetParentObject(), "Stream is closed or closing"_ns, aRv);
}
// Step 9. If state is "erroring", return a promise rejected with
// stream.[[storedError]].
if (state == WritableStream::WriterState::Erroring) {
JS::Rooted<JS::Value> error(aCx, stream->StoredError());
return Promise::CreateRejected(aWriter->GetParentObject(), error, aRv);
}
// Step 10. Assert: state is "writable".
MOZ_ASSERT(state == WritableStream::WriterState::Writable);
// Step 11. Let promise be ! WritableStreamAddWriteRequest(stream).
RefPtr<Promise> promise = WritableStreamAddWriteRequest(stream);
// Step 12. Perform ! WritableStreamDefaultControllerWrite(controller, chunk,
// chunkSize).
WritableStreamDefaultControllerWrite(aCx, controller, aChunk, chunkSize, aRv);
if (aRv.Failed()) {
return nullptr;
}
// Step 13. Return promise.
return promise.forget();
}
} // namespace streams_abstract
// https://streams.spec.whatwg.org/#default-writer-write
already_AddRefed<Promise> WritableStreamDefaultWriter::Write(
JSContext* aCx, JS::Handle<JS::Value> aChunk, ErrorResult& aRv) {
// Step 1. If this.[[stream]] is undefined, return a promise rejected with a
// TypeError exception.
if (!mStream) {
aRv.ThrowTypeError("Missing stream");
return nullptr;
}
// Step 2. Return ! WritableStreamDefaultWriterWrite(this, chunk).
return WritableStreamDefaultWriterWrite(aCx, this, aChunk, aRv);
}
namespace streams_abstract {
// https://streams.spec.whatwg.org/#set-up-writable-stream-default-writer
void SetUpWritableStreamDefaultWriter(WritableStreamDefaultWriter* aWriter,
WritableStream* aStream,
ErrorResult& aRv) {
// Step 1. If ! IsWritableStreamLocked(stream) is true, throw a TypeError
// exception.
if (IsWritableStreamLocked(aStream)) {
aRv.ThrowTypeError("WritableStream is already locked!");
return;
}
// Step 2. Set writer.[[stream]] to stream.
aWriter->SetStream(aStream);
// Step 3. Set stream.[[writer]] to writer.
aStream->SetWriter(aWriter);
// Step 4. Let state be stream.[[state]].
WritableStream::WriterState state = aStream->State();
// Step 5. If state is "writable",
if (state == WritableStream::WriterState::Writable) {
RefPtr<Promise> readyPromise =
Promise::CreateInfallible(aWriter->GetParentObject());
// Step 5.1 If ! WritableStreamCloseQueuedOrInFlight(stream) is false and
// stream.[[backpressure]] is true, set writer.[[readyPromise]] to a new
// promise.
if (!aStream->CloseQueuedOrInFlight() && aStream->Backpressure()) {
aWriter->SetReadyPromise(readyPromise);
} else {
// Step 5.2. Otherwise, set writer.[[readyPromise]] to a promise resolved
// with undefined.
readyPromise->MaybeResolveWithUndefined();
aWriter->SetReadyPromise(readyPromise);
}
// Step 5.3. Set writer.[[closedPromise]] to a new promise.
RefPtr<Promise> closedPromise =
Promise::CreateInfallible(aWriter->GetParentObject());
aWriter->SetClosedPromise(closedPromise);
} else if (state == WritableStream::WriterState::Erroring) {
// Step 6. Otherwise, if state is "erroring",
// Step 6.1. Set writer.[[readyPromise]] to a promise rejected with
// stream.[[storedError]].
JS::Rooted<JS::Value> storedError(RootingCx(), aStream->StoredError());
RefPtr<Promise> readyPromise =
Promise::CreateInfallible(aWriter->GetParentObject());
readyPromise->MaybeReject(storedError);
aWriter->SetReadyPromise(readyPromise);
// Step 6.2. Set writer.[[readyPromise]].[[PromiseIsHandled]] to true.
readyPromise->SetSettledPromiseIsHandled();
// Step 6.3. Set writer.[[closedPromise]] to a new promise.
RefPtr<Promise> closedPromise =
Promise::CreateInfallible(aWriter->GetParentObject());
aWriter->SetClosedPromise(closedPromise);
} else if (state == WritableStream::WriterState::Closed) {
// Step 7. Otherwise, if state is "closed",
// Step 7.1. Set writer.[[readyPromise]] to a promise resolved with
// undefined.
RefPtr<Promise> readyPromise =
Promise::CreateResolvedWithUndefined(aWriter->GetParentObject(), aRv);
if (aRv.Failed()) {
return;
}
aWriter->SetReadyPromise(readyPromise);
// Step 7.2. Set writer.[[closedPromise]] to a promise resolved with
// undefined.
RefPtr<Promise> closedPromise =
Promise::CreateResolvedWithUndefined(aWriter->GetParentObject(), aRv);
if (aRv.Failed()) {
return;
}
aWriter->SetClosedPromise(closedPromise);
} else {
// Step 8. Otherwise,
// Step 8.1 Assert: state is "errored".
MOZ_ASSERT(state == WritableStream::WriterState::Errored);
// Step 8.2. Step Let storedError be stream.[[storedError]].
JS::Rooted<JS::Value> storedError(RootingCx(), aStream->StoredError());
// Step 8.3. Set writer.[[readyPromise]] to a promise rejected with
// storedError.
RefPtr<Promise> readyPromise =
Promise::CreateInfallible(aWriter->GetParentObject());
readyPromise->MaybeReject(storedError);
aWriter->SetReadyPromise(readyPromise);
// Step 8.4. Set writer.[[readyPromise]].[[PromiseIsHandled]] to true.
readyPromise->SetSettledPromiseIsHandled();
// Step 8.5. Set writer.[[closedPromise]] to a promise rejected with
// storedError.
RefPtr<Promise> closedPromise =
Promise::CreateInfallible(aWriter->GetParentObject());
closedPromise->MaybeReject(storedError);
aWriter->SetClosedPromise(closedPromise);
// Step 8.6 Set writer.[[closedPromise]].[[PromiseIsHandled]] to true.
closedPromise->SetSettledPromiseIsHandled();
}
}
// https://streams.spec.whatwg.org/#writable-stream-default-writer-ensure-closed-promise-rejected
void WritableStreamDefaultWriterEnsureClosedPromiseRejected(
WritableStreamDefaultWriter* aWriter, JS::Handle<JS::Value> aError) {
RefPtr<Promise> closedPromise = aWriter->ClosedPromise();
// Step 1. If writer.[[closedPromise]].[[PromiseState]] is "pending", reject
// writer.[[closedPromise]] with error.
if (closedPromise->State() == Promise::PromiseState::Pending) {
closedPromise->MaybeReject(aError);
} else {
// Step 2. Otherwise, set writer.[[closedPromise]] to a promise rejected
// with error.
closedPromise = Promise::CreateInfallible(aWriter->GetParentObject());
closedPromise->MaybeReject(aError);
aWriter->SetClosedPromise(closedPromise);
}
// Step 3. Set writer.[[closedPromise]].[[PromiseIsHandled]] to true.
closedPromise->SetSettledPromiseIsHandled();
}
// https://streams.spec.whatwg.org/#writable-stream-default-writer-ensure-ready-promise-rejected
void WritableStreamDefaultWriterEnsureReadyPromiseRejected(
WritableStreamDefaultWriter* aWriter, JS::Handle<JS::Value> aError) {
RefPtr<Promise> readyPromise = aWriter->ReadyPromise();
// Step 1. If writer.[[readyPromise]].[[PromiseState]] is "pending", reject
// writer.[[readyPromise]] with error.
if (readyPromise->State() == Promise::PromiseState::Pending) {
readyPromise->MaybeReject(aError);
} else {
// Step 2. Otherwise, set writer.[[readyPromise]] to a promise rejected with
// error.
readyPromise = Promise::CreateInfallible(aWriter->GetParentObject());
readyPromise->MaybeReject(aError);
aWriter->SetReadyPromise(readyPromise);
}
// Step 3. Set writer.[[readyPromise]].[[PromiseIsHandled]] to true.
readyPromise->SetSettledPromiseIsHandled();
}
// https://streams.spec.whatwg.org/#writable-stream-default-writer-close-with-error-propagation
already_AddRefed<Promise> WritableStreamDefaultWriterCloseWithErrorPropagation(
JSContext* aCx, WritableStreamDefaultWriter* aWriter, ErrorResult& aRv) {
// Step 1. Let stream be writer.[[stream]].
RefPtr<WritableStream> stream = aWriter->GetStream();
// Step 2. Assert: stream is not undefined.
MOZ_ASSERT(stream);
// Step 3. Let state be stream.[[state]].
WritableStream::WriterState state = stream->State();
// Step 4. If ! WritableStreamCloseQueuedOrInFlight(stream) is true
// or state is "closed", return a promise resolved with undefined.
if (stream->CloseQueuedOrInFlight() ||
state == WritableStream::WriterState::Closed) {
return Promise::CreateResolvedWithUndefined(aWriter->GetParentObject(),
aRv);
}
// Step 5. If state is "errored",
// return a promise rejected with stream.[[storedError]].
if (state == WritableStream::WriterState::Errored) {
JS::Rooted<JS::Value> error(aCx, stream->StoredError());
return Promise::CreateRejected(aWriter->GetParentObject(), error, aRv);
}
// Step 6. Assert: state is "writable" or "erroring".
MOZ_ASSERT(state == WritableStream::WriterState::Writable ||
state == WritableStream::WriterState::Erroring);
// Step 7. Return ! WritableStreamDefaultWriterClose(writer).
return WritableStreamDefaultWriterClose(aCx, aWriter, aRv);
}
} // namespace streams_abstract
} // namespace mozilla::dom