Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/ReadableStreamBYOBReader.h"
#include "ReadIntoRequest.h"
#include "js/ArrayBuffer.h"
#include "js/experimental/TypedData.h"
#include "mozilla/dom/ReadableStreamBYOBReader.h"
#include "mozilla/dom/ReadableStream.h"
#include "mozilla/dom/ReadableStreamBYOBReaderBinding.h"
#include "mozilla/dom/ReadableStreamGenericReader.h"
#include "mozilla/dom/RootedDictionary.h"
#include "nsCOMPtr.h"
#include "nsISupportsImpl.h"
// Temporary Includes
#include "mozilla/dom/ReadableByteStreamController.h"
#include "mozilla/dom/ReadableStreamBYOBRequest.h"
namespace mozilla::dom {
using namespace streams_abstract;
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_INHERITED(ReadableStreamBYOBReader,
ReadableStreamGenericReader,
mReadIntoRequests)
NS_IMPL_ADDREF_INHERITED(ReadableStreamBYOBReader, ReadableStreamGenericReader)
NS_IMPL_RELEASE_INHERITED(ReadableStreamBYOBReader, ReadableStreamGenericReader)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadableStreamBYOBReader)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_END_INHERITING(ReadableStreamGenericReader)
ReadableStreamBYOBReader::ReadableStreamBYOBReader(nsISupports* aGlobal)
: ReadableStreamGenericReader(do_QueryInterface(aGlobal)) {}
JSObject* ReadableStreamBYOBReader::WrapObject(
JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
return ReadableStreamBYOBReader_Binding::Wrap(aCx, this, aGivenProto);
}
void SetUpReadableStreamBYOBReader(ReadableStreamBYOBReader* reader,
ReadableStream& stream, ErrorResult& rv) {
// Step 1. If !IsReadableStreamLocked(stream) is true, throw a TypeError
// exception.
if (IsReadableStreamLocked(&stream)) {
rv.ThrowTypeError("Trying to read locked stream");
return;
}
// Step 2. If stream.[[controller]] does not implement
// ReadableByteStreamController, throw a TypeError exception.
if (!stream.Controller()->IsByte()) {
rv.ThrowTypeError("Trying to read with incompatible controller");
return;
}
// Step 3. Perform ! ReadableStreamReaderGenericInitialize(reader, stream).
ReadableStreamReaderGenericInitialize(reader, &stream);
// Step 4. Set reader.[[readIntoRequests]] to a new empty list.
reader->ReadIntoRequests().clear();
}
/* static */ already_AddRefed<ReadableStreamBYOBReader>
ReadableStreamBYOBReader::Constructor(const GlobalObject& global,
ReadableStream& stream, ErrorResult& rv) {
nsCOMPtr<nsIGlobalObject> globalObject =
do_QueryInterface(global.GetAsSupports());
RefPtr<ReadableStreamBYOBReader> reader =
new ReadableStreamBYOBReader(globalObject);
// Step 1.
SetUpReadableStreamBYOBReader(reader, stream, rv);
if (rv.Failed()) {
return nullptr;
}
return reader.forget();
}
struct Read_ReadIntoRequest final : public ReadIntoRequest {
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Read_ReadIntoRequest,
ReadIntoRequest)
RefPtr<Promise> mPromise;
explicit Read_ReadIntoRequest(Promise* aPromise) : mPromise(aPromise) {}
void ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
ErrorResult& aRv) override {
MOZ_ASSERT(aChunk.isObject());
//
// chunk steps, given chunk:
// Resolve promise with «[ "value" → chunk, "done" → false ]».
// We need to wrap this as the chunk could have come from
// another compartment.
JS::Rooted<JSObject*> chunk(aCx, &aChunk.toObject());
if (!JS_WrapObject(aCx, &chunk)) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
RootedDictionary<ReadableStreamReadResult> result(aCx);
result.mValue = aChunk;
result.mDone.Construct(false);
mPromise->MaybeResolve(result);
}
void CloseSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
ErrorResult& aRv) override {
MOZ_ASSERT(aChunk.isObject() || aChunk.isUndefined());
//
// close steps, given chunk:
// Resolve promise with «[ "value" → chunk, "done" → true ]».
RootedDictionary<ReadableStreamReadResult> result(aCx);
if (aChunk.isObject()) {
// We need to wrap this as the chunk could have come from
// another compartment.
JS::Rooted<JSObject*> chunk(aCx, &aChunk.toObject());
if (!JS_WrapObject(aCx, &chunk)) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
result.mValue = aChunk;
}
result.mDone.Construct(true);
mPromise->MaybeResolve(result);
}
void ErrorSteps(JSContext* aCx, JS::Handle<JS::Value> e,
ErrorResult& aRv) override {
//
// error steps, given e:
// Reject promise with e.
mPromise->MaybeReject(e);
}
protected:
~Read_ReadIntoRequest() override = default;
};
NS_IMPL_CYCLE_COLLECTION(ReadIntoRequest)
NS_IMPL_CYCLE_COLLECTING_ADDREF(ReadIntoRequest)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ReadIntoRequest)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadIntoRequest)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_INHERITED(Read_ReadIntoRequest, ReadIntoRequest,
mPromise)
NS_IMPL_ADDREF_INHERITED(Read_ReadIntoRequest, ReadIntoRequest)
NS_IMPL_RELEASE_INHERITED(Read_ReadIntoRequest, ReadIntoRequest)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Read_ReadIntoRequest)
NS_INTERFACE_MAP_END_INHERITING(ReadIntoRequest)
namespace streams_abstract {
void ReadableStreamBYOBReaderRead(JSContext* aCx,
ReadableStreamBYOBReader* aReader,
JS::Handle<JSObject*> aView, uint64_t aMin,
ReadIntoRequest* aReadIntoRequest,
ErrorResult& aRv) {
// Step 1.Let stream be reader.[[stream]].
ReadableStream* stream = aReader->GetStream();
// Step 2. Assert: stream is not undefined.
MOZ_ASSERT(stream);
// Step 3. Set stream.[[disturbed]] to true.
stream->SetDisturbed(true);
// Step 4. If stream.[[state]] is "errored", perform readIntoRequest’s error
// steps given stream.[[storedError]].
if (stream->State() == ReadableStream::ReaderState::Errored) {
JS::Rooted<JS::Value> error(aCx, stream->StoredError());
aReadIntoRequest->ErrorSteps(aCx, error, aRv);
return;
}
// Step 5. Otherwise, perform
// !ReadableByteStreamControllerPullInto(stream.[[controller]], view, min,
// readIntoRequest).
MOZ_ASSERT(stream->Controller()->IsByte());
RefPtr<ReadableByteStreamController> controller(
stream->Controller()->AsByte());
ReadableByteStreamControllerPullInto(aCx, controller, aView, aMin,
aReadIntoRequest, aRv);
}
} // namespace streams_abstract
already_AddRefed<Promise> ReadableStreamBYOBReader::Read(
const ArrayBufferView& aArray,
const ReadableStreamBYOBReaderReadOptions& aOptions, ErrorResult& aRv) {
AutoJSAPI jsapi;
if (!jsapi.Init(GetParentObject())) {
aRv.ThrowUnknownError("Internal error");
return nullptr;
}
JSContext* cx = jsapi.cx();
JS::Rooted<JSObject*> view(cx, aArray.Obj());
// Step 1. If view.[[ByteLength]] is 0, return a promise rejected with a
// TypeError exception.
if (JS_GetArrayBufferViewByteLength(view) == 0) {
// Binding code should convert this thrown value into a rejected promise.
aRv.ThrowTypeError("Zero Length View");
return nullptr;
}
// Step 2. If view.[[ViewedArrayBuffer]].[[ArrayBufferByteLength]] is 0,
// return a promise rejected with a TypeError exception.
bool isSharedMemory;
JS::Rooted<JSObject*> viewedArrayBuffer(
cx, JS_GetArrayBufferViewBuffer(cx, view, &isSharedMemory));
if (!viewedArrayBuffer) {
aRv.StealExceptionFromJSContext(cx);
return nullptr;
}
if (JS::GetArrayBufferByteLength(viewedArrayBuffer) == 0) {
aRv.ThrowTypeError("zero length viewed buffer");
return nullptr;
}
// Step 3. If ! IsDetachedBuffer(view.[[ViewedArrayBuffer]]) is true, return a
// promise rejected with a TypeError exception.
if (JS::IsDetachedArrayBufferObject(viewedArrayBuffer)) {
aRv.ThrowTypeError("Detached Buffer");
return nullptr;
}
// Step 4. If options["min"] is 0, return a promise rejected with a TypeError
// exception.
if (aOptions.mMin == 0) {
aRv.ThrowTypeError(
"Zero is not a valid value for 'min' member of "
"ReadableStreamBYOBReaderReadOptions.");
return nullptr;
}
// Step 5. If view has a [[TypedArrayName]] internal slot,
if (JS_IsTypedArrayObject(view)) {
// Step 5.1. If options["min"] > view.[[ArrayLength]], return a promise
// rejected with a RangeError exception.
if (aOptions.mMin > JS_GetTypedArrayLength(view)) {
aRv.ThrowRangeError(
"Array length exceeded by 'min' member of "
"ReadableStreamBYOBReaderReadOptions.");
return nullptr;
}
} else {
// Step 6. Otherwise (i.e., it is a DataView),
// Step 6.1. If options["min"] > view.[[ByteLength]], return a promise
// rejected with a RangeError exception.
if (aOptions.mMin > JS_GetArrayBufferViewByteLength(view)) {
aRv.ThrowRangeError(
"byteLength exceeded by 'min' member of "
"ReadableStreamBYOBReaderReadOptions.");
return nullptr;
}
}
// Step 7. If this.[[stream]] is undefined, return a promise rejected with a
// TypeError exception.
if (!GetStream()) {
aRv.ThrowTypeError("Reader has undefined stream");
return nullptr;
}
// Step 8. Let promise be a new promise.
RefPtr<Promise> promise = Promise::CreateInfallible(GetParentObject());
// Step 9. Let readIntoRequest be a new read-into request with the following
// items:
RefPtr<ReadIntoRequest> readIntoRequest = new Read_ReadIntoRequest(promise);
// Step 10. Perform ! ReadableStreamBYOBReaderRead(this, view, options["min"],
// readIntoRequest).
ReadableStreamBYOBReaderRead(cx, this, view, aOptions.mMin, readIntoRequest,
aRv);
if (aRv.Failed()) {
return nullptr;
}
// Step 11. Return promise.
return promise.forget();
}
namespace streams_abstract {
void ReadableStreamBYOBReaderErrorReadIntoRequests(
JSContext* aCx, ReadableStreamBYOBReader* aReader,
JS::Handle<JS::Value> aError, ErrorResult& aRv) {
// Step 1. Let readIntoRequests be reader.[[readIntoRequests]].
LinkedList<RefPtr<ReadIntoRequest>> readIntoRequests =
std::move(aReader->ReadIntoRequests());
// Step 2. Set reader.[[readIntoRequests]] to a new empty list.
// Note: The std::move already cleared this anyway.
aReader->ReadIntoRequests().clear();
// Step 3. For each readIntoRequest of readIntoRequests,
while (RefPtr<ReadIntoRequest> readIntoRequest =
readIntoRequests.popFirst()) {
// Step 3.1. Perform readIntoRequest’s error steps, given e.
readIntoRequest->ErrorSteps(aCx, aError, aRv);
if (aRv.Failed()) {
return;
}
}
}
void ReadableStreamBYOBReaderRelease(JSContext* aCx,
ReadableStreamBYOBReader* aReader,
ErrorResult& aRv) {
// Step 1. Perform ! ReadableStreamReaderGenericRelease(reader).
ReadableStreamReaderGenericRelease(aReader, aRv);
if (aRv.Failed()) {
return;
}
// Step 2. Let e be a new TypeError exception.
ErrorResult rv;
rv.ThrowTypeError("Releasing lock");
JS::Rooted<JS::Value> error(aCx);
MOZ_ALWAYS_TRUE(ToJSValue(aCx, std::move(rv), &error));
// Step 3. Perform ! ReadableStreamBYOBReaderErrorReadIntoRequests(reader, e).
ReadableStreamBYOBReaderErrorReadIntoRequests(aCx, aReader, error, aRv);
}
} // namespace streams_abstract
void ReadableStreamBYOBReader::ReleaseLock(ErrorResult& aRv) {
// Step 1. If this.[[stream]] is undefined, return.
if (!mStream) {
return;
}
AutoJSAPI jsapi;
if (!jsapi.Init(mGlobal)) {
return aRv.ThrowUnknownError("Internal error");
}
JSContext* cx = jsapi.cx();
// Step 2. Perform ! ReadableStreamBYOBReaderRelease(this).
RefPtr<ReadableStreamBYOBReader> thisRefPtr = this;
ReadableStreamBYOBReaderRelease(cx, thisRefPtr, aRv);
}
namespace streams_abstract {
already_AddRefed<ReadableStreamBYOBReader> AcquireReadableStreamBYOBReader(
ReadableStream* aStream, ErrorResult& aRv) {
// Step 1. Let reader be a new ReadableStreamBYOBReader.
RefPtr<ReadableStreamBYOBReader> reader =
new ReadableStreamBYOBReader(aStream->GetParentObject());
// Step 2. Perform ? SetUpReadableStreamBYOBReader(reader, stream).
SetUpReadableStreamBYOBReader(reader, *aStream, aRv);
if (aRv.Failed()) {
return nullptr;
}
// Step 3. Return reader.
return reader.forget();
}
} // namespace streams_abstract
} // namespace mozilla::dom