blob: c7869ca20bace4f25d88021428989276b3fb00e5 [file] [log] [blame]
// Copyright 2014 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 "modules/fetch/BodyStreamBuffer.h"
#include "core/html/FormData.h"
#include "core/testing/DummyPageHolder.h"
#include "modules/fetch/DataConsumerHandleTestUtil.h"
#include "modules/fetch/FetchBlobDataConsumerHandle.h"
#include "modules/fetch/FetchFormDataConsumerHandle.h"
#include "platform/blob/BlobData.h"
#include "platform/blob/BlobURL.h"
#include "platform/network/EncodedFormData.h"
#include "platform/testing/UnitTestHelpers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "wtf/OwnPtr.h"
namespace blink {
namespace {
using ::testing::InSequence;
using ::testing::_;
using ::testing::SaveArg;
using Checkpoint = ::testing::StrictMock<::testing::MockFunction<void(int)>>;
using Command = DataConsumerHandleTestUtil::Command;
using ReplayingHandle = DataConsumerHandleTestUtil::ReplayingHandle;
using MockFetchDataLoaderClient = DataConsumerHandleTestUtil::MockFetchDataLoaderClient;
class FakeLoaderFactory : public FetchBlobDataConsumerHandle::LoaderFactory {
public:
PassOwnPtr<ThreadableLoader> create(ExecutionContext&, ThreadableLoaderClient*, const ThreadableLoaderOptions&, const ResourceLoaderOptions&) override
{
ASSERT_NOT_REACHED();
return nullptr;
}
};
class BodyStreamBufferTest : public ::testing::Test {
public:
BodyStreamBufferTest()
{
m_page = DummyPageHolder::create(IntSize(1, 1));
}
~BodyStreamBufferTest() override {}
protected:
ScriptState* getScriptState() { return ScriptState::forMainWorld(m_page->document().frame()); }
ExecutionContext* getExecutionContext() { return &m_page->document(); }
OwnPtr<DummyPageHolder> m_page;
ScriptValue eval(const char* s)
{
v8::Local<v8::String> source;
v8::Local<v8::Script> script;
v8::MicrotasksScope microtasks(getScriptState()->isolate(), v8::MicrotasksScope::kDoNotRunMicrotasks);
if (!v8Call(v8::String::NewFromUtf8(getScriptState()->isolate(), s, v8::NewStringType::kNormal), source)) {
ADD_FAILURE();
return ScriptValue();
}
if (!v8Call(v8::Script::Compile(getScriptState()->context(), source), script)) {
ADD_FAILURE() << "Compilation fails";
return ScriptValue();
}
return ScriptValue(getScriptState(), script->Run(getScriptState()->context()));
}
ScriptValue evalWithPrintingError(const char* s)
{
v8::TryCatch block(getScriptState()->isolate());
ScriptValue r = eval(s);
if (block.HasCaught()) {
ADD_FAILURE() << toCoreString(block.Exception()->ToString(getScriptState()->isolate())).utf8().data();
block.ReThrow();
}
return r;
}
};
TEST_F(BodyStreamBufferTest, Tee)
{
Checkpoint checkpoint;
MockFetchDataLoaderClient* client1 = MockFetchDataLoaderClient::create();
MockFetchDataLoaderClient* client2 = MockFetchDataLoaderClient::create();
InSequence s;
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*client1, didFetchDataLoadedString(String("hello, world")));
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(checkpoint, Call(3));
EXPECT_CALL(*client2, didFetchDataLoadedString(String("hello, world")));
EXPECT_CALL(checkpoint, Call(4));
OwnPtr<DataConsumerHandleTestUtil::ReplayingHandle> handle = DataConsumerHandleTestUtil::ReplayingHandle::create();
handle->add(DataConsumerHandleTestUtil::Command(DataConsumerHandleTestUtil::Command::Data, "hello, "));
handle->add(DataConsumerHandleTestUtil::Command(DataConsumerHandleTestUtil::Command::Data, "world"));
handle->add(DataConsumerHandleTestUtil::Command(DataConsumerHandleTestUtil::Command::Done));
BodyStreamBuffer* buffer = new BodyStreamBuffer(getScriptState(), createFetchDataConsumerHandleFromWebHandle(std::move(handle)));
BodyStreamBuffer* new1;
BodyStreamBuffer* new2;
buffer->tee(&new1, &new2);
EXPECT_TRUE(buffer->isStreamLocked());
EXPECT_TRUE(buffer->isStreamDisturbed());
EXPECT_FALSE(buffer->hasPendingActivity());
new1->startLoading(FetchDataLoader::createLoaderAsString(), client1);
checkpoint.Call(1);
testing::runPendingTasks();
checkpoint.Call(2);
new2->startLoading(FetchDataLoader::createLoaderAsString(), client2);
checkpoint.Call(3);
testing::runPendingTasks();
checkpoint.Call(4);
}
TEST_F(BodyStreamBufferTest, TeeFromHandleMadeFromStream)
{
ScriptState::Scope scope(getScriptState());
ScriptValue stream = evalWithPrintingError(
"stream = new ReadableStream({start: c => controller = c});"
"controller.enqueue(new Uint8Array([0x41, 0x42]));"
"controller.enqueue(new Uint8Array([0x55, 0x58]));"
"controller.close();"
"stream");
Checkpoint checkpoint;
MockFetchDataLoaderClient* client1 = MockFetchDataLoaderClient::create();
MockFetchDataLoaderClient* client2 = MockFetchDataLoaderClient::create();
InSequence s;
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*client1, didFetchDataLoadedString(String("ABUX")));
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(checkpoint, Call(3));
EXPECT_CALL(*client2, didFetchDataLoadedString(String("ABUX")));
EXPECT_CALL(checkpoint, Call(4));
BodyStreamBuffer* buffer = new BodyStreamBuffer(getScriptState(), stream);
BodyStreamBuffer* new1;
BodyStreamBuffer* new2;
buffer->tee(&new1, &new2);
EXPECT_TRUE(buffer->isStreamLocked());
// Note that this behavior is slightly different from for the behavior of
// a BodyStreamBuffer made from a FetchDataConsumerHandle. See the above
// test. In this test, the stream will get disturbed when the microtask
// is performed.
// TODO(yhirano): A uniformed behavior is preferred.
EXPECT_FALSE(buffer->isStreamDisturbed());
EXPECT_FALSE(buffer->hasPendingActivity());
v8::MicrotasksScope::PerformCheckpoint(getScriptState()->isolate());
EXPECT_TRUE(buffer->isStreamLocked());
EXPECT_TRUE(buffer->isStreamDisturbed());
EXPECT_FALSE(buffer->hasPendingActivity());
new1->startLoading(FetchDataLoader::createLoaderAsString(), client1);
checkpoint.Call(1);
testing::runPendingTasks();
checkpoint.Call(2);
new2->startLoading(FetchDataLoader::createLoaderAsString(), client2);
checkpoint.Call(3);
testing::runPendingTasks();
checkpoint.Call(4);
}
TEST_F(BodyStreamBufferTest, DrainAsBlobDataHandle)
{
OwnPtr<BlobData> data = BlobData::create();
data->appendText("hello", false);
auto size = data->length();
RefPtr<BlobDataHandle> blobDataHandle = BlobDataHandle::create(std::move(data), size);
BodyStreamBuffer* buffer = new BodyStreamBuffer(getScriptState(), FetchBlobDataConsumerHandle::create(getExecutionContext(), blobDataHandle, new FakeLoaderFactory));
EXPECT_FALSE(buffer->isStreamLocked());
EXPECT_FALSE(buffer->isStreamDisturbed());
EXPECT_FALSE(buffer->hasPendingActivity());
RefPtr<BlobDataHandle> outputBlobDataHandle = buffer->drainAsBlobDataHandle(FetchDataConsumerHandle::Reader::AllowBlobWithInvalidSize);
EXPECT_TRUE(buffer->isStreamLocked());
EXPECT_TRUE(buffer->isStreamDisturbed());
EXPECT_FALSE(buffer->hasPendingActivity());
EXPECT_EQ(blobDataHandle, outputBlobDataHandle);
}
TEST_F(BodyStreamBufferTest, DrainAsBlobDataHandleReturnsNull)
{
// This handle is not drainable.
OwnPtr<FetchDataConsumerHandle> handle = createFetchDataConsumerHandleFromWebHandle(createWaitingDataConsumerHandle());
BodyStreamBuffer* buffer = new BodyStreamBuffer(getScriptState(), std::move(handle));
EXPECT_FALSE(buffer->isStreamLocked());
EXPECT_FALSE(buffer->isStreamDisturbed());
EXPECT_FALSE(buffer->hasPendingActivity());
EXPECT_FALSE(buffer->drainAsBlobDataHandle(FetchDataConsumerHandle::Reader::AllowBlobWithInvalidSize));
EXPECT_FALSE(buffer->isStreamLocked());
EXPECT_FALSE(buffer->isStreamDisturbed());
EXPECT_FALSE(buffer->hasPendingActivity());
}
TEST_F(BodyStreamBufferTest, DrainAsBlobFromBufferMadeFromBufferMadeFromStream)
{
ScriptState::Scope scope(getScriptState());
ScriptValue stream = evalWithPrintingError("new ReadableStream()");
BodyStreamBuffer* buffer = new BodyStreamBuffer(getScriptState(), stream);
EXPECT_FALSE(buffer->hasPendingActivity());
EXPECT_FALSE(buffer->isStreamLocked());
EXPECT_FALSE(buffer->isStreamDisturbed());
EXPECT_TRUE(buffer->isStreamReadable());
EXPECT_FALSE(buffer->drainAsBlobDataHandle(FetchDataConsumerHandle::Reader::AllowBlobWithInvalidSize));
EXPECT_FALSE(buffer->hasPendingActivity());
EXPECT_FALSE(buffer->isStreamLocked());
EXPECT_FALSE(buffer->isStreamDisturbed());
EXPECT_TRUE(buffer->isStreamReadable());
}
TEST_F(BodyStreamBufferTest, DrainAsFormData)
{
FormData* data = FormData::create(UTF8Encoding());
data->append("name1", "value1");
data->append("name2", "value2");
RefPtr<EncodedFormData> inputFormData = data->encodeMultiPartFormData();
BodyStreamBuffer* buffer = new BodyStreamBuffer(getScriptState(), FetchFormDataConsumerHandle::create(getExecutionContext(), inputFormData));
EXPECT_FALSE(buffer->isStreamLocked());
EXPECT_FALSE(buffer->isStreamDisturbed());
EXPECT_FALSE(buffer->hasPendingActivity());
RefPtr<EncodedFormData> outputFormData = buffer->drainAsFormData();
EXPECT_TRUE(buffer->isStreamLocked());
EXPECT_TRUE(buffer->isStreamDisturbed());
EXPECT_FALSE(buffer->hasPendingActivity());
EXPECT_EQ(outputFormData->flattenToString(), inputFormData->flattenToString());
}
TEST_F(BodyStreamBufferTest, DrainAsFormDataReturnsNull)
{
// This handle is not drainable.
OwnPtr<FetchDataConsumerHandle> handle = createFetchDataConsumerHandleFromWebHandle(createWaitingDataConsumerHandle());
BodyStreamBuffer* buffer = new BodyStreamBuffer(getScriptState(), std::move(handle));
EXPECT_FALSE(buffer->isStreamLocked());
EXPECT_FALSE(buffer->isStreamDisturbed());
EXPECT_FALSE(buffer->hasPendingActivity());
EXPECT_FALSE(buffer->drainAsFormData());
EXPECT_FALSE(buffer->isStreamLocked());
EXPECT_FALSE(buffer->isStreamDisturbed());
EXPECT_FALSE(buffer->hasPendingActivity());
}
TEST_F(BodyStreamBufferTest, DrainAsFormDataFromBufferMadeFromBufferMadeFromStream)
{
ScriptState::Scope scope(getScriptState());
ScriptValue stream = evalWithPrintingError("new ReadableStream()");
BodyStreamBuffer* buffer = new BodyStreamBuffer(getScriptState(), stream);
EXPECT_FALSE(buffer->hasPendingActivity());
EXPECT_FALSE(buffer->isStreamLocked());
EXPECT_FALSE(buffer->isStreamDisturbed());
EXPECT_TRUE(buffer->isStreamReadable());
EXPECT_FALSE(buffer->drainAsFormData());
EXPECT_FALSE(buffer->hasPendingActivity());
EXPECT_FALSE(buffer->isStreamLocked());
EXPECT_FALSE(buffer->isStreamDisturbed());
EXPECT_TRUE(buffer->isStreamReadable());
}
TEST_F(BodyStreamBufferTest, LoadBodyStreamBufferAsArrayBuffer)
{
Checkpoint checkpoint;
MockFetchDataLoaderClient* client = MockFetchDataLoaderClient::create();
DOMArrayBuffer* arrayBuffer = nullptr;
InSequence s;
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*client, didFetchDataLoadedArrayBufferMock(_)).WillOnce(SaveArg<0>(&arrayBuffer));
EXPECT_CALL(checkpoint, Call(2));
OwnPtr<ReplayingHandle> handle = ReplayingHandle::create();
handle->add(Command(Command::Data, "hello"));
handle->add(Command(Command::Done));
BodyStreamBuffer* buffer = new BodyStreamBuffer(getScriptState(), createFetchDataConsumerHandleFromWebHandle(std::move(handle)));
buffer->startLoading(FetchDataLoader::createLoaderAsArrayBuffer(), client);
EXPECT_TRUE(buffer->isStreamLocked());
EXPECT_TRUE(buffer->isStreamDisturbed());
EXPECT_TRUE(buffer->hasPendingActivity());
checkpoint.Call(1);
testing::runPendingTasks();
checkpoint.Call(2);
EXPECT_TRUE(buffer->isStreamLocked());
EXPECT_TRUE(buffer->isStreamDisturbed());
EXPECT_FALSE(buffer->hasPendingActivity());
ASSERT_TRUE(arrayBuffer);
EXPECT_EQ("hello", String(static_cast<const char*>(arrayBuffer->data()), arrayBuffer->byteLength()));
}
TEST_F(BodyStreamBufferTest, LoadBodyStreamBufferAsBlob)
{
Checkpoint checkpoint;
MockFetchDataLoaderClient* client = MockFetchDataLoaderClient::create();
RefPtr<BlobDataHandle> blobDataHandle;
InSequence s;
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*client, didFetchDataLoadedBlobHandleMock(_)).WillOnce(SaveArg<0>(&blobDataHandle));
EXPECT_CALL(checkpoint, Call(2));
OwnPtr<ReplayingHandle> handle = ReplayingHandle::create();
handle->add(Command(Command::Data, "hello"));
handle->add(Command(Command::Done));
BodyStreamBuffer* buffer = new BodyStreamBuffer(getScriptState(), createFetchDataConsumerHandleFromWebHandle(std::move(handle)));
buffer->startLoading(FetchDataLoader::createLoaderAsBlobHandle("text/plain"), client);
EXPECT_TRUE(buffer->isStreamLocked());
EXPECT_TRUE(buffer->isStreamDisturbed());
EXPECT_TRUE(buffer->hasPendingActivity());
checkpoint.Call(1);
testing::runPendingTasks();
checkpoint.Call(2);
EXPECT_TRUE(buffer->isStreamLocked());
EXPECT_TRUE(buffer->isStreamDisturbed());
EXPECT_FALSE(buffer->hasPendingActivity());
EXPECT_EQ(5u, blobDataHandle->size());
}
TEST_F(BodyStreamBufferTest, LoadBodyStreamBufferAsString)
{
Checkpoint checkpoint;
MockFetchDataLoaderClient* client = MockFetchDataLoaderClient::create();
InSequence s;
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*client, didFetchDataLoadedString(String("hello")));
EXPECT_CALL(checkpoint, Call(2));
OwnPtr<ReplayingHandle> handle = ReplayingHandle::create();
handle->add(Command(Command::Data, "hello"));
handle->add(Command(Command::Done));
BodyStreamBuffer* buffer = new BodyStreamBuffer(getScriptState(), createFetchDataConsumerHandleFromWebHandle(std::move(handle)));
buffer->startLoading(FetchDataLoader::createLoaderAsString(), client);
EXPECT_TRUE(buffer->isStreamLocked());
EXPECT_TRUE(buffer->isStreamDisturbed());
EXPECT_TRUE(buffer->hasPendingActivity());
checkpoint.Call(1);
testing::runPendingTasks();
checkpoint.Call(2);
EXPECT_TRUE(buffer->isStreamLocked());
EXPECT_TRUE(buffer->isStreamDisturbed());
EXPECT_FALSE(buffer->hasPendingActivity());
}
TEST_F(BodyStreamBufferTest, LoadClosedHandle)
{
Checkpoint checkpoint;
MockFetchDataLoaderClient* client = MockFetchDataLoaderClient::create();
InSequence s;
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*client, didFetchDataLoadedString(String("")));
EXPECT_CALL(checkpoint, Call(2));
BodyStreamBuffer* buffer = new BodyStreamBuffer(getScriptState(), createFetchDataConsumerHandleFromWebHandle(createDoneDataConsumerHandle()));
EXPECT_TRUE(buffer->isStreamReadable());
testing::runPendingTasks();
EXPECT_TRUE(buffer->isStreamClosed());
EXPECT_FALSE(buffer->isStreamLocked());
EXPECT_FALSE(buffer->isStreamDisturbed());
EXPECT_FALSE(buffer->hasPendingActivity());
buffer->startLoading(FetchDataLoader::createLoaderAsString(), client);
EXPECT_TRUE(buffer->isStreamLocked());
EXPECT_TRUE(buffer->isStreamDisturbed());
EXPECT_TRUE(buffer->hasPendingActivity());
checkpoint.Call(1);
testing::runPendingTasks();
checkpoint.Call(2);
EXPECT_TRUE(buffer->isStreamLocked());
EXPECT_TRUE(buffer->isStreamDisturbed());
EXPECT_FALSE(buffer->hasPendingActivity());
}
TEST_F(BodyStreamBufferTest, LoadErroredHandle)
{
Checkpoint checkpoint;
MockFetchDataLoaderClient* client = MockFetchDataLoaderClient::create();
InSequence s;
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*client, didFetchDataLoadFailed());
EXPECT_CALL(checkpoint, Call(2));
BodyStreamBuffer* buffer = new BodyStreamBuffer(getScriptState(), createFetchDataConsumerHandleFromWebHandle(createUnexpectedErrorDataConsumerHandle()));
EXPECT_TRUE(buffer->isStreamReadable());
testing::runPendingTasks();
EXPECT_TRUE(buffer->isStreamErrored());
EXPECT_FALSE(buffer->isStreamLocked());
EXPECT_FALSE(buffer->isStreamDisturbed());
EXPECT_FALSE(buffer->hasPendingActivity());
buffer->startLoading(FetchDataLoader::createLoaderAsString(), client);
EXPECT_TRUE(buffer->isStreamLocked());
EXPECT_TRUE(buffer->isStreamDisturbed());
EXPECT_TRUE(buffer->hasPendingActivity());
checkpoint.Call(1);
testing::runPendingTasks();
checkpoint.Call(2);
EXPECT_TRUE(buffer->isStreamLocked());
EXPECT_TRUE(buffer->isStreamDisturbed());
EXPECT_FALSE(buffer->hasPendingActivity());
}
TEST_F(BodyStreamBufferTest, LoaderShouldBeKeptAliveByBodyStreamBuffer)
{
Checkpoint checkpoint;
MockFetchDataLoaderClient* client = MockFetchDataLoaderClient::create();
InSequence s;
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*client, didFetchDataLoadedString(String("hello")));
EXPECT_CALL(checkpoint, Call(2));
OwnPtr<ReplayingHandle> handle = ReplayingHandle::create();
handle->add(Command(Command::Data, "hello"));
handle->add(Command(Command::Done));
Persistent<BodyStreamBuffer> buffer = new BodyStreamBuffer(getScriptState(), createFetchDataConsumerHandleFromWebHandle(std::move(handle)));
buffer->startLoading(FetchDataLoader::createLoaderAsString(), client);
ThreadHeap::collectAllGarbage();
checkpoint.Call(1);
testing::runPendingTasks();
checkpoint.Call(2);
}
// TODO(hiroshige): Merge this class into MockFetchDataConsumerHandle.
class MockFetchDataConsumerHandleWithMockDestructor : public DataConsumerHandleTestUtil::MockFetchDataConsumerHandle {
public:
static PassOwnPtr<::testing::StrictMock<MockFetchDataConsumerHandleWithMockDestructor>> create() { return adoptPtr(new ::testing::StrictMock<MockFetchDataConsumerHandleWithMockDestructor>); }
~MockFetchDataConsumerHandleWithMockDestructor() override
{
destruct();
}
MOCK_METHOD0(destruct, void());
};
TEST_F(BodyStreamBufferTest, SourceHandleAndReaderShouldBeDestructedWhenCanceled)
{
ScriptState::Scope scope(getScriptState());
using MockHandle = MockFetchDataConsumerHandleWithMockDestructor;
using MockReader = DataConsumerHandleTestUtil::MockFetchDataConsumerReader;
OwnPtr<MockHandle> handle = MockHandle::create();
OwnPtr<MockReader> reader = MockReader::create();
Checkpoint checkpoint;
InSequence s;
EXPECT_CALL(*handle, obtainReaderInternal(_)).WillOnce(::testing::Return(reader.get()));
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*reader, destruct());
EXPECT_CALL(*handle, destruct());
EXPECT_CALL(checkpoint, Call(2));
// |reader| is adopted by |obtainReader|.
ASSERT_TRUE(reader.leakPtr());
BodyStreamBuffer* buffer = new BodyStreamBuffer(getScriptState(), std::move(handle));
checkpoint.Call(1);
ScriptValue reason(getScriptState(), v8String(getScriptState()->isolate(), "reason"));
buffer->cancelSource(getScriptState(), reason);
checkpoint.Call(2);
}
} // namespace
} // namespace blink