diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/jsapi-tests/testReadableStream.cpp | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jsapi-tests/testReadableStream.cpp')
-rw-r--r-- | js/src/jsapi-tests/testReadableStream.cpp | 1187 |
1 files changed, 1187 insertions, 0 deletions
diff --git a/js/src/jsapi-tests/testReadableStream.cpp b/js/src/jsapi-tests/testReadableStream.cpp new file mode 100644 index 0000000000..1aa5926c34 --- /dev/null +++ b/js/src/jsapi-tests/testReadableStream.cpp @@ -0,0 +1,1187 @@ +/* -*- 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/. */ + +#include "jsapi.h" +#include "jsfriendapi.h" +#include "js/experimental/TypedData.h" // JS_GetArrayBufferViewData, JS_IsUint8Array +#include "js/Stream.h" +#include "jsapi-tests/tests.h" + +using namespace JS; + +char testBufferData[] = + "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +struct StubExternalUnderlyingSource + : public JS::ReadableStreamUnderlyingSource { + void* buffer = testBufferData; + bool dataRequestCBCalled = false; + bool writeIntoRequestBufferCBCalled = false; + bool cancelStreamCBCalled = false; + Value cancelStreamReason; + bool streamClosedCBCalled = false; + Value streamClosedReason; + bool streamErroredCBCalled = false; + Value streamErroredReason; + bool finalizeStreamCBCalled = false; + void* finalizedStreamUnderlyingSource; + + static StubExternalUnderlyingSource instance; + + void requestData(JSContext* cx, HandleObject stream, + size_t desiredSize) override { + js::AssertSameCompartment(cx, stream); + MOZ_RELEASE_ASSERT(!dataRequestCBCalled, "Invalid test setup"); + dataRequestCBCalled = true; + } + + void writeIntoReadRequestBuffer(JSContext* cx, HandleObject stream, + void* buffer, size_t length, + size_t* bytesWritten) override { + js::AssertSameCompartment(cx, stream); + MOZ_RELEASE_ASSERT(!writeIntoRequestBufferCBCalled, "Invalid test setup"); + writeIntoRequestBufferCBCalled = true; + + MOZ_RELEASE_ASSERT(this == &StubExternalUnderlyingSource::instance); + MOZ_RELEASE_ASSERT(StubExternalUnderlyingSource::instance.buffer == + testBufferData); + MOZ_RELEASE_ASSERT(length <= sizeof(testBufferData)); + memcpy(buffer, testBufferData, length); + *bytesWritten = length; + } + + Value cancel(JSContext* cx, HandleObject stream, + HandleValue reason) override { + js::AssertSameCompartment(cx, stream); + js::AssertSameCompartment(cx, reason); + MOZ_RELEASE_ASSERT(!cancelStreamCBCalled, "Invalid test setup"); + cancelStreamCBCalled = true; + cancelStreamReason = reason; + return reason; + } + + void onClosed(JSContext* cx, HandleObject stream) override { + js::AssertSameCompartment(cx, stream); + MOZ_RELEASE_ASSERT(!streamClosedCBCalled, "Invalid test setup"); + streamClosedCBCalled = true; + } + + void onErrored(JSContext* cx, HandleObject stream, + HandleValue reason) override { + js::AssertSameCompartment(cx, stream); + js::AssertSameCompartment(cx, reason); + MOZ_RELEASE_ASSERT(!streamErroredCBCalled, "Invalid test setup"); + streamErroredCBCalled = true; + streamErroredReason = reason; + } + + void finalize() override { + MOZ_RELEASE_ASSERT(!finalizeStreamCBCalled, "Invalid test setup"); + finalizeStreamCBCalled = true; + finalizedStreamUnderlyingSource = this; + } + + void reset() { + dataRequestCBCalled = false; + writeIntoRequestBufferCBCalled = false; + cancelStreamReason = UndefinedValue(); + cancelStreamCBCalled = false; + streamClosedCBCalled = false; + streamErroredCBCalled = false; + finalizeStreamCBCalled = false; + } +}; + +StubExternalUnderlyingSource StubExternalUnderlyingSource::instance; + +static_assert(MOZ_ALIGNOF(StubExternalUnderlyingSource) > 1, + "UnderlyingSource pointers must not have the low bit set"); + +static JSObject* NewDefaultStream(JSContext* cx, HandleObject source = nullptr, + HandleFunction size = nullptr, + double highWaterMark = 1, + HandleObject proto = nullptr) { + RootedObject stream(cx, NewReadableDefaultStreamObject(cx, source, size, + highWaterMark, proto)); + if (stream) { + MOZ_RELEASE_ASSERT(IsReadableStream(stream)); + } + return stream; +} + +static bool GetIterResult(JSContext* cx, HandleObject promise, + MutableHandleValue value, bool* done) { + RootedObject iterResult(cx, &GetPromiseResult(promise).toObject()); + + bool found; + if (!JS_HasProperty(cx, iterResult, "value", &found)) { + return false; + } + MOZ_RELEASE_ASSERT(found); + if (!JS_HasProperty(cx, iterResult, "done", &found)) { + return false; + } + MOZ_RELEASE_ASSERT(found); + + RootedValue doneVal(cx); + if (!JS_GetProperty(cx, iterResult, "value", value)) { + return false; + } + if (!JS_GetProperty(cx, iterResult, "done", &doneVal)) { + return false; + } + + *done = doneVal.toBoolean(); + if (*done) { + MOZ_RELEASE_ASSERT(value.isUndefined()); + } + + return true; +} + +static JSObject* GetReadChunk(JSContext* cx, HandleObject readRequest) { + MOZ_RELEASE_ASSERT(GetPromiseState(readRequest) == PromiseState::Fulfilled); + RootedValue resultVal(cx, GetPromiseResult(readRequest)); + MOZ_RELEASE_ASSERT(resultVal.isObject()); + RootedObject result(cx, &resultVal.toObject()); + RootedValue chunkVal(cx); + JS_GetProperty(cx, result, "value", &chunkVal); + return &chunkVal.toObject(); +} + +struct StreamTestFixture : public JSAPITest { + virtual ~StreamTestFixture() {} +}; + +BEGIN_FIXTURE_TEST(StreamTestFixture, testReadableStream_NewReadableStream) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + ReadableStreamMode mode; + CHECK(ReadableStreamGetMode(cx, stream, &mode)); + CHECK(mode == ReadableStreamMode::Default); + return true; +} +END_FIXTURE_TEST(StreamTestFixture, testReadableStream_NewReadableStream) + +BEGIN_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamGetReaderDefault) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + + RootedObject reader(cx, ReadableStreamGetReader( + cx, stream, ReadableStreamReaderMode::Default)); + CHECK(reader); + CHECK(IsReadableStreamDefaultReader(reader)); + bool locked; + CHECK(ReadableStreamIsLocked(cx, stream, &locked)); + CHECK(locked); + bool closed; + CHECK(ReadableStreamReaderIsClosed(cx, reader, &closed)); + CHECK(!closed); + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamGetReaderDefault) + +BEGIN_FIXTURE_TEST(StreamTestFixture, testReadableStream_ReadableStreamTee) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + + RootedObject leftStream(cx); + RootedObject rightStream(cx); + CHECK(ReadableStreamTee(cx, stream, &leftStream, &rightStream)); + bool locked; + CHECK(ReadableStreamIsLocked(cx, stream, &locked)); + CHECK(locked); + CHECK(leftStream); + CHECK(IsReadableStream(leftStream)); + CHECK(rightStream); + CHECK(IsReadableStream(rightStream)); + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, testReadableStream_ReadableStreamTee) + +BEGIN_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamEnqueue) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + + RootedObject chunk(cx, JS_NewPlainObject(cx)); + CHECK(chunk); + RootedValue chunkVal(cx, ObjectValue(*chunk)); + CHECK(ReadableStreamEnqueue(cx, stream, chunkVal)); + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, testReadableStream_ReadableStreamEnqueue) + +BEGIN_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamDefaultReaderRead) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + RootedObject reader(cx, ReadableStreamGetReader( + cx, stream, ReadableStreamReaderMode::Default)); + CHECK(reader); + + RootedObject request(cx, ReadableStreamDefaultReaderRead(cx, reader)); + CHECK(request); + CHECK(IsPromiseObject(request)); + CHECK(GetPromiseState(request) == PromiseState::Pending); + + RootedObject chunk(cx, JS_NewPlainObject(cx)); + CHECK(chunk); + RootedValue chunkVal(cx, ObjectValue(*chunk)); + CHECK(ReadableStreamEnqueue(cx, stream, chunkVal)); + + CHECK(GetReadChunk(cx, request) == chunk); + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamDefaultReaderRead) + +BEGIN_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamDefaultReaderClose) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + RootedObject reader(cx, ReadableStreamGetReader( + cx, stream, ReadableStreamReaderMode::Default)); + CHECK(reader); + + RootedObject request(cx, ReadableStreamDefaultReaderRead(cx, reader)); + CHECK(request); + CHECK(IsPromiseObject(request)); + CHECK(GetPromiseState(request) == PromiseState::Pending); + + CHECK(ReadableStreamClose(cx, stream)); + + bool done; + RootedValue value(cx); + CHECK(GetPromiseState(request) == PromiseState::Fulfilled); + CHECK(GetIterResult(cx, request, &value, &done)); + CHECK(value.isUndefined()); + CHECK(done); + + // The callbacks are only invoked for external streams. + CHECK(!StubExternalUnderlyingSource::instance.streamClosedCBCalled); + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamDefaultReaderClose) + +BEGIN_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamDefaultReaderError) { + StubExternalUnderlyingSource::instance.reset(); + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + RootedObject reader(cx, ReadableStreamGetReader( + cx, stream, ReadableStreamReaderMode::Default)); + CHECK(reader); + + RootedObject request(cx, ReadableStreamDefaultReaderRead(cx, reader)); + CHECK(request); + CHECK(IsPromiseObject(request)); + CHECK(GetPromiseState(request) == PromiseState::Pending); + + bool locked; + CHECK(ReadableStreamIsLocked(cx, stream, &locked)); + CHECK(locked); + bool readable; + CHECK(ReadableStreamIsReadable(cx, stream, &readable)); + CHECK(readable); + RootedValue error(cx, Int32Value(42)); + CHECK(ReadableStreamError(cx, stream, error)); + + CHECK(GetPromiseState(request) == PromiseState::Rejected); + RootedValue reason(cx, GetPromiseResult(request)); + CHECK(reason.isInt32()); + CHECK(reason.toInt32() == 42); + + // The callbacks are only invoked for external streams. + CHECK(!StubExternalUnderlyingSource::instance.streamErroredCBCalled); + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamDefaultReaderError) + +static JSObject* NewExternalSourceStream( + JSContext* cx, ReadableStreamUnderlyingSource* source) { + RootedObject stream(cx, NewReadableExternalSourceStreamObject(cx, source)); + if (stream) { + MOZ_RELEASE_ASSERT(IsReadableStream(stream)); + } + return stream; +} + +static JSObject* NewExternalSourceStream(JSContext* cx) { + return NewExternalSourceStream(cx, &StubExternalUnderlyingSource::instance); +} + +BEGIN_FIXTURE_TEST( + StreamTestFixture, + testReadableStream_CreateReadableByteStreamWithExternalSource) { + StubExternalUnderlyingSource::instance.reset(); + + RootedObject stream(cx, NewExternalSourceStream(cx)); + CHECK(stream); + ReadableStreamMode mode; + CHECK(ReadableStreamGetMode(cx, stream, &mode)); + CHECK(mode == ReadableStreamMode::ExternalSource); + ReadableStreamUnderlyingSource* underlyingSource; + CHECK( + ReadableStreamGetExternalUnderlyingSource(cx, stream, &underlyingSource)); + CHECK(underlyingSource == &StubExternalUnderlyingSource::instance); + bool locked; + CHECK(ReadableStreamIsLocked(cx, stream, &locked)); + CHECK(locked); + CHECK(ReadableStreamReleaseExternalUnderlyingSource(cx, stream)); + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, + testReadableStream_CreateReadableByteStreamWithExternalSource) + +BEGIN_FIXTURE_TEST(StreamTestFixture, testReadableStream_ExternalSourceCancel) { + StubExternalUnderlyingSource::instance.reset(); + + RootedObject stream(cx, NewExternalSourceStream(cx)); + CHECK(stream); + RootedValue reason(cx, Int32Value(42)); + CHECK(ReadableStreamCancel(cx, stream, reason)); + CHECK(StubExternalUnderlyingSource::instance.cancelStreamCBCalled); + CHECK(StubExternalUnderlyingSource::instance.cancelStreamReason == reason); + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, testReadableStream_ExternalSourceCancel) + +BEGIN_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ExternalSourceGetReader) { + StubExternalUnderlyingSource::instance.reset(); + + RootedObject stream(cx, NewExternalSourceStream(cx)); + CHECK(stream); + + RootedValue streamVal(cx, ObjectValue(*stream)); + CHECK(JS_SetProperty(cx, global, "stream", streamVal)); + RootedValue rval(cx); + EVAL("stream.getReader()", &rval); + CHECK(rval.isObject()); + RootedObject reader(cx, &rval.toObject()); + CHECK(IsReadableStreamDefaultReader(reader)); + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, testReadableStream_ExternalSourceGetReader) + +enum class CompartmentMode { + Same, + Cross, +}; + +struct ReadFromExternalSourceFixture : public StreamTestFixture { + virtual ~ReadFromExternalSourceFixture() {} + + // On success, streamGlobal is a global object (not a wrapper) + // and stream is in the same compartment as cx (it may be a CCW). + bool createExternalSourceStream(CompartmentMode compartmentMode, + MutableHandleObject streamGlobal, + MutableHandleObject stream) { + if (compartmentMode == CompartmentMode::Same) { + streamGlobal.set(global); + stream.set(NewExternalSourceStream(cx)); + if (!stream) { + return false; + } + } else { + RootedObject savedGlobal(cx, global); + streamGlobal.set(createGlobal()); + if (!streamGlobal) { + return false; + } + global = savedGlobal; + + { + JSAutoRealm ar(cx, streamGlobal); + stream.set(NewExternalSourceStream(cx)); + if (!stream) { + return false; + } + } + if (!JS_WrapObject(cx, stream)) { + return false; + } + } + return true; + } + + bool readWithoutDataAvailable(CompartmentMode compartmentMode, + const char* evalSrc, const char* evalSrc2, + uint32_t writtenLength) { + StubExternalUnderlyingSource::instance.reset(); + definePrint(); + + // Create the stream. + RootedObject streamGlobal(cx); + RootedObject stream(cx); // can be a wrapper + CHECK(createExternalSourceStream(compartmentMode, &streamGlobal, &stream)); + js::RunJobs(cx); + + // GetExternalUnderlyingSource locks the stream. + ReadableStreamUnderlyingSource* underlyingSource; + CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, + &underlyingSource)); + CHECK(underlyingSource == &StubExternalUnderlyingSource::instance); + bool locked; + CHECK(ReadableStreamIsLocked(cx, stream, &locked)); + CHECK(locked); + CHECK(ReadableStreamReleaseExternalUnderlyingSource(cx, stream)); + + // Run caller-supplied JS code to read from the stream. + RootedValue streamVal(cx, ObjectValue(*stream)); + CHECK(JS_SetProperty(cx, global, "stream", streamVal)); + RootedValue rval(cx); + EVAL(evalSrc, &rval); + CHECK(StubExternalUnderlyingSource::instance.dataRequestCBCalled); + CHECK( + !StubExternalUnderlyingSource::instance.writeIntoRequestBufferCBCalled); + CHECK(rval.isObject()); + RootedObject unwrappedPromise(cx, + js::CheckedUnwrapStatic(&rval.toObject())); + CHECK(unwrappedPromise); + CHECK(IsPromiseObject(unwrappedPromise)); + CHECK(GetPromiseState(unwrappedPromise) == PromiseState::Pending); + + // Stream in some data; this resolves the read() result promise. + size_t length = sizeof(testBufferData); + CHECK(ReadableStreamUpdateDataAvailableFromSource(cx, stream, length)); + CHECK( + StubExternalUnderlyingSource::instance.writeIntoRequestBufferCBCalled); + CHECK(GetPromiseState(unwrappedPromise) == PromiseState::Fulfilled); + RootedObject chunk(cx); + { + JSAutoRealm ar(cx, unwrappedPromise); + RootedValue iterVal(cx); + bool done; + if (!GetIterResult(cx, unwrappedPromise, &iterVal, &done)) { + return false; + } + CHECK(!done); + chunk = &iterVal.toObject(); + } + CHECK(JS_WrapObject(cx, &chunk)); + CHECK(JS_IsUint8Array(chunk)); + + { + JS::AutoCheckCannotGC noGC(cx); + bool dummy; + void* buffer = JS_GetArrayBufferViewData(chunk, &dummy, noGC); + CHECK(!memcmp(buffer, testBufferData, writtenLength)); + } + + // Check the callbacks fired by calling read() again. + StubExternalUnderlyingSource::instance.dataRequestCBCalled = false; + StubExternalUnderlyingSource::instance.writeIntoRequestBufferCBCalled = + false; + EVAL(evalSrc2, &rval); + CHECK(StubExternalUnderlyingSource::instance.dataRequestCBCalled); + CHECK( + !StubExternalUnderlyingSource::instance.writeIntoRequestBufferCBCalled); + + return true; + } + + bool readWithDataAvailable(CompartmentMode compartmentMode, + const char* evalSrc, uint32_t writtenLength) { + StubExternalUnderlyingSource::instance.reset(); + definePrint(); + + // Create a stream. + RootedObject streamGlobal(cx); + RootedObject stream(cx); + CHECK(createExternalSourceStream(compartmentMode, &streamGlobal, &stream)); + + // Getting the underlying source locks the stream. + ReadableStreamUnderlyingSource* underlyingSource; + CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, + &underlyingSource)); + CHECK(underlyingSource == &StubExternalUnderlyingSource::instance); + bool locked; + CHECK(ReadableStreamIsLocked(cx, stream, &locked)); + CHECK(locked); + CHECK(ReadableStreamReleaseExternalUnderlyingSource(cx, stream)); + + // Make some data available. + size_t length = sizeof(testBufferData); + CHECK(ReadableStreamUpdateDataAvailableFromSource(cx, stream, length)); + + // Read from the stream. + RootedValue streamVal(cx, ObjectValue(*stream)); + CHECK(JS_SetProperty(cx, global, "stream", streamVal)); + RootedValue rval(cx); + EVAL(evalSrc, &rval); + CHECK( + StubExternalUnderlyingSource::instance.writeIntoRequestBufferCBCalled); + CHECK(rval.isObject()); + RootedObject unwrappedPromise(cx, + js::CheckedUnwrapStatic(&rval.toObject())); + CHECK(unwrappedPromise); + CHECK(IsPromiseObject(unwrappedPromise)); + CHECK(GetPromiseState(unwrappedPromise) == PromiseState::Fulfilled); + RootedObject chunk(cx); + { + JSAutoRealm ar(cx, unwrappedPromise); + RootedValue iterVal(cx); + bool done; + if (!GetIterResult(cx, unwrappedPromise, &iterVal, &done)) { + return false; + } + CHECK(!done); + chunk = &iterVal.toObject(); + } + CHECK(JS_WrapObject(cx, &chunk)); + CHECK(JS_IsUint8Array(chunk)); + + { + JS::AutoCheckCannotGC noGC(cx); + bool dummy; + void* buffer = JS_GetArrayBufferViewData(chunk, &dummy, noGC); + CHECK(!memcmp(buffer, testBufferData, writtenLength)); + } + + return true; + } +}; + +BEGIN_FIXTURE_TEST( + ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadDefaultWithoutDataAvailable) { + return readWithoutDataAvailable(CompartmentMode::Same, + "r = stream.getReader(); r.read()", + "r.read()", sizeof(testBufferData)); +} +END_FIXTURE_TEST( + ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadDefaultWithoutDataAvailable) + +BEGIN_FIXTURE_TEST( + ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadDefaultWithoutDataAvailable_CrossCompartment1) { + // Scenario 1: The stream and reader are both in the same compartment, but + // ReadableStreamUpdateDataAvailableFromSource is applied to a wrapper. + return readWithoutDataAvailable(CompartmentMode::Cross, + "r = stream.getReader(); r.read()", + "r.read()", sizeof(testBufferData)); +} +END_FIXTURE_TEST( + ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadDefaultWithoutDataAvailable_CrossCompartment1) + +BEGIN_FIXTURE_TEST( + ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadDefaultWithoutDataAvailable_CrossCompartment2) { + // Scenario 2: The stream and reader are in the same compartment, but a + // `read` method from another compartment is used on the reader. + return readWithoutDataAvailable( + CompartmentMode::Cross, + "r = stream.getReader(); read = new " + "ReadableStream({start(){}}).getReader().read; read.call(r)", + "read.call(r)", sizeof(testBufferData)); +} +END_FIXTURE_TEST( + ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadDefaultWithoutDataAvailable_CrossCompartment2) + +BEGIN_FIXTURE_TEST( + ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadDefaultWithoutDataAvailable_CrossCompartment3) { + // Scenario 3: The stream and reader are in different compartments. + return readWithoutDataAvailable( + CompartmentMode::Cross, + "r = ReadableStream.prototype.getReader.call(stream); r.read()", + "r.read()", sizeof(testBufferData)); +} +END_FIXTURE_TEST( + ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadDefaultWithoutDataAvailable_CrossCompartment3) + +BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceCloseWithPendingRead) { + CHECK(readWithoutDataAvailable(CompartmentMode::Same, + "r = stream.getReader(); request0 = r.read(); " + "request1 = r.read(); request0", + "r.read()", sizeof(testBufferData))); + + RootedValue val(cx); + CHECK(JS_GetProperty(cx, global, "request1", &val)); + CHECK(val.isObject()); + RootedObject request(cx, &val.toObject()); + CHECK(IsPromiseObject(request)); + CHECK(GetPromiseState(request) == PromiseState::Pending); + + CHECK(JS_GetProperty(cx, global, "stream", &val)); + RootedObject stream(cx, &val.toObject()); + ReadableStreamClose(cx, stream); + + val = GetPromiseResult(request); + CHECK(val.isObject()); + RootedObject result(cx, &val.toObject()); + + JS_GetProperty(cx, result, "done", &val); + CHECK(val.isBoolean()); + CHECK(val.toBoolean() == true); + + JS_GetProperty(cx, result, "value", &val); + CHECK(val.isUndefined()); + return true; +} +END_FIXTURE_TEST(ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceCloseWithPendingRead) + +BEGIN_FIXTURE_TEST( + ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadDefaultWithDataAvailable) { + return readWithDataAvailable(CompartmentMode::Same, + "r = stream.getReader(); r.read()", + sizeof(testBufferData)); +} +END_FIXTURE_TEST(ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadDefaultWithDataAvailable) + +BEGIN_FIXTURE_TEST( + ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadDefaultWithDataAvailable_CrossCompartment1) { + // Scenario 1: The stream and reader are both in the same compartment, but + // ReadableStreamUpdateDataAvailableFromSource is applied to a wrapper. + return readWithDataAvailable(CompartmentMode::Cross, + "r = stream.getReader(); r.read()", + sizeof(testBufferData)); +} +END_FIXTURE_TEST( + ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadDefaultWithDataAvailable_CrossCompartment1) + +BEGIN_FIXTURE_TEST( + ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadDefaultWithDataAvailable_CrossCompartment2) { + // Scenario 2: The stream and reader are in the same compartment, but a + // `read` method from another compartment is used on the reader. + return readWithDataAvailable( + CompartmentMode::Cross, + "r = stream.getReader(); read = new " + "ReadableStream({start(){}}).getReader().read; read.call(r)", + sizeof(testBufferData)); +} +END_FIXTURE_TEST( + ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadDefaultWithDataAvailable_CrossCompartment2) + +BEGIN_FIXTURE_TEST( + ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadDefaultWithDataAvailable_CrossCompartment3) { + // Scenario 3: The stream and reader are in different compartments. + return readWithDataAvailable( + CompartmentMode::Cross, + "r = ReadableStream.prototype.getReader.call(stream); r.read()", + sizeof(testBufferData)); +} +END_FIXTURE_TEST( + ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadDefaultWithDataAvailable_CrossCompartment3) + +// Cross-global tests: +BEGIN_FIXTURE_TEST( + StreamTestFixture, + testReadableStream_ReadableStreamOtherGlobalDefaultReaderRead) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &stream)); + RootedObject reader(cx, ReadableStreamGetReader( + cx, stream, ReadableStreamReaderMode::Default)); + CHECK(reader); + + RootedObject request(cx, ReadableStreamDefaultReaderRead(cx, reader)); + CHECK(request); + CHECK(IsPromiseObject(request)); + CHECK(!js::IsWrapper(request)); + CHECK(GetPromiseState(request) == PromiseState::Pending); + + RootedObject chunk(cx, JS_NewPlainObject(cx)); + CHECK(chunk); + RootedValue chunkVal(cx, ObjectValue(*chunk)); + CHECK(ReadableStreamEnqueue(cx, stream, chunkVal)); + + CHECK(GetReadChunk(cx, request) == chunk); + } + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamOtherGlobalDefaultReaderRead) + +BEGIN_FIXTURE_TEST( + StreamTestFixture, + testReadableStream_ReadableStreamGetExternalUnderlyingSource) { + StubExternalUnderlyingSource::instance.reset(); + + RootedObject stream(cx, NewExternalSourceStream(cx)); + CHECK(stream); + ReadableStreamUnderlyingSource* source; + CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, &source)); + CHECK(source == &StubExternalUnderlyingSource::instance); + CHECK(ReadableStreamReleaseExternalUnderlyingSource(cx, stream)); + + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &stream)); + ReadableStreamUnderlyingSource* source; + CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, &source)); + CHECK(source == &StubExternalUnderlyingSource::instance); + CHECK(ReadableStreamReleaseExternalUnderlyingSource(cx, stream)); + } + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamGetExternalUnderlyingSource) + +BEGIN_FIXTURE_TEST( + StreamTestFixture, + testReadableStream_ReadableStreamUpdateDataAvailableFromSource) { + RootedObject stream(cx, NewExternalSourceStream(cx)); + CHECK(stream); + CHECK(ReadableStreamUpdateDataAvailableFromSource(cx, stream, 0)); + + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &stream)); + CHECK(ReadableStreamUpdateDataAvailableFromSource(cx, stream, 1)); + } + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamUpdateDataAvailableFromSource) + +BEGIN_FIXTURE_TEST(StreamTestFixture, testReadableStream_IsReadableStream) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + CHECK(IsReadableStream(stream)); + + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &stream)); + CHECK(IsReadableStream(stream)); + } + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, testReadableStream_IsReadableStream) + +BEGIN_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamGetMode) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + ReadableStreamMode mode; + CHECK(ReadableStreamGetMode(cx, stream, &mode)); + CHECK(mode == ReadableStreamMode::Default); + + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &stream)); + CHECK(ReadableStreamGetMode(cx, stream, &mode)); + CHECK(mode == ReadableStreamMode::Default); + } + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, testReadableStream_ReadableStreamGetMode) + +BEGIN_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamIsReadable) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + bool result; + CHECK(ReadableStreamIsReadable(cx, stream, &result)); + CHECK(result); + + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &stream)); + CHECK(ReadableStreamIsReadable(cx, stream, &result)); + CHECK(result); + } + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, testReadableStream_ReadableStreamIsReadable) + +BEGIN_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamIsLocked) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + bool result; + CHECK(ReadableStreamIsLocked(cx, stream, &result)); + CHECK_EQUAL(result, false); + + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &stream)); + CHECK(ReadableStreamIsLocked(cx, stream, &result)); + CHECK_EQUAL(result, false); + } + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, testReadableStream_ReadableStreamIsLocked) + +BEGIN_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamIsDisturbed) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + bool result; + CHECK(ReadableStreamIsDisturbed(cx, stream, &result)); + CHECK_EQUAL(result, false); + + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &stream)); + CHECK(ReadableStreamIsDisturbed(cx, stream, &result)); + CHECK_EQUAL(result, false); + } + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamIsDisturbed) + +BEGIN_FIXTURE_TEST(StreamTestFixture, testReadableStream_ReadableStreamCancel) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + + RootedValue reason(cx); + JSObject* callResult = ReadableStreamCancel(cx, stream, reason); + CHECK(callResult); + + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &stream)); + RootedValue reason(cx); + JSObject* callResult = ReadableStreamCancel(cx, stream, reason); + CHECK(callResult); + } + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, testReadableStream_ReadableStreamCancel) + +BEGIN_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamGetReader) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + + RootedObject reader(cx); + reader = + ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default); + CHECK(reader); + CHECK(IsReadableStreamDefaultReader(reader)); + CHECK(ReadableStreamReaderReleaseLock(cx, reader)); + + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &stream)); + JSObject* callResult = + ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default); + CHECK(callResult); + } + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, testReadableStream_ReadableStreamGetReader) + +BEGIN_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamTee_CrossCompartment) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + + RootedObject branch1Stream(cx); + RootedObject branch2Stream(cx); + CHECK(ReadableStreamTee(cx, stream, &branch1Stream, &branch2Stream)); + CHECK(IsReadableStream(branch1Stream)); + CHECK(IsReadableStream(branch2Stream)); + stream = branch1Stream; + + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &stream)); + CHECK(ReadableStreamTee(cx, stream, &branch1Stream, &branch2Stream)); + CHECK(IsReadableStream(branch1Stream)); + CHECK(IsReadableStream(branch2Stream)); + } + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamTee_CrossCompartment) + +BEGIN_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamGetDesiredSize) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + bool hasValue; + double value; + CHECK(ReadableStreamGetDesiredSize(cx, stream, &hasValue, &value)); + CHECK_EQUAL(hasValue, true); + CHECK_EQUAL(value, 1.0); + + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &stream)); + hasValue = false; + value = 0; + CHECK(ReadableStreamGetDesiredSize(cx, stream, &hasValue, &value)); + CHECK_EQUAL(hasValue, true); + CHECK_EQUAL(value, 1.0); + } + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamGetDesiredSize) + +BEGIN_FIXTURE_TEST(StreamTestFixture, testReadableStream_ReadableStreamClose) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + CHECK(ReadableStreamClose(cx, stream)); + + stream = NewDefaultStream(cx); + CHECK(stream); + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &stream)); + CHECK(ReadableStreamClose(cx, stream)); + } + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, testReadableStream_ReadableStreamClose) + +BEGIN_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamEnqueue_CrossCompartment) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + RootedValue chunk(cx); + CHECK(ReadableStreamEnqueue(cx, stream, chunk)); + + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &stream)); + RootedValue chunk(cx); + CHECK(ReadableStreamEnqueue(cx, stream, chunk)); + } + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamEnqueue_CrossCompartment) + +BEGIN_FIXTURE_TEST(StreamTestFixture, testReadableStream_ReadableStreamError) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + RootedValue error(cx); + CHECK(ReadableStreamError(cx, stream, error)); + + stream = NewDefaultStream(cx); + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &stream)); + RootedValue error(cx); + CHECK(ReadableStreamError(cx, stream, error)); + } + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, testReadableStream_ReadableStreamError) + +BEGIN_FIXTURE_TEST(StreamTestFixture, + testReadableStream_IsReadableStreamReader) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + RootedObject reader(cx, ReadableStreamGetReader( + cx, stream, ReadableStreamReaderMode::Default)); + CHECK(reader); + CHECK(IsReadableStreamReader(reader)); + + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &reader)); + CHECK(IsReadableStreamReader(reader)); + } + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, testReadableStream_IsReadableStreamReader) + +BEGIN_FIXTURE_TEST(StreamTestFixture, + testReadableStream_IsReadableStreamDefaultReader) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + RootedObject reader(cx, ReadableStreamGetReader( + cx, stream, ReadableStreamReaderMode::Default)); + CHECK(IsReadableStreamDefaultReader(reader)); + + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &reader)); + CHECK(IsReadableStreamDefaultReader(reader)); + } + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, + testReadableStream_IsReadableStreamDefaultReader) + +BEGIN_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamReaderIsClosed) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + + RootedObject reader(cx, ReadableStreamGetReader( + cx, stream, ReadableStreamReaderMode::Default)); + bool result; + CHECK(ReadableStreamReaderIsClosed(cx, reader, &result)); + CHECK_EQUAL(result, false); + + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &reader)); + bool result; + CHECK(ReadableStreamReaderIsClosed(cx, reader, &result)); + } + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamReaderIsClosed) + +BEGIN_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamReaderCancel) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + RootedObject reader(cx, ReadableStreamGetReader( + cx, stream, ReadableStreamReaderMode::Default)); + RootedValue reason(cx); + CHECK(ReadableStreamReaderCancel(cx, reader, reason)); + + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &reader)); + RootedValue reason(cx); + CHECK(ReadableStreamReaderCancel(cx, reader, reason)); + } + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamReaderCancel) + +BEGIN_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamReaderReleaseLock) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + RootedObject reader(cx, ReadableStreamGetReader( + cx, stream, ReadableStreamReaderMode::Default)); + CHECK(reader); + CHECK(ReadableStreamReaderReleaseLock(cx, reader)); + + // Repeat the test cross-compartment. This creates a new reader, since + // releasing the lock above deactivated the first reader. + reader = + ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default); + CHECK(reader); + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &reader)); + CHECK(ReadableStreamReaderReleaseLock(cx, reader)); + } + + return true; +} +END_FIXTURE_TEST(StreamTestFixture, + testReadableStream_ReadableStreamReaderReleaseLock) + +BEGIN_FIXTURE_TEST( + StreamTestFixture, + testReadableStream_ReadableStreamDefaultReaderRead_CrossCompartment) { + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + RootedObject reader(cx, ReadableStreamGetReader( + cx, stream, ReadableStreamReaderMode::Default)); + JSObject* callResult = ReadableStreamDefaultReaderRead(cx, reader); + CHECK(callResult); + + RootedObject otherGlobal(cx, createGlobal()); + CHECK(otherGlobal); + { + JSAutoRealm ar(cx, otherGlobal); + CHECK(JS_WrapObject(cx, &reader)); + JSObject* callResult = ReadableStreamDefaultReaderRead(cx, reader); + CHECK(callResult); + } + + return true; +} +END_FIXTURE_TEST( + StreamTestFixture, + testReadableStream_ReadableStreamDefaultReaderRead_CrossCompartment) |