/* -*- 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)