/* -*- 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() // Streams spec, 3.8.2. IsReadableStreamBYOBReader ( x ) // Implemented via is() /** * Streams spec, 3.8.3. ReadableStreamReaderGenericCancel ( reader, reason ) */ MOZ_MUST_USE JSObject* js::ReadableStreamReaderGenericCancel( JSContext* cx, Handle unwrappedReader, Handle reason) { // Step 1: Let stream be reader.[[ownerReadableStream]]. // Step 2: Assert: stream is not undefined (implicit). Rooted 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 reader, Handle unwrappedStream, ForAuthorCodeBool forAuthorCode) { cx->check(reader); // Step 1: Set reader.[[forAuthorCode]] to true. reader->setForAuthorCode(forAuthorCode); // Step 2: Set reader.[[ownerReadableStream]] to stream. { Rooted 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 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 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 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 unwrappedReader) { // Step 1: Assert: reader.[[ownerReadableStream]] is not undefined. Rooted 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 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 unwrappedClosedPromise(cx); if (unwrappedStream->readable()) { unwrappedClosedPromise = UnwrapInternalSlot( 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 closedPromise(cx, PromiseObject::unforgeableReject(cx, exn)); if (!closedPromise) { return false; } unwrappedClosedPromise = &closedPromise->as(); 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 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 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 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 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 unwrappedController( cx, unwrappedStream->controller()); return ReadableStreamControllerPullSteps(cx, unwrappedController); }