blob: 764eb4c6c611c5e854546a8dffb07023d214d7c8 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/streams/writable_stream_default_writer.h"
#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/core/streams/miscellaneous_operations.h"
#include "third_party/blink/renderer/core/streams/stream_promise_resolver.h"
#include "third_party/blink/renderer/core/streams/writable_stream.h"
#include "third_party/blink/renderer/core/streams/writable_stream_default_controller.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/bindings/v8_binding.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/heap/visitor.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace blink {
namespace {
v8::Local<v8::Value> CreateWriterLockReleasedException(v8::Isolate* isolate,
const char* verbed) {
return v8::Exception::TypeError(V8String(
isolate,
String::Format(
"This writable stream writer has been released and cannot be %s",
verbed)));
}
v8::Local<v8::String> CreateCannotActionOnStateStreamMessage(
v8::Isolate* isolate,
const char* action,
const char* state_name) {
return V8String(isolate, String::Format("Cannot %s a %s writable stream",
action, state_name));
}
v8::Local<v8::Value> CreateCannotActionOnStateStreamException(
v8::Isolate* isolate,
const char* action,
WritableStream::State state) {
const char* state_name = nullptr;
switch (state) {
case WritableStream::kClosed:
state_name = "CLOSED";
break;
case WritableStream::kErrored:
state_name = "ERRORED";
break;
default:
NOTREACHED();
}
return v8::Exception::TypeError(
CreateCannotActionOnStateStreamMessage(isolate, action, state_name));
}
} // namespace
WritableStreamDefaultWriter* WritableStreamDefaultWriter::Create(
ScriptState* script_state,
WritableStream* stream,
ExceptionState& exception_state) {
auto* writer = MakeGarbageCollected<WritableStreamDefaultWriter>(
script_state, static_cast<WritableStream*>(stream), exception_state);
if (exception_state.HadException()) {
return nullptr;
}
return writer;
}
// TODO(ricea): Does using the ScriptState supplied by IDL result in promises
// being created with the correct global?
WritableStreamDefaultWriter::WritableStreamDefaultWriter(
ScriptState* script_state,
WritableStream* stream,
ExceptionState& exception_state)
// 3. Set this.[[ownerWritableStream]] to stream.
: owner_writable_stream_(stream) {
// https://streams.spec.whatwg.org/#default-writer-constructor 2. If !
// IsWritableStreamLocked(stream) is true, throw a TypeError exception.
if (WritableStream::IsLocked(stream)) {
exception_state.ThrowTypeError(
"Cannot create writer when WritableStream is locked");
return;
}
// 4. Set stream.[[writer]] to this.
stream->SetWriter(this);
// 5. Let state be stream.[[state]].
const auto state = stream->GetState();
auto* isolate = script_state->GetIsolate();
switch (state) {
// 6. If state is "writable",
case WritableStream::kWritable: {
// a. If ! WritableStreamCloseQueuedOrInFlight(stream) is false and
// stream.[[backpressure]] is true, set this.[[readyPromise]] to
// a new promise.
if (!WritableStream::CloseQueuedOrInFlight(stream) &&
stream->HasBackpressure()) {
ready_promise_ =
MakeGarbageCollected<StreamPromiseResolver>(script_state);
} else {
// b. Otherwise, set this.[[readyPromise]] to a promise resolved
// with undefined.
ready_promise_ =
StreamPromiseResolver::CreateResolvedWithUndefined(script_state);
}
// c. Set this.[[closedPromise]] to a new promise.
closed_promise_ =
MakeGarbageCollected<StreamPromiseResolver>(script_state);
break;
}
// 7. Otherwise, if state is "erroring",
case WritableStream::kErroring: {
// a. Set this.[[readyPromise]] to a promise rejected with
// stream.[[storedError]].
ready_promise_ = StreamPromiseResolver::CreateRejected(
script_state, stream->GetStoredError(isolate));
// b. Set this.[[readyPromise]].[[PromiseIsHandled]] to true.
ready_promise_->MarkAsHandled(isolate);
// c. Set this.[[closedPromise]] to a new promise.
closed_promise_ =
MakeGarbageCollected<StreamPromiseResolver>(script_state);
break;
}
// 8. Otherwise, if state is "closed",
case WritableStream::kClosed: {
// a. Set this.[[readyPromise]] to a promise resolved with undefined.
ready_promise_ =
StreamPromiseResolver::CreateResolvedWithUndefined(script_state);
// b. Set this.[[closedPromise]] to a promise resolved with
// undefined.
closed_promise_ =
StreamPromiseResolver::CreateResolvedWithUndefined(script_state);
break;
}
// 9. Otherwise,
case WritableStream::kErrored: {
// a. Assert: state is "errored".
// Check omitted as it is not meaningful.
// b. Let storedError be stream.[[storedError]].
const auto stored_error = stream->GetStoredError(isolate);
// c. Set this.[[readyPromise]] to a promise rejected with
// storedError.
ready_promise_ =
StreamPromiseResolver::CreateRejected(script_state, stored_error);
// d. Set this.[[readyPromise]].[[PromiseIsHandled]] to true.
ready_promise_->MarkAsHandled(isolate);
// e. Set this.[[closedPromise]] to a promise rejected with
// storedError.
closed_promise_ =
StreamPromiseResolver::CreateRejected(script_state, stored_error);
// f. Set this.[[closedPromise]].[[PromiseIsHandled]] to true.
closed_promise_->MarkAsHandled(isolate);
break;
}
}
}
WritableStreamDefaultWriter::~WritableStreamDefaultWriter() = default;
ScriptPromise WritableStreamDefaultWriter::closed(
ScriptState* script_state) const {
// https://streams.spec.whatwg.org/#default-writer-closed
// 2. Return this.[[closedPromise]].
return closed_promise_->GetScriptPromise(script_state);
}
ScriptValue WritableStreamDefaultWriter::desiredSize(
ScriptState* script_state,
ExceptionState& exception_state) const {
auto* isolate = script_state->GetIsolate();
// https://streams.spec.whatwg.org/#default-writer-desired-size
// 2. If this.[[ownerWritableStream]] is undefined, throw a TypeError
// exception.
if (!owner_writable_stream_) {
exception_state.RethrowV8Exception(CreateWriterLockReleasedException(
isolate, "used to get the desiredSize"));
return ScriptValue();
}
// 3. Return ! WritableStreamDefaultWriterGetDesiredSize(this).
return ScriptValue(isolate, GetDesiredSize(isolate, this));
}
ScriptPromise WritableStreamDefaultWriter::ready(
ScriptState* script_state) const {
// https://streams.spec.whatwg.org/#default-writer-ready
// 2. Return this.[[readyPromise]].
return ready_promise_->GetScriptPromise(script_state);
}
ScriptPromise WritableStreamDefaultWriter::abort(ScriptState* script_state) {
return abort(script_state,
ScriptValue(script_state->GetIsolate(),
v8::Undefined(script_state->GetIsolate())));
}
ScriptPromise WritableStreamDefaultWriter::abort(ScriptState* script_state,
ScriptValue reason) {
// https://streams.spec.whatwg.org/#default-writer-abort
// 2. If this.[[ownerWritableStream]] is undefined, return a promise rejected
// with a TypeError exception.
if (!owner_writable_stream_) {
return ScriptPromise::Reject(script_state,
CreateWriterLockReleasedException(
script_state->GetIsolate(), "aborted"));
}
// 3. Return ! WritableStreamDefaultWriterAbort(this, reason).
return ScriptPromise(script_state,
Abort(script_state, this, reason.V8Value()));
}
ScriptPromise WritableStreamDefaultWriter::close(ScriptState* script_state) {
// https://streams.spec.whatwg.org/#default-writer-close
// 2. Let stream be this.[[ownerWritableStream]].
WritableStream* stream = owner_writable_stream_;
// 3. If stream is undefined, return a promise rejected with a TypeError
// exception.
if (!stream) {
return ScriptPromise::Reject(script_state,
CreateWriterLockReleasedException(
script_state->GetIsolate(), "closed"));
}
// 4. If ! WritableStreamCloseQueuedOrInFlight(stream) is true, return a
// promise rejected with a TypeError exception.
if (WritableStream::CloseQueuedOrInFlight(stream)) {
return ScriptPromise::Reject(
script_state, v8::Exception::TypeError(
V8String(script_state->GetIsolate(),
"Cannot close a writable stream that has "
"already been requested to be closed")));
}
// 5. Return ! WritableStreamDefaultWriterClose(this).
return ScriptPromise(script_state, Close(script_state, this));
}
void WritableStreamDefaultWriter::releaseLock(ScriptState* script_state) {
// https://streams.spec.whatwg.org/#default-writer-release-lock
// 2. Let stream be this.[[ownerWritableStream]].
WritableStream* stream = owner_writable_stream_;
// 3. If stream is undefined, return.
if (!stream) {
return;
}
// 4. Assert: stream.[[writer]] is not undefined.
DCHECK(stream->Writer());
// 5. Perform ! WritableStreamDefaultWriterRelease(this).
Release(script_state, this);
}
ScriptPromise WritableStreamDefaultWriter::write(ScriptState* script_state) {
return write(script_state,
ScriptValue(script_state->GetIsolate(),
v8::Undefined(script_state->GetIsolate())));
}
ScriptPromise WritableStreamDefaultWriter::write(ScriptState* script_state,
ScriptValue chunk) {
// https://streams.spec.whatwg.org/#default-writer-write
// 2. If this.[[ownerWritableStream]] is undefined, return a promise rejected
// with a TypeError exception.
if (!owner_writable_stream_) {
return ScriptPromise::Reject(script_state,
CreateWriterLockReleasedException(
script_state->GetIsolate(), "written to"));
}
// 3. Return ! WritableStreamDefaultWriterWrite(this, chunk).
return ScriptPromise(script_state,
Write(script_state, this, chunk.V8Value()));
}
void WritableStreamDefaultWriter::EnsureReadyPromiseRejected(
ScriptState* script_state,
WritableStreamDefaultWriter* writer,
v8::Local<v8::Value> error) {
auto* isolate = script_state->GetIsolate();
// https://streams.spec.whatwg.org/#writable-stream-default-writer-ensure-ready-promise-rejected
// 1. If writer.[[readyPromise]].[[PromiseState]] is "pending", reject
// writer.[[readyPromise]] with error.
if (!writer->ready_promise_->IsSettled()) {
writer->ready_promise_->Reject(script_state, error);
} else {
// 2. Otherwise, set writer.[[readyPromise]] to a promise rejected with
// error.
writer->ready_promise_ =
StreamPromiseResolver::CreateRejected(script_state, error);
}
// 3. Set writer.[[readyPromise]].[[PromiseIsHandled]] to true.
writer->ready_promise_->MarkAsHandled(isolate);
}
v8::Local<v8::Promise> WritableStreamDefaultWriter::CloseWithErrorPropagation(
ScriptState* script_state,
WritableStreamDefaultWriter* writer) {
// https://streams.spec.whatwg.org/#writable-stream-default-writer-close-with-error-propagation
// 1. Let stream be writer.[[ownerWritableStream]].
WritableStream* stream = writer->owner_writable_stream_;
// 2. Assert: stream is not undefined.
DCHECK(stream);
// 3. Let state be stream.[[state]].
const auto state = stream->GetState();
// 4. If ! WritableStreamCloseQueuedOrInFlight(stream) is true or state is
// "closed", return a promise resolved with undefined.
if (WritableStream::CloseQueuedOrInFlight(stream) ||
state == WritableStream::kClosed) {
return PromiseResolveWithUndefined(script_state);
}
// 5. If state is "errored", return a promise rejected with
// stream.[[storedError]].
if (state == WritableStream::kErrored) {
return PromiseReject(script_state,
stream->GetStoredError(script_state->GetIsolate()));
}
// 6. Assert: state is "writable" or "erroring".
DCHECK(state == WritableStream::kWritable ||
state == WritableStream::kErroring);
// 7. Return ! WritableStreamDefaultWriterClose(writer).
return Close(script_state, writer);
}
void WritableStreamDefaultWriter::Release(ScriptState* script_state,
WritableStreamDefaultWriter* writer) {
// https://streams.spec.whatwg.org/#writable-stream-default-writer-release
// 1. Let stream be writer.[[ownerWritableStream]].
WritableStream* stream = writer->owner_writable_stream_;
// 2. Assert: stream is not undefined.
DCHECK(stream);
// 3. Assert: stream.[[writer]] is writer.
DCHECK_EQ(stream->Writer(), writer);
// 4. Let releasedError be a new TypeError.
const auto released_error = v8::Exception::TypeError(V8String(
script_state->GetIsolate(),
"This writable stream writer has been released and cannot be used to "
"monitor the stream\'s state"));
// 5. Perform ! WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer,
// releasedError).
EnsureReadyPromiseRejected(script_state, writer, released_error);
// 6. Perform !
// WritableStreamDefaultWriterEnsureClosedPromiseRejected(writer,
// releasedError).
EnsureClosedPromiseRejected(script_state, writer, released_error);
// 7. Set stream.[[writer]] to undefined.
stream->SetWriter(nullptr);
// 8. Set writer.[[ownerWritableStream]] to undefined.
writer->owner_writable_stream_ = nullptr;
}
v8::Local<v8::Promise> WritableStreamDefaultWriter::Write(
ScriptState* script_state,
WritableStreamDefaultWriter* writer,
v8::Local<v8::Value> chunk) {
// https://streams.spec.whatwg.org/#writable-stream-default-writer-write
// 1. Let stream be writer.[[ownerWritableStream]].
WritableStream* stream = writer->owner_writable_stream_;
// 2. Assert: stream is not undefined.
DCHECK(stream);
// 3. Let controller be stream.[[writableStreamController]].
WritableStreamDefaultController* controller = stream->Controller();
auto* isolate = script_state->GetIsolate();
// 4. Let chunkSize be !
// WritableStreamDefaultControllerGetChunkSize(controller, chunk).
double chunk_size = WritableStreamDefaultController::GetChunkSize(
script_state, controller, chunk);
// 5. If stream is not equal to writer.[[ownerWritableStream]], return a
// promise rejected with a TypeError exception.
if (stream != writer->owner_writable_stream_) {
return PromiseReject(
script_state, CreateWriterLockReleasedException(isolate, "written to"));
}
// 6. Let state be stream.[[state]].
const auto state = stream->GetState();
// 7. If state is "errored", return a promise rejected with
// stream.[[storedError]].
if (state == WritableStream::kErrored) {
return PromiseReject(script_state, stream->GetStoredError(isolate));
}
// 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 (WritableStream::CloseQueuedOrInFlight(stream)) {
return PromiseReject(
script_state,
v8::Exception::TypeError(CreateCannotActionOnStateStreamMessage(
isolate, "write to", "closing")));
}
if (state == WritableStream::kClosed) {
return PromiseReject(script_state,
CreateCannotActionOnStateStreamException(
isolate, "write to", WritableStream::kClosed));
}
// 9. If state is "erroring", return a promise rejected with
// stream.[[storedError]].
if (state == WritableStream::kErroring) {
return PromiseReject(script_state, stream->GetStoredError(isolate));
}
// 10. Assert: state is "writable".
DCHECK_EQ(state, WritableStream::kWritable);
// 11. Let promise be ! WritableStreamAddWriteRequest(stream).
auto promise = WritableStream::AddWriteRequest(script_state, stream);
// 12. Perform ! WritableStreamDefaultControllerWrite(controller, chunk,
// chunkSize).
WritableStreamDefaultController::Write(script_state, controller, chunk,
chunk_size);
// 13. Return promise.
return promise;
}
base::Optional<double> WritableStreamDefaultWriter::GetDesiredSizeInternal()
const {
// https://streams.spec.whatwg.org/#writable-stream-default-writer-get-desired-size
// 1. Let stream be writer.[[ownerWritableStream]].
const WritableStream* stream = owner_writable_stream_;
// 2. Let state be stream.[[state]].
const auto state = stream->GetState();
switch (state) {
// 3. If state is "errored" or "erroring", return null.
case WritableStream::kErrored:
case WritableStream::kErroring:
return base::nullopt;
// 4. If state is "closed", return 0.
case WritableStream::kClosed:
return 0.0;
default:
// 5. Return ! WritableStreamDefaultControllerGetDesiredSize(
// stream.[[writableStreamController]]).
return WritableStreamDefaultController::GetDesiredSize(
stream->Controller());
}
}
void WritableStreamDefaultWriter::SetReadyPromise(
StreamPromiseResolver* ready_promise) {
ready_promise_ = ready_promise;
}
void WritableStreamDefaultWriter::Trace(Visitor* visitor) {
visitor->Trace(closed_promise_);
visitor->Trace(owner_writable_stream_);
visitor->Trace(ready_promise_);
ScriptWrappable::Trace(visitor);
}
// Writable Stream Writer Abstract Operations
v8::Local<v8::Promise> WritableStreamDefaultWriter::Abort(
ScriptState* script_state,
WritableStreamDefaultWriter* writer,
v8::Local<v8::Value> reason) {
// https://streams.spec.whatwg.org/#writable-stream-default-writer-abort
// 1. Let stream be writer.[[ownerWritableStream]].
WritableStream* stream = writer->owner_writable_stream_;
// 2. Assert: stream is not undefined.
DCHECK(stream);
// 3. Return ! WritableStreamAbort(stream, reason).
return WritableStream::Abort(script_state, stream, reason);
}
v8::Local<v8::Promise> WritableStreamDefaultWriter::Close(
ScriptState* script_state,
WritableStreamDefaultWriter* writer) {
// https://streams.spec.whatwg.org/#writable-stream-default-writer-close
// 1. Let stream be writer.[[ownerWritableStream]].
WritableStream* stream = writer->owner_writable_stream_;
// 2. Assert: stream is not undefined.
DCHECK(stream);
// 3. Let state be stream.[[state]].
const auto state = stream->GetState();
// 4. If state is "closed" or "errored", return a promise rejected with a
// TypeError exception.
if (state == WritableStream::kClosed || state == WritableStream::kErrored) {
return PromiseReject(script_state,
CreateCannotActionOnStateStreamException(
script_state->GetIsolate(), "close", state));
}
// 5. Assert: state is "writable" or "erroring".
DCHECK(state == WritableStream::kWritable ||
state == WritableStream::kErroring);
// 6. Assert: ! WritableStreamCloseQueuedOrInFlight(stream) is false.
DCHECK(!WritableStream::CloseQueuedOrInFlight(stream));
// 7. Let promise be a new promise.
auto* promise = MakeGarbageCollected<StreamPromiseResolver>(script_state);
// 8. Set stream.[[closeRequest]] to promise.
stream->SetCloseRequest(promise);
// 9. If stream.[[backpressure]] is true and state is "writable", resolve
// writer.[[readyPromise]] with undefined.
if (stream->HasBackpressure() && state == WritableStream::kWritable) {
writer->ready_promise_->ResolveWithUndefined(script_state);
}
// 10. Perform ! WritableStreamDefaultControllerClose(
// stream.[[writableStreamController]]).
WritableStreamDefaultController::Close(script_state, stream->Controller());
// 11. Return promise.
return promise->V8Promise(script_state->GetIsolate());
}
void WritableStreamDefaultWriter::EnsureClosedPromiseRejected(
ScriptState* script_state,
WritableStreamDefaultWriter* writer,
v8::Local<v8::Value> error) {
auto* isolate = script_state->GetIsolate();
// https://streams.spec.whatwg.org/#writable-stream-default-writer-ensure-closed-promise-rejected
// 1. If writer.[[closedPromise]].[[PromiseState]] is "pending", reject
// writer.[[closedPromise]] with error.
if (!writer->closed_promise_->IsSettled()) {
writer->closed_promise_->Reject(script_state, error);
} else {
// 2. Otherwise, set writer.[[closedPromise]] to a promise rejected with
// error.
writer->closed_promise_ =
StreamPromiseResolver::CreateRejected(script_state, error);
}
// 3. Set writer.[[closedPromise]].[[PromiseIsHandled]] to true.
writer->closed_promise_->MarkAsHandled(isolate);
}
v8::Local<v8::Value> WritableStreamDefaultWriter::GetDesiredSize(
v8::Isolate* isolate,
const WritableStreamDefaultWriter* writer) {
// https://streams.spec.whatwg.org/#writable-stream-default-writer-get-desired-size
// 1. Let stream be writer.[[ownerWritableStream]].
// 2. Let state be stream.[[state]].
// 3. If state is "errored" or "erroring", return null.
base::Optional<double> desired_size = writer->GetDesiredSizeInternal();
if (!desired_size.has_value()) {
return v8::Null(isolate);
}
// 4. If state is "closed", return 0.
// 5. Return ! WritableStreamDefaultControllerGetDesiredSize(
// stream.[[writableStreamController]]).
return v8::Number::New(isolate, desired_size.value());
}
} // namespace blink