diff options
Diffstat (limited to '')
-rw-r--r-- | js/src/builtin/streams/ReadableStreamReader.cpp | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/js/src/builtin/streams/ReadableStreamReader.cpp b/js/src/builtin/streams/ReadableStreamReader.cpp new file mode 100644 index 0000000000..f384bce83e --- /dev/null +++ b/js/src/builtin/streams/ReadableStreamReader.cpp @@ -0,0 +1,276 @@ +/* -*- 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/. */ + +/* ReadableStream reader abstract operations. */ + +#include "builtin/streams/ReadableStreamReader-inl.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF} +#include "mozilla/Attributes.h" // MOZ_MUST_USE + +#include "jsfriendapi.h" // JS_ReportErrorNumberASCII + +#include "builtin/Stream.h" // js::ReadableStreamController, js::ReadableStreamControllerPullSteps +#include "builtin/streams/ReadableStream.h" // js::ReadableStream +#include "builtin/streams/ReadableStreamController.h" // js::ReadableStreamController +#include "builtin/streams/ReadableStreamInternals.h" // js::ReadableStream{Cancel,CreateReadResult} +#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* +#include "js/RootingAPI.h" // JS::Handle, JS::Rooted +#include "js/Value.h" // JS::Value, JS::UndefinedHandleValue +#include "vm/Interpreter.h" // js::GetAndClearException +#include "vm/JSContext.h" // JSContext +#include "vm/PlainObject.h" // js::PlainObject +#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined +#include "vm/Runtime.h" // JSRuntime + +#include "builtin/Promise-inl.h" // js::SetSettledPromiseIsHandled +#include "vm/Compartment-inl.h" // JS::Compartment::wrap, js::UnwrapInternalSlot +#include "vm/List-inl.h" // js::StoreNewListInFixedSlot +#include "vm/Realm-inl.h" // js::AutoRealm + +using JS::Handle; +using JS::Rooted; +using JS::Value; + +using js::PromiseObject; +using js::ReadableStreamController; +using js::UnwrapStreamFromReader; + +/*** 3.8. Readable stream reader abstract operations ************************/ + +// Streams spec, 3.8.1. IsReadableStreamDefaultReader ( x ) +// Implemented via is<ReadableStreamDefaultReader>() + +// Streams spec, 3.8.2. IsReadableStreamBYOBReader ( x ) +// Implemented via is<ReadableStreamBYOBReader>() + +/** + * Streams spec, 3.8.3. ReadableStreamReaderGenericCancel ( reader, reason ) + */ +MOZ_MUST_USE JSObject* js::ReadableStreamReaderGenericCancel( + JSContext* cx, Handle<ReadableStreamReader*> unwrappedReader, + Handle<Value> reason) { + // Step 1: Let stream be reader.[[ownerReadableStream]]. + // Step 2: Assert: stream is not undefined (implicit). + Rooted<ReadableStream*> unwrappedStream( + cx, UnwrapStreamFromReader(cx, unwrappedReader)); + if (!unwrappedStream) { + return nullptr; + } + + // Step 3: Return ! ReadableStreamCancel(stream, reason). + return js::ReadableStreamCancel(cx, unwrappedStream, reason); +} + +/** + * Streams spec, 3.8.4. + * ReadableStreamReaderGenericInitialize ( reader, stream ) + */ +MOZ_MUST_USE bool js::ReadableStreamReaderGenericInitialize( + JSContext* cx, Handle<ReadableStreamReader*> reader, + Handle<ReadableStream*> unwrappedStream, ForAuthorCodeBool forAuthorCode) { + cx->check(reader); + + // Step 1: Set reader.[[forAuthorCode]] to true. + reader->setForAuthorCode(forAuthorCode); + + // Step 2: Set reader.[[ownerReadableStream]] to stream. + { + Rooted<JSObject*> readerCompartmentStream(cx, unwrappedStream); + if (!cx->compartment()->wrap(cx, &readerCompartmentStream)) { + return false; + } + reader->setStream(readerCompartmentStream); + } + + // Step 3 is moved to the end. + + // Step 4: If stream.[[state]] is "readable", + Rooted<PromiseObject*> promise(cx); + if (unwrappedStream->readable()) { + // Step a: Set reader.[[closedPromise]] to a new promise. + promise = PromiseObject::createSkippingExecutor(cx); + } else if (unwrappedStream->closed()) { + // Step 5: Otherwise, if stream.[[state]] is "closed", + // Step a: Set reader.[[closedPromise]] to a promise resolved with + // undefined. + promise = PromiseResolvedWithUndefined(cx); + } else { + // Step 6: Otherwise, + // Step a: Assert: stream.[[state]] is "errored". + MOZ_ASSERT(unwrappedStream->errored()); + + // Step b: Set reader.[[closedPromise]] to a promise rejected with + // stream.[[storedError]]. + Rooted<Value> storedError(cx, unwrappedStream->storedError()); + if (!cx->compartment()->wrap(cx, &storedError)) { + return false; + } + promise = PromiseObject::unforgeableReject(cx, storedError); + if (!promise) { + return false; + } + + // Step c. Set reader.[[closedPromise]].[[PromiseIsHandled]] to true. + js::SetSettledPromiseIsHandled(cx, promise); + } + + if (!promise) { + return false; + } + + reader->setClosedPromise(promise); + + // Step 4 of caller 3.6.3. new ReadableStreamDefaultReader(stream): + // Step 5 of caller 3.7.3. new ReadableStreamBYOBReader(stream): + // Set this.[[read{Into}Requests]] to a new empty List. + if (!StoreNewListInFixedSlot(cx, reader, + ReadableStreamReader::Slot_Requests)) { + return false; + } + + // Step 3: Set stream.[[reader]] to reader. + // Doing this last prevents a partially-initialized reader from being + // attached to the stream (and possibly left there on OOM). + { + AutoRealm ar(cx, unwrappedStream); + Rooted<JSObject*> streamCompartmentReader(cx, reader); + if (!cx->compartment()->wrap(cx, &streamCompartmentReader)) { + return false; + } + unwrappedStream->setReader(streamCompartmentReader); + } + + return true; +} + +/** + * Streams spec, 3.8.5. ReadableStreamReaderGenericRelease ( reader ) + */ +MOZ_MUST_USE bool js::ReadableStreamReaderGenericRelease( + JSContext* cx, Handle<ReadableStreamReader*> unwrappedReader) { + // Step 1: Assert: reader.[[ownerReadableStream]] is not undefined. + Rooted<ReadableStream*> unwrappedStream( + cx, UnwrapStreamFromReader(cx, unwrappedReader)); + if (!unwrappedStream) { + return false; + } + + // Step 2: Assert: reader.[[ownerReadableStream]].[[reader]] is reader. +#ifdef DEBUG + // The assertion is weakened a bit to allow for nuked wrappers. + ReadableStreamReader* unwrappedReader2 = + UnwrapReaderFromStreamNoThrow(unwrappedStream); + MOZ_ASSERT_IF(unwrappedReader2, unwrappedReader2 == unwrappedReader); +#endif + + // Create an exception to reject promises with below. We don't have a + // clean way to do this, unfortunately. + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMREADER_RELEASED); + Rooted<Value> exn(cx); + if (!cx->isExceptionPending() || !GetAndClearException(cx, &exn)) { + // Uncatchable error. Die immediately without resolving + // reader.[[closedPromise]]. + return false; + } + + // Step 3: If reader.[[ownerReadableStream]].[[state]] is "readable", reject + // reader.[[closedPromise]] with a TypeError exception. + Rooted<PromiseObject*> unwrappedClosedPromise(cx); + if (unwrappedStream->readable()) { + unwrappedClosedPromise = UnwrapInternalSlot<PromiseObject>( + cx, unwrappedReader, ReadableStreamReader::Slot_ClosedPromise); + if (!unwrappedClosedPromise) { + return false; + } + + AutoRealm ar(cx, unwrappedClosedPromise); + if (!cx->compartment()->wrap(cx, &exn)) { + return false; + } + if (!PromiseObject::reject(cx, unwrappedClosedPromise, exn)) { + return false; + } + } else { + // Step 4: Otherwise, set reader.[[closedPromise]] to a new promise + // rejected with a TypeError exception. + Rooted<JSObject*> closedPromise(cx, + PromiseObject::unforgeableReject(cx, exn)); + if (!closedPromise) { + return false; + } + unwrappedClosedPromise = &closedPromise->as<PromiseObject>(); + + AutoRealm ar(cx, unwrappedReader); + if (!cx->compartment()->wrap(cx, &closedPromise)) { + return false; + } + unwrappedReader->setClosedPromise(closedPromise); + } + + // Step 5: Set reader.[[closedPromise]].[[PromiseIsHandled]] to true. + js::SetSettledPromiseIsHandled(cx, unwrappedClosedPromise); + + // Step 6: Set reader.[[ownerReadableStream]].[[reader]] to undefined. + unwrappedStream->clearReader(); + + // Step 7: Set reader.[[ownerReadableStream]] to undefined. + unwrappedReader->clearStream(); + + return true; +} + +/** + * Streams spec, 3.8.7. + * ReadableStreamDefaultReaderRead ( reader [, forAuthorCode ] ) + */ +MOZ_MUST_USE PromiseObject* js::ReadableStreamDefaultReaderRead( + JSContext* cx, Handle<ReadableStreamDefaultReader*> unwrappedReader) { + // Step 1: If forAuthorCode was not passed, set it to false (implicit). + + // Step 2: Let stream be reader.[[ownerReadableStream]]. + // Step 3: Assert: stream is not undefined. + Rooted<ReadableStream*> unwrappedStream( + cx, UnwrapStreamFromReader(cx, unwrappedReader)); + if (!unwrappedStream) { + return nullptr; + } + + // Step 4: Set stream.[[disturbed]] to true. + unwrappedStream->setDisturbed(); + + // Step 5: If stream.[[state]] is "closed", return a promise resolved with + // ! ReadableStreamCreateReadResult(undefined, true, forAuthorCode). + if (unwrappedStream->closed()) { + PlainObject* iterResult = ReadableStreamCreateReadResult( + cx, UndefinedHandleValue, true, unwrappedReader->forAuthorCode()); + if (!iterResult) { + return nullptr; + } + + Rooted<Value> iterResultVal(cx, JS::ObjectValue(*iterResult)); + return PromiseObject::unforgeableResolveWithNonPromise(cx, iterResultVal); + } + + // Step 6: If stream.[[state]] is "errored", return a promise rejected + // with stream.[[storedError]]. + if (unwrappedStream->errored()) { + Rooted<Value> storedError(cx, unwrappedStream->storedError()); + if (!cx->compartment()->wrap(cx, &storedError)) { + return nullptr; + } + return PromiseObject::unforgeableReject(cx, storedError); + } + + // Step 7: Assert: stream.[[state]] is "readable". + MOZ_ASSERT(unwrappedStream->readable()); + + // Step 8: Return ! stream.[[readableStreamController]].[[PullSteps]](). + Rooted<ReadableStreamController*> unwrappedController( + cx, unwrappedStream->controller()); + return ReadableStreamControllerPullSteps(cx, unwrappedController); +} |