mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 10:58:56 +00:00
649 lines
24 KiB
C++
649 lines
24 KiB
C++
#include "root.h"
|
|
|
|
#include "BunWritableStream.h"
|
|
#include "BunWritableStreamDefaultController.h"
|
|
#include "BunWritableStreamDefaultWriter.h"
|
|
#include "BunStreamStructures.h"
|
|
#include "BunStreamInlines.h"
|
|
|
|
namespace Bun {
|
|
|
|
using namespace JSC;
|
|
|
|
// JSWritableStreamPrototype bindings
|
|
JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_abort, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
JSWritableStream* stream = jsDynamicCast<JSWritableStream*>(callFrame->thisValue());
|
|
if (!stream)
|
|
return throwVMTypeError(globalObject, scope, "WritableStream.prototype.abort called on non-WritableStream object"_s);
|
|
|
|
JSValue reason = callFrame->argument(0);
|
|
return JSValue::encode(stream->abort(globalObject, reason));
|
|
}
|
|
|
|
JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_close, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
JSWritableStream* stream = jsDynamicCast<JSWritableStream*>(callFrame->thisValue());
|
|
if (!stream)
|
|
return throwVMTypeError(globalObject, scope, "WritableStream.prototype.close called on non-WritableStream object"_s);
|
|
|
|
return JSValue::encode(stream->close(globalObject));
|
|
}
|
|
|
|
JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_getWriter, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
JSWritableStream* stream = jsDynamicCast<JSWritableStream*>(callFrame->thisValue());
|
|
if (!stream)
|
|
return throwVMTypeError(globalObject, scope, "WritableStream.prototype.getWriter called on non-WritableStream object"_s);
|
|
|
|
if (stream->isLocked())
|
|
return throwVMTypeError(globalObject, scope, "Cannot get writer for locked WritableStream"_s);
|
|
|
|
Structure* writerStructure = globalObject->writableStreamDefaultWriterStructure();
|
|
auto* writer = JSWritableStreamDefaultWriter::create(vm, globalObject, writerStructure, stream);
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
|
|
stream->setWriter(vm, writer);
|
|
return JSValue::encode(writer);
|
|
}
|
|
|
|
JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamPrototypeLockedGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName))
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
JSWritableStream* stream = jsDynamicCast<JSWritableStream*>(JSValue::decode(thisValue));
|
|
if (!stream)
|
|
return throwVMTypeError(globalObject, scope, "WritableStream.prototype.locked called on non-WritableStream object"_s);
|
|
|
|
return JSValue::encode(jsBoolean(stream->isLocked()));
|
|
}
|
|
|
|
// Static hash table of properties
|
|
static const HashTableValue JSWritableStreamPrototypeTableValues[] = {
|
|
{ "abort"_s,
|
|
static_cast<unsigned>(PropertyAttribute::Function),
|
|
NoIntrinsic,
|
|
{ HashTableValue::NativeFunctionType, jsWritableStreamPrototypeFunction_abort, 1 } },
|
|
{ "close"_s,
|
|
static_cast<unsigned>(PropertyAttribute::Function),
|
|
NoIntrinsic,
|
|
{ HashTableValue::NativeFunctionType, jsWritableStreamPrototypeFunction_close, 0 } },
|
|
{ "getWriter"_s,
|
|
static_cast<unsigned>(PropertyAttribute::Function),
|
|
NoIntrinsic,
|
|
{ HashTableValue::NativeFunctionType, jsWritableStreamPrototypeFunction_getWriter, 0 } },
|
|
{ "locked"_s,
|
|
static_cast<unsigned>(PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly),
|
|
NoIntrinsic,
|
|
{ HashTableValue::GetterSetterType, jsWritableStreamPrototypeLockedGetter, nullptr } }
|
|
};
|
|
|
|
class JSWritableStreamPrototype final : public JSNonFinalObject {
|
|
public:
|
|
using Base = JSNonFinalObject;
|
|
|
|
static JSWritableStreamPrototype* create(VM& vm, JSGlobalObject* globalObject, Structure* structure);
|
|
static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
|
|
{
|
|
auto* structure = Base::createStructure(vm, globalObject, prototype);
|
|
structure->setMayBePrototype(true);
|
|
return structure;
|
|
}
|
|
|
|
DECLARE_INFO;
|
|
template<typename CellType, SubspaceAccess>
|
|
static GCClient::IsoSubspace* subspaceFor(VM& vm)
|
|
{
|
|
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSWritableStreamPrototype, Base);
|
|
return &vm.plainObjectSpace();
|
|
}
|
|
|
|
private:
|
|
JSWritableStreamPrototype(VM& vm, Structure* structure)
|
|
: Base(vm, structure)
|
|
{
|
|
}
|
|
|
|
void finishCreation(VM& vm, JSGlobalObject* globalObject);
|
|
};
|
|
|
|
class JSWritableStreamConstructor final : public InternalFunction {
|
|
public:
|
|
using Base = InternalFunction;
|
|
static constexpr unsigned StructureFlags = Base::StructureFlags;
|
|
|
|
static JSWritableStreamConstructor* create(VM&, JSGlobalObject*, Structure*, JSWritableStreamPrototype*);
|
|
DECLARE_INFO;
|
|
|
|
template<typename CellType, SubspaceAccess mode>
|
|
static GCClient::IsoSubspace* subspaceFor(VM& vm)
|
|
{
|
|
if constexpr (mode == SubspaceAccess::Concurrently)
|
|
return nullptr;
|
|
return WebCore::subspaceForImpl<JSWritableStreamConstructor,
|
|
WebCore::UseCustomHeapCellType::No>(
|
|
vm,
|
|
[](auto& spaces) { return spaces.m_clientSubspaceForConstructor.get(); },
|
|
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForConstructor = std::forward<decltype(space)>(space); },
|
|
[](auto& spaces) { return spaces.m_subspaceForConstructor.get(); },
|
|
[](auto& spaces, auto&& space) { spaces.m_subspaceForConstructor = std::forward<decltype(space)>(space); });
|
|
}
|
|
|
|
static Structure* createStructure(VM&, JSGlobalObject*, JSValue prototype);
|
|
static EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSGlobalObject*, CallFrame*);
|
|
static EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSGlobalObject*, CallFrame*);
|
|
|
|
private:
|
|
JSWritableStreamConstructor(VM& vm, Structure* structure);
|
|
void finishCreation(VM& vm, JSGlobalObject* globalObject, JSWritableStreamPrototype* prototype)
|
|
{
|
|
Base::finishCreation(vm, 1, "WritableStream"_s, PropertyAdditionMode::WithStructureTransition);
|
|
this->putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, 0);
|
|
}
|
|
};
|
|
|
|
// Prototype Implementation
|
|
const ClassInfo JSWritableStreamPrototype::s_info = { "WritableStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWritableStreamPrototype) };
|
|
|
|
JSWritableStreamPrototype* JSWritableStreamPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure)
|
|
{
|
|
auto* prototype = new (NotNull, allocateCell<JSWritableStreamPrototype>(vm)) JSWritableStreamPrototype(vm, structure);
|
|
prototype->finishCreation(vm, globalObject);
|
|
return prototype;
|
|
}
|
|
|
|
void JSWritableStreamPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject)
|
|
{
|
|
Base::finishCreation(vm);
|
|
reifyStaticProperties(vm, JSWritableStream::info(), JSWritableStreamPrototypeTableValues, *this);
|
|
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
|
|
}
|
|
|
|
// Constructor Implementation
|
|
const ClassInfo JSWritableStreamConstructor::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWritableStreamConstructor) };
|
|
|
|
JSWritableStreamConstructor::JSWritableStreamConstructor(VM& vm, Structure* structure)
|
|
: Base(vm, structure, call, construct)
|
|
{
|
|
}
|
|
|
|
JSWritableStreamConstructor* JSWritableStreamConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSWritableStreamPrototype* prototype)
|
|
{
|
|
JSWritableStreamConstructor* constructor = new (NotNull, allocateCell<JSWritableStreamConstructor>(vm)) JSWritableStreamConstructor(vm, structure);
|
|
constructor->finishCreation(vm, globalObject, prototype);
|
|
return constructor;
|
|
}
|
|
|
|
Structure* JSWritableStreamConstructor::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
|
|
{
|
|
return Structure::create(vm, globalObject, prototype, TypeInfo(InternalFunctionType, StructureFlags), info());
|
|
}
|
|
|
|
JSC_DEFINE_HOST_FUNCTION(jsWritableStreamConstructor, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
JSValue newTarget = callFrame->newTarget();
|
|
if (newTarget.isUndefined())
|
|
return throwVMTypeError(globalObject, scope, "WritableStream constructor must be called with 'new'"_s);
|
|
|
|
JSObject* underlyingSink = callFrame->argument(0).getObject();
|
|
JSValue strategy = callFrame->argument(1);
|
|
|
|
JSObject* constructor = asObject(newTarget);
|
|
Structure* structure = JSC::InternalFunction::createSubclassStructure(globalObject, newTarget, globalObject->writableStreamStructure());
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
|
|
JSWritableStream* stream = JSWritableStream::create(vm, globalObject, structure);
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
|
|
// Initialize with underlying sink if provided
|
|
if (underlyingSink) {
|
|
// Set up controller with underlying sink...
|
|
auto controller = JSWritableStreamDefaultController::create(vm, globalObject, stream, underlyingSink);
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
stream->setController(controller);
|
|
}
|
|
|
|
return JSValue::encode(stream);
|
|
}
|
|
|
|
JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrivateConstructor, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
|
{
|
|
// Similar to above but for internal usage
|
|
VM& vm = globalObject->vm();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
Structure* structure = globalObject->writableStreamStructure();
|
|
JSWritableStream* stream = JSWritableStream::create(vm, globalObject, structure);
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
|
|
return JSValue::encode(stream);
|
|
}
|
|
|
|
// WritableStream implementation
|
|
const ClassInfo JSWritableStream::s_info = { "WritableStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWritableStream) };
|
|
|
|
JSWritableStream::JSWritableStream(VM& vm, Structure* structure)
|
|
: Base(vm, structure)
|
|
{
|
|
}
|
|
|
|
void JSWritableStream::finishCreation(VM& vm)
|
|
{
|
|
Base::finishCreation(vm);
|
|
ASSERT(inherits(info()));
|
|
}
|
|
|
|
JSWritableStream* JSWritableStream::create(VM& vm, JSGlobalObject* globalObject, Structure* structure)
|
|
{
|
|
JSWritableStream* stream = new (NotNull, allocateCell<JSWritableStream>(vm))
|
|
JSWritableStream(vm, structure);
|
|
stream->finishCreation(vm);
|
|
return stream;
|
|
}
|
|
|
|
void JSWritableStream::destroy(JSCell* cell)
|
|
{
|
|
static_cast<JSWritableStream*>(cell)->JSWritableStream::~JSWritableStream();
|
|
}
|
|
|
|
template<typename Visitor>
|
|
void JSWritableStream::visitChildrenImpl(JSCell* cell, Visitor& visitor)
|
|
{
|
|
auto* thisObject = jsCast<JSWritableStream*>(cell);
|
|
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
|
|
Base::visitChildren(thisObject, visitor);
|
|
|
|
visitor.append(thisObject->m_controller);
|
|
visitor.append(thisObject->m_writer);
|
|
visitor.append(thisObject->m_closeRequest);
|
|
visitor.append(thisObject->m_inFlightWriteRequest);
|
|
visitor.append(thisObject->m_inFlightCloseRequest);
|
|
visitor.append(thisObject->m_storedError);
|
|
}
|
|
|
|
DEFINE_VISIT_CHILDREN(JSWritableStream);
|
|
|
|
Structure* JSWritableStream::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
|
|
{
|
|
return Structure::create(vm, globalObject, prototype,
|
|
TypeInfo(ObjectType, StructureFlags), info());
|
|
}
|
|
|
|
bool JSWritableStream::isLocked() const
|
|
{
|
|
return !!m_writer;
|
|
}
|
|
|
|
JSValue JSWritableStream::error(JSGlobalObject* globalObject, JSValue error)
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
if (m_state != State::Writable)
|
|
return jsUndefined();
|
|
|
|
m_state = State::Errored;
|
|
m_storedError.set(vm, this, error);
|
|
|
|
if (m_writer)
|
|
m_writer->error(globalObject, error);
|
|
|
|
RELEASE_AND_RETURN(scope, jsUndefined());
|
|
}
|
|
|
|
namespace Operations {
|
|
|
|
// WritableStreamDefaultControllerErrorSteps(stream.[[writableStreamController]]).
|
|
void WritableStreamDefaultControllerErrorSteps(JSWritableStreamDefaultController* controller)
|
|
{
|
|
// 1. Let stream be controller.[[controlledWritableStream]].
|
|
ASSERT(stream);
|
|
|
|
// 2. Assert: stream.[[state]] is "writable".
|
|
ASSERT(stream->state() == JSWritableStream::State::Writable);
|
|
|
|
// 3. Perform ! WritableStreamStartErroring(stream, controller.[[signal]].[[error]]).
|
|
WritableStreamStartErroring(stream, controller->signalError());
|
|
}
|
|
|
|
JSC_DEFINE_HOST_FUNCTION(jsFunctionResolveAbortPromiseWithUndefined, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
JSPromise* promise = jsDynamicCast<JSPromise*>(callFrame->argument(1));
|
|
promise->fulfillWithNonPromise(globalObject, jsUndefined());
|
|
return JSValue::encode(jsUndefined());
|
|
}
|
|
|
|
JSC_DEFINE_HOST_FUNCTION(jsFunctionRejectAbortPromiseWithReason, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
JSPromise* promise = jsDynamicCast<JSPromise*>(callFrame->argument(1));
|
|
promise->reject(globalObject, callFrame->argument(0));
|
|
return JSValue::encode(jsUndefined());
|
|
}
|
|
|
|
static void WritableStreamStartErroring(JSWritableStream* stream, JSValue reason)
|
|
{
|
|
// 1. Assert: stream.[[storedError]] is undefined.
|
|
ASSERT(!stream->storedError() || stream->storedError().isUndefined());
|
|
|
|
// 2. Assert: stream.[[state]] is "writable".
|
|
ASSERT(stream->state() == JSWritableStream::State::Writable);
|
|
|
|
// 3. Let controller be stream.[[writableStreamController]].
|
|
auto* controller = stream->controller();
|
|
ASSERT(controller);
|
|
|
|
// 4. Set stream.[[state]] to "erroring".
|
|
stream->setState(JSWritableStream::State::Erroring);
|
|
|
|
// 5. Set stream.[[storedError]] to reason.
|
|
stream->setStoredError(reason);
|
|
|
|
// 6. Let writer be stream.[[writer]].
|
|
auto* writer = stream->writer();
|
|
|
|
// 7. If writer is not undefined, perform ! WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason).
|
|
if (writer)
|
|
WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason);
|
|
|
|
// 8. If ! WritableStreamHasOperationMarkedInFlight(stream) is false and controller.[[started]] is true,
|
|
// perform ! WritableStreamFinishErroring(stream).
|
|
if (!stream->hasOperationMarkedInFlight() && controller->started())
|
|
WritableStreamFinishErroring(stream);
|
|
}
|
|
|
|
static void WritableStreamFinishErroring(JSWritableStream* stream)
|
|
{
|
|
// 1. Assert: stream.[[state]] is "erroring".
|
|
ASSERT(stream->state() == JSWritableStream::State::Erroring);
|
|
|
|
// 2. Assert: ! WritableStreamHasOperationMarkedInFlight(stream) is false.
|
|
ASSERT(!stream->hasOperationMarkedInFlight());
|
|
|
|
// 3. Set stream.[[state]] to "errored".
|
|
stream->setState(JSWritableStream::State::Errored);
|
|
|
|
// 4. Perform ! WritableStreamDefaultControllerErrorSteps(stream.[[writableStreamController]]).
|
|
stream->controller()->errorSteps();
|
|
|
|
JSValue storedError = stream->storedError();
|
|
|
|
// 5. Let writer be stream.[[writer]].
|
|
auto* writer = stream->writer();
|
|
|
|
// 6. If writer is not undefined,
|
|
if (writer) {
|
|
// a. Let writeRequests be writer.[[writeRequests]].
|
|
// b. Set writer.[[writeRequests]] to an empty List.
|
|
// c. For each writeRequest of writeRequests,
|
|
// 1. Reject writeRequest with stream.[[storedError]].
|
|
writer->rejectWriteRequests(storedError);
|
|
}
|
|
|
|
JSPromise* abortPromise = stream->pendingAbortRequestPromise();
|
|
|
|
// 7. Let pendingAbortRequest be stream.[[pendingAbortRequest]].
|
|
// 8. If pendingAbortRequest is undefined, return.
|
|
if (!abortPromise)
|
|
return;
|
|
|
|
// 9. Set stream.[[pendingAbortRequest]] to undefined.
|
|
|
|
JSValue abortReason = stream->pendingAbortRequestReason();
|
|
bool wasAlreadyErroring = stream->wasAlreadyErroring();
|
|
stream->clearPendingAbortRequest();
|
|
|
|
// 10. If pendingAbortRequest.[[wasAlreadyErroring]] is true,
|
|
if (wasAlreadyErroring) {
|
|
// a. Reject pendingAbortRequest.[[promise]] with pendingAbortRequest.[[reason]].
|
|
abortPromise->(abortReason);
|
|
// b. Return.
|
|
return;
|
|
}
|
|
|
|
// 11. Let abortAlgorithm be stream.[[writableStreamController]].[[abortAlgorithm]].
|
|
// 12. Let result be the result of performing abortAlgorithm with argument pendingAbortRequest.[[reason]].
|
|
JSValue result = stream->controller()->performAbortAlgorithm(abortReason);
|
|
|
|
// 13. Upon fulfillment of result,
|
|
// a. Resolve pendingAbortRequest.[[promise]] with undefined.
|
|
// 14. Upon rejection of result with reason r,
|
|
// a. Reject pendingAbortRequest.[[promise]] with r.
|
|
if (JSPromise* resultPromise = jsDynamicCast<JSPromise*>(result)) {
|
|
Bun::performPromiseThen(vm, globalObject, resultPromise,
|
|
jsFunctionResolveAbortPromiseWithUndefined,
|
|
jsFunctionRejectAbortPromiseWithReason);
|
|
} else {
|
|
// If not a promise, treat as fulfilled
|
|
abortPromise->resolve(jsUndefined());
|
|
}
|
|
}
|
|
|
|
static JSValue WritableStreamAbort(JSGlobalObject* globalObject, JSWritableStream* stream, JSValue reason)
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
// 1. Let state be stream.[[state]].
|
|
const auto state = stream->state();
|
|
|
|
// 2. If state is "closed" or state is "errored", return a promise resolved with undefined.
|
|
if (state == JSWritableStream::State::Closed || state == JSWritableStream::State::Errored) {
|
|
return JSPromise::resolvedPromise(globalObject, jsUndefined());
|
|
}
|
|
|
|
// 3. If stream.[[pendingAbortRequest]] is not undefined, return stream.[[pendingAbortRequest]].[[promise]].
|
|
if (auto promise = stream->pendingAbortRequestPromise())
|
|
return promise;
|
|
|
|
// 4. Assert: state is "writable" or state is "erroring".
|
|
ASSERT(state == JSWritableStream::State::Writable || state == JSWritableStream::State::Erroring);
|
|
|
|
// 5. Let wasAlreadyErroring be false.
|
|
bool wasAlreadyErroring = false;
|
|
|
|
// 6. If state is "erroring",
|
|
if (state == JSWritableStream::State::Erroring) {
|
|
// a. Set wasAlreadyErroring to true.
|
|
wasAlreadyErroring = true;
|
|
// b. Set reason to undefined.
|
|
reason = jsUndefined();
|
|
}
|
|
|
|
// 7. Let promise be a new promise.
|
|
JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure());
|
|
|
|
// 8. Set stream.[[pendingAbortRequest]] to record {[[promise]]: promise, [[reason]]: reason,
|
|
// [[wasAlreadyErroring]]: wasAlreadyErroring}.
|
|
stream->setPendingAbortRequest(vm, promise, reason, wasAlreadyErroring);
|
|
|
|
// 9. If wasAlreadyErroring is false, perform ! WritableStreamStartErroring(stream, reason).
|
|
if (!wasAlreadyErroring) {
|
|
WritableStreamStartErroring(stream, reason);
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
}
|
|
|
|
// 10. If stream.[[state]] is "errored", perform ! WritableStreamFinishErroring(stream).
|
|
if (stream->state() == JSWritableStream::State::Errored) {
|
|
WritableStreamFinishErroring(stream);
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
}
|
|
|
|
// 11. Return promise.
|
|
return promise;
|
|
}
|
|
}
|
|
|
|
JSValue JSWritableStream::abort(JSGlobalObject* globalObject, JSValue reason)
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
// 1. If ! IsWritableStreamLocked(this) is true, return a promise rejected with a TypeError exception.
|
|
if (isLocked())
|
|
return JSPromise::rejectedPromise(globalObject, createTypeError(globalObject, "Cannot abort a locked WritableStream"_s));
|
|
|
|
// 2. Let state be this.[[state]].
|
|
const auto state = m_state;
|
|
|
|
// 3. If state is "closed" or state is "errored", return a promise resolved with undefined.
|
|
if (state == State::Closed || state == State::Errored)
|
|
return JSPromise::resolvedPromise(globalObject, jsUndefined());
|
|
|
|
// 4. If this.[[pendingAbortRequest]] is not undefined, return this.[[pendingAbortRequest]].[[promise]].
|
|
if (auto promise = m_pendingAbortRequestPromise.get())
|
|
return promise;
|
|
|
|
// 5. Assert: state is "writable" or state is "erroring".
|
|
ASSERT(state == State::Writable || state == State::Erroring);
|
|
|
|
// 6. Let wasAlreadyErroring be false.
|
|
bool wasAlreadyErroring = false;
|
|
|
|
// 7. If state is "erroring",
|
|
if (state == State::Erroring) {
|
|
// a. Set wasAlreadyErroring to true.
|
|
wasAlreadyErroring = true;
|
|
// b. Set reason to undefined.
|
|
reason = jsUndefined();
|
|
}
|
|
|
|
// 8. Let promise be a new promise.
|
|
JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure());
|
|
|
|
// 9. Set this.[[pendingAbortRequest]] to record {[[promise]]: promise, [[reason]]: reason, [[wasAlreadyErroring]]: wasAlreadyErroring}.
|
|
m_pendingAbortRequestPromise.set(vm, this, promise);
|
|
m_pendingAbortRequestReason.set(vm, this, reason);
|
|
m_wasAlreadyErroring = wasAlreadyErroring;
|
|
|
|
// 10. If wasAlreadyErroring is false, perform ! WritableStreamStartErroring(this, reason).
|
|
if (!wasAlreadyErroring)
|
|
Operations::WritableStreamStartErroring(this, reason);
|
|
|
|
// 11. If this.[[state]] is "errored", perform ! WritableStreamFinishErroring(this).
|
|
if (m_state == State::Errored)
|
|
Operations::WritableStreamFinishErroring(this);
|
|
|
|
// 12. Return promise.
|
|
return promise;
|
|
}
|
|
|
|
JSValue JSWritableStream::close(JSGlobalObject* globalObject)
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
// 1. If ! IsWritableStreamLocked(this) is true, return a promise rejected with a TypeError exception.
|
|
if (isLocked())
|
|
return JSPromise::rejectedPromise(globalObject, createTypeError(globalObject, "Cannot close a locked WritableStream"_s));
|
|
|
|
// 2. If ! WritableStreamCloseQueuedOrInFlight(this) is true, return a promise rejected with a TypeError exception.
|
|
if (m_closeRequest || m_inFlightCloseRequest)
|
|
return JSPromise::rejectedPromise(globalObject, createTypeError(globalObject, "Cannot close an already closing stream"_s));
|
|
|
|
// 3. Let state be this.[[state]].
|
|
const auto state = m_state;
|
|
|
|
// 4. If state is "closed", return a promise rejected with a TypeError exception.
|
|
if (state == State::Closed)
|
|
return JSPromise::rejectedPromise(globalObject, createTypeError(globalObject, "Cannot close an already closed stream"_s));
|
|
|
|
// 5. If state is "errored", return a promise rejected with this.[[storedError]].
|
|
if (state == State::Errored)
|
|
return JSPromise::rejectedPromise(globalObject, m_storedError.get());
|
|
|
|
// 6. If state is "erroring", return a promise rejected with this.[[storedError]].
|
|
if (state == State::Erroring)
|
|
return JSPromise::rejectedPromise(globalObject, m_storedError.get());
|
|
|
|
// 7. Assert: state is "writable".
|
|
ASSERT(state == State::Writable);
|
|
|
|
// 8. Let closeRequest be ! WritableStreamCreateCloseRequest(this).
|
|
JSPromise* closeRequest = JSPromise::create(vm, globalObject->promiseStructure());
|
|
m_closeRequest.set(vm, this, closeRequest);
|
|
|
|
// 9. Perform ! WritableStreamDefaultControllerClose(this.[[controller]]).
|
|
m_controller->close(globalObject);
|
|
|
|
// 10. Return closeRequest.[[promise]].
|
|
return closeRequest;
|
|
}
|
|
|
|
void JSWritableStream::finishInFlightClose()
|
|
{
|
|
VM& vm = m_controller->vm();
|
|
JSGlobalObject* globalObject = m_controller->globalObject();
|
|
|
|
// 1. Assert: this.[[inFlightCloseRequest]] is not undefined.
|
|
ASSERT(m_inFlightCloseRequest);
|
|
|
|
// 2. Resolve this.[[inFlightCloseRequest]] with undefined.
|
|
m_inFlightCloseRequest->resolve(globalObject, jsUndefined());
|
|
|
|
// 3. Set this.[[inFlightCloseRequest]] to undefined.
|
|
m_inFlightCloseRequest.clear();
|
|
|
|
// 4. Set this.[[state]] to "closed".
|
|
m_state = State::Closed;
|
|
|
|
// 5. Let writer be this.[[writer]].
|
|
auto* writer = m_writer.get();
|
|
|
|
// 6. If writer is not undefined,
|
|
if (writer) {
|
|
// a. Resolve writer.[[closedPromise]] with undefined.
|
|
writer->resolveClosedPromise(globalObject, jsUndefined());
|
|
}
|
|
}
|
|
|
|
void JSWritableStream::finishInFlightCloseWithError(JSValue error)
|
|
{
|
|
VM& vm = m_controller->vm();
|
|
JSGlobalObject* globalObject = m_controller->globalObject();
|
|
|
|
// 1. Assert: this.[[inFlightCloseRequest]] is not undefined.
|
|
ASSERT(m_inFlightCloseRequest);
|
|
|
|
// 2. Reject this.[[inFlightCloseRequest]] with error.
|
|
m_inFlightCloseRequest->reject(globalObject, error);
|
|
|
|
// 3. Set this.[[inFlightCloseRequest]] to undefined.
|
|
m_inFlightCloseRequest.clear();
|
|
|
|
// 4. Set this.[[state]] to "errored".
|
|
m_state = State::Errored;
|
|
|
|
// 5. Set this.[[storedError]] to error.
|
|
m_storedError.set(vm, this, error);
|
|
|
|
// 6. Let writer be this.[[writer]].
|
|
auto* writer = m_writer.get();
|
|
|
|
// 7. If writer is not undefined,
|
|
if (writer) {
|
|
// a. Reject writer.[[closedPromise]] with error.
|
|
writer->rejectClosedPromise(globalObject, error);
|
|
}
|
|
}
|
|
|
|
}
|