mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-10-31 16:28:05 +02:00 
			
		
		
		
	 a140dbde6f
			
		
	
	
		a140dbde6f
		
	
	
	
	
		
			
			Implements https://github.com/whatwg/streams/pull/1145 Differential Revision: https://phabricator.services.mozilla.com/D226225
		
			
				
	
	
		
			392 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			392 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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);
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#set-up-readable-stream-byob-reader
 | ||
| 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();
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#byob-reader-constructor
 | ||
| /* 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());
 | ||
|     // https://streams.spec.whatwg.org/#byob-reader-read Step 6.
 | ||
|     //
 | ||
|     // 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());
 | ||
|     // https://streams.spec.whatwg.org/#byob-reader-read Step 6.
 | ||
|     //
 | ||
|     // 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 {
 | ||
|     // https://streams.spec.whatwg.org/#byob-reader-read Step 6.
 | ||
|     //
 | ||
|     // 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 {
 | ||
| // https://streams.spec.whatwg.org/#readable-stream-byob-reader-read
 | ||
| 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
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#byob-reader-read
 | ||
| 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 {
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#abstract-opdef-readablestreambyobreadererrorreadintorequests
 | ||
| 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;
 | ||
|     }
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#abstract-opdef-readablestreambyobreaderrelease
 | ||
| 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
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#byob-reader-release-lock
 | ||
| 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 {
 | ||
| // https://streams.spec.whatwg.org/#acquire-readable-stream-byob-reader
 | ||
| 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
 |