blob: adf91375067092deea52d402671dfe8032126e74 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/streams/readable_stream.h"
#include "base/optional.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/bindings/core/v8/readable_stream_default_reader_or_readable_stream_byob_reader.h"
#include "third_party/blink/renderer/bindings/core/v8/script_function.h"
#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_extras_test_utils.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_iterator_result_value.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_readable_stream.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_readable_stream_get_reader_options.h"
#include "third_party/blink/renderer/core/messaging/message_channel.h"
#include "third_party/blink/renderer/core/streams/readable_stream_default_controller_with_script_scope.h"
#include "third_party/blink/renderer/core/streams/readable_stream_default_reader.h"
#include "third_party/blink/renderer/core/streams/readable_stream_transferring_optimizer.h"
#include "third_party/blink/renderer/core/streams/test_underlying_source.h"
#include "third_party/blink/renderer/core/streams/underlying_source_base.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/bindings/string_resource.h"
#include "third_party/blink/renderer/platform/heap/handle.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "v8/include/v8.h"
namespace blink {
namespace {
// Web platform tests test ReadableStream more thoroughly from scripts.
class ReadableStreamTest : public testing::Test {
public:
ReadableStreamTest() {}
base::Optional<String> ReadAll(V8TestingScope& scope,
ReadableStream* stream) {
ScriptState* script_state = scope.GetScriptState();
v8::Isolate* isolate = script_state->GetIsolate();
v8::Local<v8::Context> context = script_state->GetContext();
v8::Local<v8::Value> v8_stream = ToV8(stream, context->Global(), isolate);
v8::Local<v8::Object> global = context->Global();
bool set_result = false;
if (!global->Set(context, V8String(isolate, "stream"), v8_stream)
.To(&set_result)) {
ADD_FAILURE();
return base::nullopt;
}
const char script[] =
R"JS(;
result = undefined;
async function readAll(stream) {
const reader = stream.getReader();
let temp = "";
while (true) {
const v = await reader.read();
if (v.done) {
result = temp;
return;
}
temp = temp + v.value;
}
}
readAll(stream);
)JS";
if (EvalWithPrintingError(&scope, script).IsEmpty()) {
ADD_FAILURE();
return base::nullopt;
}
while (true) {
v8::Local<v8::Value> result;
if (!global->Get(context, V8String(isolate, "result")).ToLocal(&result)) {
ADD_FAILURE();
return base::nullopt;
}
if (!result->IsUndefined()) {
DCHECK(result->IsString());
return ToCoreString(result.As<v8::String>());
}
// Need to run the event loop for the Serialize test to pass messages
// through the MessagePort.
test::RunPendingTasks();
// Allow Promises to resolve.
v8::MicrotasksScope::PerformCheckpoint(isolate);
}
NOTREACHED();
return base::nullopt;
}
};
// This breaks expectations for general ReadableStreamTransferringOptimizer
// subclasses, but we don't care.
class TestTransferringOptimizer final
: public ReadableStreamTransferringOptimizer {
USING_FAST_MALLOC(TestTransferringOptimizer);
public:
TestTransferringOptimizer() = default;
UnderlyingSourceBase* PerformInProcessOptimization(
ScriptState* script_state) override {
return MakeGarbageCollected<Source>(script_state);
}
private:
class Source final : public UnderlyingSourceBase {
public:
explicit Source(ScriptState* script_state)
: UnderlyingSourceBase(script_state) {}
ScriptPromise Start(ScriptState* script_state) override {
Controller()->Enqueue("foo");
Controller()->Enqueue(", bar");
Controller()->Close();
return ScriptPromise::CastUndefined(script_state);
}
};
};
TEST_F(ReadableStreamTest, CreateWithoutArguments) {
V8TestingScope scope;
ReadableStream* stream =
ReadableStream::Create(scope.GetScriptState(), scope.GetExceptionState());
ASSERT_TRUE(stream);
ASSERT_FALSE(scope.GetExceptionState().HadException());
}
TEST_F(ReadableStreamTest, CreateWithUnderlyingSourceOnly) {
V8TestingScope scope;
auto* underlying_source =
MakeGarbageCollected<TestUnderlyingSource>(scope.GetScriptState());
ScriptValue js_underlying_source = ScriptValue(
scope.GetIsolate(),
ToV8(underlying_source, scope.GetScriptState()->GetContext()->Global(),
scope.GetIsolate()));
EXPECT_FALSE(underlying_source->IsStartCalled());
ReadableStream* stream = ReadableStream::Create(
scope.GetScriptState(), js_underlying_source, scope.GetExceptionState());
ASSERT_TRUE(stream);
ASSERT_FALSE(scope.GetExceptionState().HadException());
EXPECT_TRUE(underlying_source->IsStartCalled());
}
TEST_F(ReadableStreamTest, CreateWithFullArguments) {
V8TestingScope scope;
auto* underlying_source =
MakeGarbageCollected<TestUnderlyingSource>(scope.GetScriptState());
ScriptValue js_underlying_source = ScriptValue(
scope.GetIsolate(),
ToV8(underlying_source, scope.GetScriptState()->GetContext()->Global(),
scope.GetIsolate()));
ScriptValue js_empty_strategy = EvalWithPrintingError(&scope, "{}");
ASSERT_FALSE(js_empty_strategy.IsEmpty());
ReadableStream* stream =
ReadableStream::Create(scope.GetScriptState(), js_underlying_source,
js_empty_strategy, scope.GetExceptionState());
ASSERT_TRUE(stream);
ASSERT_FALSE(scope.GetExceptionState().HadException());
EXPECT_TRUE(underlying_source->IsStartCalled());
}
TEST_F(ReadableStreamTest, CreateWithPathologicalStrategy) {
V8TestingScope scope;
auto* underlying_source =
MakeGarbageCollected<TestUnderlyingSource>(scope.GetScriptState());
ScriptValue js_underlying_source = ScriptValue(
scope.GetIsolate(),
ToV8(underlying_source, scope.GetScriptState()->GetContext()->Global(),
scope.GetIsolate()));
ScriptValue js_pathological_strategy =
EvalWithPrintingError(&scope, "({get size() { throw Error('e'); }})");
ASSERT_FALSE(js_pathological_strategy.IsEmpty());
ReadableStream* stream = ReadableStream::Create(
scope.GetScriptState(), js_underlying_source, js_pathological_strategy,
scope.GetExceptionState());
ASSERT_FALSE(stream);
ASSERT_TRUE(scope.GetExceptionState().HadException());
EXPECT_FALSE(underlying_source->IsStartCalled());
}
// Testing getReader, locked, IsLocked and IsDisturbed.
TEST_F(ReadableStreamTest, GetReader) {
V8TestingScope scope;
ScriptState* script_state = scope.GetScriptState();
v8::Isolate* isolate = scope.GetIsolate();
auto* underlying_source =
MakeGarbageCollected<TestUnderlyingSource>(script_state);
ScriptValue js_underlying_source = ScriptValue(
isolate,
ToV8(underlying_source, script_state->GetContext()->Global(), isolate));
ReadableStream* stream = ReadableStream::Create(
script_state, js_underlying_source, ASSERT_NO_EXCEPTION);
ASSERT_TRUE(stream);
EXPECT_FALSE(stream->locked());
EXPECT_FALSE(stream->IsLocked());
EXPECT_FALSE(stream->IsDisturbed());
ReadableStreamDefaultReader* reader =
stream->GetDefaultReaderForTesting(script_state, ASSERT_NO_EXCEPTION);
EXPECT_TRUE(stream->locked());
EXPECT_TRUE(stream->IsLocked());
EXPECT_FALSE(stream->IsDisturbed());
reader->read(script_state, ASSERT_NO_EXCEPTION);
EXPECT_TRUE(stream->IsDisturbed());
}
// Testing getReader with mode BYOB.
TEST_F(ReadableStreamTest, GetBYOBReader) {
V8TestingScope scope;
ScriptState* script_state = scope.GetScriptState();
v8::Isolate* isolate = scope.GetIsolate();
ScriptValue byte_stream =
EvalWithPrintingError(&scope, "new ReadableStream({type: 'bytes'})");
ReadableStream* stream{
V8ReadableStream::ToImplWithTypeCheck(isolate, byte_stream.V8Value())};
ASSERT_TRUE(stream);
EXPECT_FALSE(stream->locked());
EXPECT_FALSE(stream->IsLocked());
EXPECT_FALSE(stream->IsDisturbed());
auto* options = ReadableStreamGetReaderOptions::Create();
options->setMode("byob");
ReadableStreamDefaultReaderOrReadableStreamBYOBReader return_value;
stream->getReader(script_state, options, return_value, ASSERT_NO_EXCEPTION);
ReadableStreamBYOBReader* reader =
return_value.GetAsReadableStreamBYOBReader();
ASSERT_TRUE(reader);
EXPECT_TRUE(stream->locked());
EXPECT_TRUE(stream->IsLocked());
EXPECT_FALSE(stream->IsDisturbed());
NotShared<DOMArrayBufferView> view =
NotShared<DOMUint8Array>(DOMUint8Array::Create(1));
reader->read(script_state, view, ASSERT_NO_EXCEPTION);
EXPECT_TRUE(stream->IsDisturbed());
}
TEST_F(ReadableStreamTest, Cancel) {
V8TestingScope scope;
ScriptState* script_state = scope.GetScriptState();
v8::Isolate* isolate = scope.GetIsolate();
auto* underlying_source =
MakeGarbageCollected<TestUnderlyingSource>(script_state);
ScriptValue js_underlying_source = ScriptValue(
isolate,
ToV8(underlying_source, script_state->GetContext()->Global(), isolate));
ReadableStream* stream = ReadableStream::Create(
script_state, js_underlying_source, ASSERT_NO_EXCEPTION);
ASSERT_TRUE(stream);
EXPECT_FALSE(underlying_source->IsCancelled());
EXPECT_FALSE(underlying_source->IsCancelledWithUndefined());
EXPECT_FALSE(underlying_source->IsCancelledWithNull());
stream->cancel(script_state, ASSERT_NO_EXCEPTION);
EXPECT_TRUE(underlying_source->IsCancelled());
EXPECT_TRUE(underlying_source->IsCancelledWithUndefined());
EXPECT_FALSE(underlying_source->IsCancelledWithNull());
}
TEST_F(ReadableStreamTest, CancelWithNull) {
V8TestingScope scope;
ScriptState* script_state = scope.GetScriptState();
v8::Isolate* isolate = scope.GetIsolate();
auto* underlying_source =
MakeGarbageCollected<TestUnderlyingSource>(script_state);
ScriptValue js_underlying_source = ScriptValue(
isolate,
ToV8(underlying_source, script_state->GetContext()->Global(), isolate));
ReadableStream* stream = ReadableStream::Create(
script_state, js_underlying_source, ASSERT_NO_EXCEPTION);
ASSERT_TRUE(stream);
EXPECT_FALSE(underlying_source->IsCancelled());
EXPECT_FALSE(underlying_source->IsCancelledWithUndefined());
EXPECT_FALSE(underlying_source->IsCancelledWithNull());
stream->cancel(script_state, ScriptValue(isolate, v8::Null(isolate)),
ASSERT_NO_EXCEPTION);
EXPECT_TRUE(underlying_source->IsCancelled());
EXPECT_FALSE(underlying_source->IsCancelledWithUndefined());
EXPECT_TRUE(underlying_source->IsCancelledWithNull());
}
// TODO(yhirano): Write tests for pipeThrough and pipeTo.
TEST_F(ReadableStreamTest, Tee) {
V8TestingScope scope;
ScriptState* script_state = scope.GetScriptState();
v8::Isolate* isolate = scope.GetIsolate();
auto* underlying_source =
MakeGarbageCollected<TestUnderlyingSource>(script_state);
ScriptValue js_underlying_source = ScriptValue(
isolate,
ToV8(underlying_source, script_state->GetContext()->Global(), isolate));
ReadableStream* stream = ReadableStream::Create(
script_state, js_underlying_source, ASSERT_NO_EXCEPTION);
ASSERT_TRUE(stream);
underlying_source->Enqueue(ScriptValue(isolate, V8String(isolate, "hello")));
underlying_source->Enqueue(ScriptValue(isolate, V8String(isolate, ", bye")));
underlying_source->Close();
ReadableStream* branch1 = nullptr;
ReadableStream* branch2 = nullptr;
stream->Tee(script_state, &branch1, &branch2, ASSERT_NO_EXCEPTION);
EXPECT_TRUE(stream->IsLocked());
EXPECT_FALSE(stream->IsDisturbed());
ASSERT_TRUE(branch1);
ASSERT_TRUE(branch2);
EXPECT_FALSE(branch1->IsLocked());
EXPECT_FALSE(branch1->IsDisturbed());
EXPECT_FALSE(branch2->IsLocked());
EXPECT_FALSE(branch2->IsDisturbed());
auto result1 = ReadAll(scope, branch1);
ASSERT_TRUE(result1);
EXPECT_EQ(*result1, "hello, bye");
EXPECT_TRUE(stream->IsDisturbed());
auto result2 = ReadAll(scope, branch2);
ASSERT_TRUE(result2);
EXPECT_EQ(*result2, "hello, bye");
}
TEST_F(ReadableStreamTest, Close) {
V8TestingScope scope;
ScriptState* script_state = scope.GetScriptState();
auto* underlying_source =
MakeGarbageCollected<TestUnderlyingSource>(script_state);
auto* stream = ReadableStream::CreateWithCountQueueingStrategy(
script_state, underlying_source, 0);
ASSERT_TRUE(stream);
EXPECT_TRUE(stream->IsReadable());
EXPECT_FALSE(stream->IsClosed());
EXPECT_FALSE(stream->IsErrored());
underlying_source->Close();
EXPECT_FALSE(stream->IsReadable());
EXPECT_TRUE(stream->IsClosed());
EXPECT_FALSE(stream->IsErrored());
}
TEST_F(ReadableStreamTest, Error) {
V8TestingScope scope;
ScriptState* script_state = scope.GetScriptState();
v8::Isolate* isolate = scope.GetIsolate();
auto* underlying_source =
MakeGarbageCollected<TestUnderlyingSource>(script_state);
auto* stream = ReadableStream::CreateWithCountQueueingStrategy(
script_state, underlying_source, 0);
ASSERT_TRUE(stream);
EXPECT_TRUE(stream->IsReadable());
EXPECT_FALSE(stream->IsClosed());
EXPECT_FALSE(stream->IsErrored());
underlying_source->Error(ScriptValue(isolate, v8::Undefined(isolate)));
EXPECT_FALSE(stream->IsReadable());
EXPECT_FALSE(stream->IsClosed());
EXPECT_TRUE(stream->IsErrored());
}
TEST_F(ReadableStreamTest, LockAndDisturb) {
V8TestingScope scope;
ScriptState* script_state = scope.GetScriptState();
auto* underlying_source =
MakeGarbageCollected<TestUnderlyingSource>(script_state);
auto* stream = ReadableStream::CreateWithCountQueueingStrategy(
script_state, underlying_source, 0);
ASSERT_TRUE(stream);
EXPECT_FALSE(stream->IsLocked());
EXPECT_FALSE(stream->IsDisturbed());
stream->LockAndDisturb(script_state);
EXPECT_TRUE(stream->IsLocked());
EXPECT_TRUE(stream->IsDisturbed());
}
TEST_F(ReadableStreamTest, Serialize) {
V8TestingScope scope;
auto* script_state = scope.GetScriptState();
auto* isolate = scope.GetIsolate();
auto* underlying_source =
MakeGarbageCollected<TestUnderlyingSource>(script_state);
auto* stream = ReadableStream::CreateWithCountQueueingStrategy(
script_state, underlying_source, 0);
ASSERT_TRUE(stream);
auto* channel =
MakeGarbageCollected<MessageChannel>(scope.GetExecutionContext());
stream->Serialize(script_state, channel->port1(), ASSERT_NO_EXCEPTION);
EXPECT_TRUE(stream->IsLocked());
auto* transferred =
ReadableStream::Deserialize(script_state, channel->port2(),
/*optimizer=*/nullptr, ASSERT_NO_EXCEPTION);
ASSERT_TRUE(transferred);
underlying_source->Enqueue(ScriptValue(isolate, V8String(isolate, "hello")));
underlying_source->Enqueue(ScriptValue(isolate, V8String(isolate, ", bye")));
underlying_source->Close();
EXPECT_EQ(ReadAll(scope, transferred),
base::make_optional<String>("hello, bye"));
}
TEST_F(ReadableStreamTest, DeserializeWithNullOptimizer) {
V8TestingScope scope;
auto optimizer = std::make_unique<ReadableStreamTransferringOptimizer>();
auto* script_state = scope.GetScriptState();
auto* isolate = scope.GetIsolate();
auto* underlying_source =
MakeGarbageCollected<TestUnderlyingSource>(script_state);
auto* stream = ReadableStream::CreateWithCountQueueingStrategy(
script_state, underlying_source, 0);
ASSERT_TRUE(stream);
auto* channel =
MakeGarbageCollected<MessageChannel>(scope.GetExecutionContext());
stream->Serialize(script_state, channel->port1(), ASSERT_NO_EXCEPTION);
EXPECT_TRUE(stream->IsLocked());
auto* transferred =
ReadableStream::Deserialize(script_state, channel->port2(),
std::move(optimizer), ASSERT_NO_EXCEPTION);
ASSERT_TRUE(transferred);
underlying_source->Enqueue(ScriptValue(isolate, V8String(isolate, "hello")));
underlying_source->Enqueue(ScriptValue(isolate, V8String(isolate, ", bye")));
underlying_source->Close();
EXPECT_EQ(ReadAll(scope, transferred),
base::make_optional<String>("hello, bye"));
}
TEST_F(ReadableStreamTest, DeserializeWithTestOptimizer) {
V8TestingScope scope;
auto optimizer = std::make_unique<TestTransferringOptimizer>();
auto* script_state = scope.GetScriptState();
auto* isolate = scope.GetIsolate();
auto* underlying_source =
MakeGarbageCollected<TestUnderlyingSource>(script_state);
auto* stream = ReadableStream::CreateWithCountQueueingStrategy(
script_state, underlying_source, 0);
ASSERT_TRUE(stream);
auto* channel =
MakeGarbageCollected<MessageChannel>(scope.GetExecutionContext());
stream->Serialize(script_state, channel->port1(), ASSERT_NO_EXCEPTION);
EXPECT_TRUE(stream->IsLocked());
auto* transferred =
ReadableStream::Deserialize(script_state, channel->port2(),
std::move(optimizer), ASSERT_NO_EXCEPTION);
ASSERT_TRUE(transferred);
underlying_source->Enqueue(ScriptValue(isolate, V8String(isolate, "hello")));
underlying_source->Enqueue(ScriptValue(isolate, V8String(isolate, ", bye")));
underlying_source->Close();
EXPECT_EQ(ReadAll(scope, transferred),
base::make_optional<String>("hello, byefoo, bar"));
}
TEST_F(ReadableStreamTest, GarbageCollectJavaScriptUnderlyingSource) {
V8TestingScope scope;
auto* isolate = scope.GetIsolate();
v8::Global<v8::Object> weak_underlying_source;
{
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Object> underlying_source = v8::Object::New(isolate);
ReadableStream::Create(scope.GetScriptState(),
ScriptValue(isolate, underlying_source),
ASSERT_NO_EXCEPTION);
weak_underlying_source = v8::Global<v8::Object>(isolate, underlying_source);
weak_underlying_source.SetWeak();
}
ThreadState::Current()->CollectAllGarbageForTesting();
EXPECT_TRUE(weak_underlying_source.IsEmpty());
}
TEST_F(ReadableStreamTest, GarbageCollectCPlusPlusUnderlyingSource) {
class NoopUnderlyingSource : public UnderlyingSourceBase {
public:
NoopUnderlyingSource(ScriptState* script_state)
: UnderlyingSourceBase(script_state) {}
};
V8TestingScope scope;
auto* isolate = scope.GetIsolate();
WeakPersistent<NoopUnderlyingSource> weak_underlying_source;
{
v8::HandleScope handle_scope(isolate);
auto* underlying_source =
MakeGarbageCollected<NoopUnderlyingSource>(scope.GetScriptState());
weak_underlying_source = underlying_source;
ReadableStream::CreateWithCountQueueingStrategy(scope.GetScriptState(),
underlying_source, 0);
}
// Allow Promises to resolve.
v8::MicrotasksScope::PerformCheckpoint(isolate);
ThreadState::Current()->CollectAllGarbageForTesting();
EXPECT_FALSE(weak_underlying_source);
}
} // namespace
} // namespace blink