Commit 2e3335cb authored by Marijn Kruisselbrink's avatar Marijn Kruisselbrink Committed by Commit Bot

Rewrite BlobBytesConsumer to not rely on blob URLs.

The existing implementation depended on the non-mojo blob URL code path,
and so would break soon after mojo blob URLs ship when the old code gets
deleted. So rewrite the implementation to read blobs more directly rather
than going through a blob URL.

Bug: 859109
Change-Id: Ia6f2432e48ac5304ea8f5598d293e31c2c1e1905
Reviewed-on: https://chromium-review.googlesource.com/1120679
Commit-Queue: Marijn Kruisselbrink <mek@chromium.org>
Reviewed-by: default avatarYutaka Hirano <yhirano@chromium.org>
Cr-Commit-Position: refs/heads/master@{#572363}
parent ad08d8f0
......@@ -30,7 +30,9 @@ promise_test(function(test) {
var closedPromise = reader.closed.then(function() {
return reader.cancel();
});
reader.read();
reader.read().then(function readMore({done, value}) {
if (!done) return reader.read().then(readMore);
});
return closedPromise;
}, "Cancelling a closed blob Response stream");
......
......@@ -4,132 +4,52 @@
#include "third_party/blink/renderer/core/fetch/blob_bytes_consumer.h"
#include "third_party/blink/renderer/core/fetch/bytes_consumer_for_data_consumer_handle.h"
#include "third_party/blink/renderer/core/loader/threadable_loader.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/fetch/form_data_bytes_consumer.h"
#include "third_party/blink/renderer/platform/blob/blob_data.h"
#include "third_party/blink/renderer/platform/blob/blob_registry.h"
#include "third_party/blink/renderer/platform/blob/blob_url.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_error.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
namespace blink {
BlobBytesConsumer::BlobBytesConsumer(
ExecutionContext* execution_context,
scoped_refptr<BlobDataHandle> blob_data_handle,
ThreadableLoader* loader)
: ContextLifecycleObserver(execution_context),
blob_data_handle_(std::move(blob_data_handle)),
loader_(loader) {
if (!blob_data_handle_) {
// Note that |m_loader| is non-null only in tests.
if (loader_) {
loader_->Cancel();
loader_ = nullptr;
}
state_ = PublicState::kClosed;
}
}
BlobBytesConsumer::BlobBytesConsumer(
ExecutionContext* execution_context,
scoped_refptr<BlobDataHandle> blob_data_handle)
: BlobBytesConsumer(execution_context,
std::move(blob_data_handle),
nullptr) {}
: execution_context_(execution_context),
blob_data_handle_(std::move(blob_data_handle)) {}
BlobBytesConsumer::~BlobBytesConsumer() {
if (!blob_url_.IsEmpty())
BlobRegistry::RevokePublicBlobURL(blob_url_);
}
BytesConsumer::Result BlobBytesConsumer::BeginRead(const char** buffer,
size_t* available) {
*buffer = nullptr;
*available = 0;
if (state_ == PublicState::kClosed) {
// It's possible that |cancel| has been called before the first
// |beginRead| call. That's why we need to check this condition
// before checking |isClean()|.
return Result::kDone;
}
if (IsClean()) {
DCHECK(blob_url_.IsEmpty());
blob_url_ =
BlobURL::CreatePublicURL(GetExecutionContext()->GetSecurityOrigin());
if (blob_url_.IsEmpty()) {
GetError();
} else {
BlobRegistry::RegisterPublicBlobURL(
GetExecutionContext()->GetMutableSecurityOrigin(), blob_url_,
blob_data_handle_);
// m_loader is non-null only in tests.
if (!loader_)
loader_ = CreateLoader();
ResourceRequest request(blob_url_);
request.SetRequestContext(WebURLRequest::kRequestContextInternal);
request.SetFetchRequestMode(
network::mojom::FetchRequestMode::kSameOrigin);
request.SetFetchCredentialsMode(
network::mojom::FetchCredentialsMode::kOmit);
request.SetUseStreamOnResponse(true);
// We intentionally skip
// 'setExternalRequestStateFromRequestorAddressSpace', as 'blob:'
// can never be external.
loader_->Start(request);
}
if (!nested_consumer_) {
if (!blob_data_handle_)
return Result::kDone;
scoped_refptr<EncodedFormData> form_data = EncodedFormData::Create();
form_data->AppendDataPipe(base::MakeRefCounted<WrappedDataPipeGetter>(
blob_data_handle_->AsDataPipeGetter()));
nested_consumer_ =
new FormDataBytesConsumer(execution_context_, std::move(form_data));
if (client_)
nested_consumer_->SetClient(client_);
blob_data_handle_ = nullptr;
client_ = nullptr;
}
DCHECK_NE(state_, PublicState::kClosed);
if (state_ == PublicState::kErrored)
return Result::kError;
if (!body_) {
// The response has not arrived.
return Result::kShouldWait;
}
auto result = body_->BeginRead(buffer, available);
switch (result) {
case Result::kOk:
case Result::kShouldWait:
break;
case Result::kDone:
has_seen_end_of_data_ = true;
if (has_finished_loading_)
Close();
return state_ == PublicState::kClosed ? Result::kDone
: Result::kShouldWait;
case Result::kError:
GetError();
break;
}
return result;
return nested_consumer_->BeginRead(buffer, available);
}
BytesConsumer::Result BlobBytesConsumer::EndRead(size_t read) {
DCHECK(body_);
return body_->EndRead(read);
DCHECK(nested_consumer_);
return nested_consumer_->EndRead(read);
}
scoped_refptr<BlobDataHandle> BlobBytesConsumer::DrainAsBlobDataHandle(
BlobSizePolicy policy) {
if (!IsClean())
if (!blob_data_handle_)
return nullptr;
DCHECK(blob_data_handle_);
if (policy == BlobSizePolicy::kDisallowBlobWithInvalidSize &&
blob_data_handle_->size() == UINT64_MAX)
return nullptr;
Close();
return std::move(blob_data_handle_);
}
......@@ -146,172 +66,43 @@ scoped_refptr<EncodedFormData> BlobBytesConsumer::DrainAsFormData() {
void BlobBytesConsumer::SetClient(BytesConsumer::Client* client) {
DCHECK(!client_);
DCHECK(client);
client_ = client;
if (nested_consumer_)
nested_consumer_->SetClient(client);
else
client_ = client;
}
void BlobBytesConsumer::ClearClient() {
client_ = nullptr;
if (nested_consumer_)
nested_consumer_->ClearClient();
}
void BlobBytesConsumer::Cancel() {
if (state_ == PublicState::kClosed || state_ == PublicState::kErrored)
return;
Close();
if (body_) {
body_->Cancel();
body_ = nullptr;
}
if (!blob_url_.IsEmpty()) {
BlobRegistry::RevokePublicBlobURL(blob_url_);
blob_url_ = KURL();
}
if (nested_consumer_)
nested_consumer_->Cancel();
blob_data_handle_ = nullptr;
client_ = nullptr;
}
BytesConsumer::Error BlobBytesConsumer::GetError() const {
DCHECK_EQ(PublicState::kErrored, state_);
return Error("Failed to load a blob.");
DCHECK(nested_consumer_);
return nested_consumer_->GetError();
}
BytesConsumer::PublicState BlobBytesConsumer::GetPublicState() const {
return state_;
}
void BlobBytesConsumer::ContextDestroyed(ExecutionContext*) {
if (state_ != PublicState::kReadableOrWaiting)
return;
BytesConsumer::Client* client = client_;
GetError();
if (client)
client->OnStateChange();
}
void BlobBytesConsumer::OnStateChange() {
if (state_ != PublicState::kReadableOrWaiting)
return;
DCHECK(body_);
BytesConsumer::Client* client = client_;
switch (body_->GetPublicState()) {
case PublicState::kReadableOrWaiting:
break;
case PublicState::kClosed:
has_seen_end_of_data_ = true;
if (has_finished_loading_)
Close();
break;
case PublicState::kErrored:
GetError();
break;
if (!nested_consumer_) {
return blob_data_handle_ ? PublicState::kReadableOrWaiting
: PublicState::kClosed;
}
if (client)
client->OnStateChange();
}
void BlobBytesConsumer::DidReceiveResponse(
unsigned long identifier,
const ResourceResponse&,
std::unique_ptr<WebDataConsumerHandle> handle) {
DCHECK(handle);
DCHECK(!body_);
DCHECK_EQ(PublicState::kReadableOrWaiting, state_);
body_ = new BytesConsumerForDataConsumerHandle(GetExecutionContext(),
std::move(handle));
body_->SetClient(this);
if (IsClean()) {
// This function is called synchronously in ThreadableLoader::start.
return;
}
OnStateChange();
}
void BlobBytesConsumer::DidFinishLoading(unsigned long identifier) {
DCHECK_EQ(PublicState::kReadableOrWaiting, state_);
has_finished_loading_ = true;
loader_ = nullptr;
if (!has_seen_end_of_data_)
return;
DCHECK(!IsClean());
BytesConsumer::Client* client = client_;
Close();
if (client)
client->OnStateChange();
}
void BlobBytesConsumer::DidFail(const ResourceError& e) {
if (e.IsCancellation()) {
if (state_ != PublicState::kReadableOrWaiting)
return;
}
DCHECK_EQ(PublicState::kReadableOrWaiting, state_);
loader_ = nullptr;
BytesConsumer::Client* client = client_;
GetError();
if (IsClean()) {
// This function is called synchronously in ThreadableLoader::start.
return;
}
if (client) {
client->OnStateChange();
client = nullptr;
}
}
void BlobBytesConsumer::DidFailRedirectCheck() {
NOTREACHED();
return nested_consumer_->GetPublicState();
}
void BlobBytesConsumer::Trace(blink::Visitor* visitor) {
visitor->Trace(body_);
visitor->Trace(execution_context_);
visitor->Trace(nested_consumer_);
visitor->Trace(client_);
visitor->Trace(loader_);
BytesConsumer::Trace(visitor);
BytesConsumer::Client::Trace(visitor);
ContextLifecycleObserver::Trace(visitor);
}
BlobBytesConsumer* BlobBytesConsumer::CreateForTesting(
ExecutionContext* execution_context,
scoped_refptr<BlobDataHandle> blob_data_handle,
ThreadableLoader* loader) {
return new BlobBytesConsumer(execution_context, std::move(blob_data_handle),
loader);
}
ThreadableLoader* BlobBytesConsumer::CreateLoader() {
ThreadableLoaderOptions options;
ResourceLoaderOptions resource_loader_options;
resource_loader_options.data_buffering_policy = kDoNotBufferData;
resource_loader_options.initiator_info.name =
FetchInitiatorTypeNames::internal;
return ThreadableLoader::Create(*GetExecutionContext(), this, options,
resource_loader_options);
}
void BlobBytesConsumer::Close() {
DCHECK_EQ(state_, PublicState::kReadableOrWaiting);
state_ = PublicState::kClosed;
Clear();
}
void BlobBytesConsumer::GetError() {
DCHECK_EQ(state_, PublicState::kReadableOrWaiting);
state_ = PublicState::kErrored;
Clear();
}
void BlobBytesConsumer::Clear() {
DCHECK_NE(state_, PublicState::kReadableOrWaiting);
if (loader_) {
loader_->Cancel();
loader_ = nullptr;
}
client_ = nullptr;
}
} // namespace blink
......@@ -8,28 +8,17 @@
#include <memory>
#include "base/memory/scoped_refptr.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/dom/context_lifecycle_observer.h"
#include "third_party/blink/renderer/core/fetch/bytes_consumer.h"
#include "third_party/blink/renderer/core/loader/threadable_loader_client.h"
#include "third_party/blink/renderer/platform/heap/handle.h"
namespace blink {
class BlobDataHandle;
class EncodedFormData;
class ExecutionContext;
class ThreadableLoader;
class WebDataConsumerHandle;
// A BlobBytesConsumer is created from a blob handle and it will
// return a valid handle from drainAsBlobDataHandle as much as possible.
class CORE_EXPORT BlobBytesConsumer final : public BytesConsumer,
public ContextLifecycleObserver,
public BytesConsumer::Client,
public ThreadableLoaderClient {
USING_GARBAGE_COLLECTED_MIXIN(BlobBytesConsumer);
USING_PRE_FINALIZER(BlobBytesConsumer, Cancel);
class CORE_EXPORT BlobBytesConsumer final : public BytesConsumer {
public:
// |handle| can be null. In that case this consumer gets closed.
BlobBytesConsumer(ExecutionContext*,
......@@ -48,47 +37,13 @@ class CORE_EXPORT BlobBytesConsumer final : public BytesConsumer,
Error GetError() const override;
String DebugName() const override { return "BlobBytesConsumer"; }
// ContextLifecycleObserver implementation
void ContextDestroyed(ExecutionContext*) override;
// BytesConsumer::Client implementation
void OnStateChange() override;
// ThreadableLoaderClient implementation
void DidReceiveResponse(unsigned long identifier,
const ResourceResponse&,
std::unique_ptr<WebDataConsumerHandle>) override;
void DidFinishLoading(unsigned long identifier) override;
void DidFail(const ResourceError&) override;
void DidFailRedirectCheck() override;
void Trace(blink::Visitor*) override;
static BlobBytesConsumer* CreateForTesting(ExecutionContext*,
scoped_refptr<BlobDataHandle>,
ThreadableLoader*);
private:
BlobBytesConsumer(ExecutionContext*,
scoped_refptr<BlobDataHandle>,
ThreadableLoader*);
ThreadableLoader* CreateLoader();
void DidFailInternal();
bool IsClean() const { return blob_data_handle_.get(); }
void Close();
void GetError();
void Clear();
KURL blob_url_;
Member<ExecutionContext> execution_context_;
scoped_refptr<BlobDataHandle> blob_data_handle_;
Member<BytesConsumer> body_;
Member<BytesConsumer> nested_consumer_;
Member<BytesConsumer::Client> client_;
Member<ThreadableLoader> loader_;
PublicState state_ = PublicState::kReadableOrWaiting;
// These two booleans are meaningful only when m_state is ReadableOrWaiting.
bool has_seen_end_of_data_ = false;
bool has_finished_loading_ = false;
};
} // namespace blink
......
......@@ -4,12 +4,14 @@
#include "third_party/blink/renderer/core/fetch/blob_bytes_consumer.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/fetch/bytes_consumer_test_util.h"
#include "third_party/blink/renderer/core/fetch/data_consumer_handle_test_util.h"
#include "third_party/blink/renderer/core/loader/threadable_loader.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/platform/blob/blob_data.h"
#include "third_party/blink/renderer/platform/blob/testing/fake_blob.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_error.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
#include "third_party/blink/renderer/platform/network/encoded_form_data.h"
......@@ -25,92 +27,6 @@ using PublicState = BytesConsumer::PublicState;
using ReplayingHandle = DataConsumerHandleTestUtil::ReplayingHandle;
using Result = BytesConsumer::Result;
class TestThreadableLoader : public ThreadableLoader {
public:
~TestThreadableLoader() override {
EXPECT_FALSE(should_be_cancelled_ && !is_cancelled_)
<< "The loader should be cancelled but is not cancelled.";
}
void Start(const ResourceRequest& request) override { is_started_ = true; }
void OverrideTimeout(unsigned long timeout_milliseconds) override {
ADD_FAILURE() << "overrideTimeout should not be called.";
}
void Cancel() override { is_cancelled_ = true; }
void Detach() override { NOTREACHED(); }
bool IsStarted() const { return is_started_; }
bool IsCancelled() const { return is_cancelled_; }
void SetShouldBeCancelled() { should_be_cancelled_ = true; }
private:
bool is_started_ = false;
bool is_cancelled_ = false;
bool should_be_cancelled_ = false;
};
class SyncLoadingTestThreadableLoader : public ThreadableLoader {
public:
~SyncLoadingTestThreadableLoader() override { DCHECK(!handle_); }
void Start(const ResourceRequest& request) override {
is_started_ = true;
client_->DidReceiveResponse(0, ResourceResponse(), std::move(handle_));
client_->DidFinishLoading(0);
}
void OverrideTimeout(unsigned long timeout_milliseconds) override {
ADD_FAILURE() << "overrideTimeout should not be called.";
}
void Cancel() override { is_cancelled_ = true; }
void Detach() override { NOTREACHED(); }
bool IsStarted() const { return is_started_; }
bool IsCancelled() const { return is_cancelled_; }
void SetClient(ThreadableLoaderClient* client) { client_ = client; }
void SetHandle(std::unique_ptr<WebDataConsumerHandle> handle) {
handle_ = std::move(handle);
}
private:
bool is_started_ = false;
bool is_cancelled_ = false;
ThreadableLoaderClient* client_ = nullptr;
std::unique_ptr<WebDataConsumerHandle> handle_;
};
class SyncErrorTestThreadableLoader : public ThreadableLoader {
public:
~SyncErrorTestThreadableLoader() override {}
void Start(const ResourceRequest& request) override {
is_started_ = true;
client_->DidFail(ResourceError::Failure(NullURL()));
}
void OverrideTimeout(unsigned long timeout_milliseconds) override {
ADD_FAILURE() << "overrideTimeout should not be called.";
}
void Cancel() override { is_cancelled_ = true; }
void Detach() override { NOTREACHED(); }
bool IsStarted() const { return is_started_; }
bool IsCancelled() const { return is_cancelled_; }
void SetClient(ThreadableLoaderClient* client) { client_ = client; }
private:
bool is_started_ = false;
bool is_cancelled_ = false;
ThreadableLoaderClient* client_ = nullptr;
};
class BlobBytesConsumerTestClient final
: public GarbageCollectedFinalized<BlobBytesConsumerTestClient>,
public BytesConsumer::Client {
......@@ -128,144 +44,71 @@ class BlobBytesConsumerTestClient final
class BlobBytesConsumerTest : public PageTestBase {
public:
void SetUp() override { PageTestBase::SetUp(IntSize(1, 1)); }
scoped_refptr<BlobDataHandle> CreateBlob(const String& body) {
mojom::blink::BlobPtrInfo mojo_blob;
mojo::MakeStrongBinding(
std::make_unique<FakeBlob>(kBlobUUID, body, &blob_state_),
MakeRequest(&mojo_blob));
return BlobDataHandle::Create(kBlobUUID, "", body.length(),
std::move(mojo_blob));
}
bool DidStartLoading() {
base::RunLoop().RunUntilIdle();
return blob_state_.did_initiate_read_operation;
}
private:
const String kBlobUUID = "blob-id";
FakeBlob::State blob_state_;
};
TEST_F(BlobBytesConsumerTest, TwoPhaseRead) {
scoped_refptr<BlobDataHandle> blob_data_handle =
BlobDataHandle::Create(BlobData::Create(), 12345);
TestThreadableLoader* loader = new TestThreadableLoader();
BlobBytesConsumer* consumer = BlobBytesConsumer::CreateForTesting(
&GetDocument(), blob_data_handle, loader);
std::unique_ptr<ReplayingHandle> src = ReplayingHandle::Create();
src->Add(DataConsumerCommand(DataConsumerCommand::kData, "hello, "));
src->Add(DataConsumerCommand(DataConsumerCommand::kWait));
src->Add(DataConsumerCommand(DataConsumerCommand::kData, "world"));
src->Add(DataConsumerCommand(DataConsumerCommand::kDone));
String body = "hello, world";
scoped_refptr<BlobDataHandle> blob_data_handle = CreateBlob(body);
BlobBytesConsumer* consumer =
new BlobBytesConsumer(&GetDocument(), blob_data_handle);
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
EXPECT_FALSE(loader->IsStarted());
EXPECT_FALSE(DidStartLoading());
const char* buffer = nullptr;
size_t available = 0;
EXPECT_EQ(Result::kShouldWait, consumer->BeginRead(&buffer, &available));
EXPECT_TRUE(loader->IsStarted());
EXPECT_TRUE(DidStartLoading());
EXPECT_FALSE(consumer->DrainAsBlobDataHandle(
BytesConsumer::BlobSizePolicy::kAllowBlobWithInvalidSize));
EXPECT_FALSE(consumer->DrainAsFormData());
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
consumer->DidReceiveResponse(0, ResourceResponse(), std::move(src));
consumer->DidFinishLoading(0);
auto result = (new BytesConsumerTestUtil::TwoPhaseReader(consumer))->Run();
EXPECT_EQ(Result::kDone, result.first);
EXPECT_EQ("hello, world",
BytesConsumerTestUtil::CharVectorToString(result.second));
}
TEST_F(BlobBytesConsumerTest, FailLoading) {
scoped_refptr<BlobDataHandle> blob_data_handle =
BlobDataHandle::Create(BlobData::Create(), 12345);
TestThreadableLoader* loader = new TestThreadableLoader();
BlobBytesConsumer* consumer = BlobBytesConsumer::CreateForTesting(
&GetDocument(), blob_data_handle, loader);
BlobBytesConsumerTestClient* client = new BlobBytesConsumerTestClient();
consumer->SetClient(client);
const char* buffer = nullptr;
size_t available = 0;
EXPECT_EQ(Result::kShouldWait, consumer->BeginRead(&buffer, &available));
EXPECT_TRUE(loader->IsStarted());
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
int num_on_state_change_called = client->NumOnStateChangeCalled();
consumer->DidFail(ResourceError::Failure(NullURL()));
EXPECT_EQ(num_on_state_change_called + 1, client->NumOnStateChangeCalled());
EXPECT_EQ(PublicState::kErrored, consumer->GetPublicState());
EXPECT_EQ(Result::kError, consumer->BeginRead(&buffer, &available));
}
TEST_F(BlobBytesConsumerTest, FailLoadingAfterResponseReceived) {
scoped_refptr<BlobDataHandle> blob_data_handle =
BlobDataHandle::Create(BlobData::Create(), 12345);
TestThreadableLoader* loader = new TestThreadableLoader();
BlobBytesConsumer* consumer = BlobBytesConsumer::CreateForTesting(
&GetDocument(), blob_data_handle, loader);
BlobBytesConsumerTestClient* client = new BlobBytesConsumerTestClient();
consumer->SetClient(client);
const char* buffer = nullptr;
size_t available;
EXPECT_EQ(Result::kShouldWait, consumer->BeginRead(&buffer, &available));
EXPECT_TRUE(loader->IsStarted());
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
int num_on_state_change_called = client->NumOnStateChangeCalled();
consumer->DidReceiveResponse(
0, ResourceResponse(),
DataConsumerHandleTestUtil::CreateWaitingDataConsumerHandle());
EXPECT_EQ(num_on_state_change_called + 1, client->NumOnStateChangeCalled());
EXPECT_EQ(Result::kShouldWait, consumer->BeginRead(&buffer, &available));
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
consumer->DidFail(ResourceError::Failure(NullURL()));
EXPECT_EQ(num_on_state_change_called + 2, client->NumOnStateChangeCalled());
EXPECT_EQ(PublicState::kErrored, consumer->GetPublicState());
EXPECT_EQ(Result::kError, consumer->BeginRead(&buffer, &available));
}
TEST_F(BlobBytesConsumerTest, FailAccessControlCheck) {
scoped_refptr<BlobDataHandle> blob_data_handle =
BlobDataHandle::Create(BlobData::Create(), 12345);
TestThreadableLoader* loader = new TestThreadableLoader();
BlobBytesConsumer* consumer = BlobBytesConsumer::CreateForTesting(
&GetDocument(), blob_data_handle, loader);
BlobBytesConsumerTestClient* client = new BlobBytesConsumerTestClient();
consumer->SetClient(client);
const char* buffer = nullptr;
size_t available;
EXPECT_EQ(Result::kShouldWait, consumer->BeginRead(&buffer, &available));
EXPECT_TRUE(loader->IsStarted());
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
int num_on_state_change_called = client->NumOnStateChangeCalled();
consumer->DidFail(ResourceError::Failure(NullURL()));
EXPECT_EQ(num_on_state_change_called + 1, client->NumOnStateChangeCalled());
EXPECT_EQ(PublicState::kErrored, consumer->GetPublicState());
EXPECT_EQ(Result::kError, consumer->BeginRead(&buffer, &available));
}
TEST_F(BlobBytesConsumerTest, CancelBeforeStarting) {
scoped_refptr<BlobDataHandle> blob_data_handle =
BlobDataHandle::Create(BlobData::Create(), 12345);
TestThreadableLoader* loader = new TestThreadableLoader();
BlobBytesConsumer* consumer = BlobBytesConsumer::CreateForTesting(
&GetDocument(), blob_data_handle, loader);
scoped_refptr<BlobDataHandle> blob_data_handle = CreateBlob("foo bar");
BlobBytesConsumer* consumer =
new BlobBytesConsumer(&GetDocument(), blob_data_handle);
BlobBytesConsumerTestClient* client = new BlobBytesConsumerTestClient();
consumer->SetClient(client);
consumer->Cancel();
// This should be FALSE in production, but TRUE here because we set the
// loader before starting loading in tests.
EXPECT_TRUE(loader->IsCancelled());
const char* buffer = nullptr;
size_t available;
EXPECT_EQ(Result::kDone, consumer->BeginRead(&buffer, &available));
EXPECT_EQ(PublicState::kClosed, consumer->GetPublicState());
EXPECT_FALSE(loader->IsStarted());
EXPECT_FALSE(DidStartLoading());
EXPECT_EQ(0, client->NumOnStateChangeCalled());
}
TEST_F(BlobBytesConsumerTest, CancelAfterStarting) {
scoped_refptr<BlobDataHandle> blob_data_handle =
BlobDataHandle::Create(BlobData::Create(), 12345);
TestThreadableLoader* loader = new TestThreadableLoader();
BlobBytesConsumer* consumer = BlobBytesConsumer::CreateForTesting(
&GetDocument(), blob_data_handle, loader);
scoped_refptr<BlobDataHandle> blob_data_handle = CreateBlob("foo bar");
BlobBytesConsumer* consumer =
new BlobBytesConsumer(&GetDocument(), blob_data_handle);
BlobBytesConsumerTestClient* client = new BlobBytesConsumerTestClient();
consumer->SetClient(client);
......@@ -273,130 +116,43 @@ TEST_F(BlobBytesConsumerTest, CancelAfterStarting) {
size_t available;
EXPECT_EQ(Result::kShouldWait, consumer->BeginRead(&buffer, &available));
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
EXPECT_TRUE(loader->IsStarted());
EXPECT_EQ(0, client->NumOnStateChangeCalled());
consumer->Cancel();
EXPECT_TRUE(loader->IsCancelled());
EXPECT_EQ(PublicState::kClosed, consumer->GetPublicState());
EXPECT_EQ(Result::kDone, consumer->BeginRead(&buffer, &available));
EXPECT_EQ(0, client->NumOnStateChangeCalled());
}
TEST_F(BlobBytesConsumerTest, ReadLastChunkBeforeDidFinishLoadingArrives) {
scoped_refptr<BlobDataHandle> blob_data_handle =
BlobDataHandle::Create(BlobData::Create(), 12345);
TestThreadableLoader* loader = new TestThreadableLoader();
BlobBytesConsumer* consumer = BlobBytesConsumer::CreateForTesting(
&GetDocument(), blob_data_handle, loader);
BlobBytesConsumerTestClient* client = new BlobBytesConsumerTestClient();
consumer->SetClient(client);
std::unique_ptr<ReplayingHandle> src = ReplayingHandle::Create();
src->Add(DataConsumerCommand(DataConsumerCommand::kData, "hello"));
src->Add(DataConsumerCommand(DataConsumerCommand::kDone));
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
EXPECT_FALSE(loader->IsStarted());
const char* buffer = nullptr;
size_t available;
EXPECT_EQ(Result::kShouldWait, consumer->BeginRead(&buffer, &available));
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
EXPECT_TRUE(loader->IsStarted());
EXPECT_EQ(0, client->NumOnStateChangeCalled());
consumer->DidReceiveResponse(0, ResourceResponse(), std::move(src));
EXPECT_EQ(1, client->NumOnStateChangeCalled());
test::RunPendingTasks();
EXPECT_EQ(2, client->NumOnStateChangeCalled());
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
ASSERT_EQ(Result::kOk, consumer->BeginRead(&buffer, &available));
ASSERT_EQ(5u, available);
EXPECT_EQ("hello", String(buffer, available));
ASSERT_EQ(Result::kOk, consumer->EndRead(available));
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
ASSERT_EQ(Result::kShouldWait, consumer->BeginRead(&buffer, &available));
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
consumer->DidFinishLoading(0);
EXPECT_EQ(3, client->NumOnStateChangeCalled());
EXPECT_EQ(PublicState::kClosed, consumer->GetPublicState());
ASSERT_EQ(Result::kDone, consumer->BeginRead(&buffer, &available));
}
TEST_F(BlobBytesConsumerTest, ReadLastChunkAfterDidFinishLoadingArrives) {
scoped_refptr<BlobDataHandle> blob_data_handle =
BlobDataHandle::Create(BlobData::Create(), 12345);
TestThreadableLoader* loader = new TestThreadableLoader();
BlobBytesConsumer* consumer = BlobBytesConsumer::CreateForTesting(
&GetDocument(), blob_data_handle, loader);
BlobBytesConsumerTestClient* client = new BlobBytesConsumerTestClient();
consumer->SetClient(client);
std::unique_ptr<ReplayingHandle> src = ReplayingHandle::Create();
src->Add(DataConsumerCommand(DataConsumerCommand::kData, "hello"));
src->Add(DataConsumerCommand(DataConsumerCommand::kDone));
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
EXPECT_FALSE(loader->IsStarted());
const char* buffer = nullptr;
size_t available;
EXPECT_EQ(Result::kShouldWait, consumer->BeginRead(&buffer, &available));
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
EXPECT_TRUE(loader->IsStarted());
EXPECT_EQ(0, client->NumOnStateChangeCalled());
consumer->DidReceiveResponse(0, ResourceResponse(), std::move(src));
EXPECT_EQ(1, client->NumOnStateChangeCalled());
test::RunPendingTasks();
EXPECT_EQ(2, client->NumOnStateChangeCalled());
consumer->DidFinishLoading(0);
test::RunPendingTasks();
EXPECT_EQ(2, client->NumOnStateChangeCalled());
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
ASSERT_EQ(Result::kOk, consumer->BeginRead(&buffer, &available));
ASSERT_EQ(5u, available);
EXPECT_EQ("hello", String(buffer, available));
ASSERT_EQ(Result::kOk, consumer->EndRead(available));
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
EXPECT_EQ(Result::kDone, consumer->BeginRead(&buffer, &available));
EXPECT_EQ(2, client->NumOnStateChangeCalled());
EXPECT_TRUE(DidStartLoading());
}
TEST_F(BlobBytesConsumerTest, DrainAsBlobDataHandle) {
scoped_refptr<BlobDataHandle> blob_data_handle =
BlobDataHandle::Create(BlobData::Create(), 12345);
TestThreadableLoader* loader = new TestThreadableLoader();
BlobBytesConsumer* consumer = BlobBytesConsumer::CreateForTesting(
&GetDocument(), blob_data_handle, loader);
String body = "hello, world";
scoped_refptr<BlobDataHandle> blob_data_handle = CreateBlob(body);
BlobBytesConsumer* consumer =
new BlobBytesConsumer(&GetDocument(), blob_data_handle);
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
EXPECT_FALSE(loader->IsStarted());
EXPECT_FALSE(DidStartLoading());
scoped_refptr<BlobDataHandle> result = consumer->DrainAsBlobDataHandle(
BytesConsumer::BlobSizePolicy::kDisallowBlobWithInvalidSize);
ASSERT_TRUE(result);
EXPECT_FALSE(consumer->DrainAsBlobDataHandle(
BytesConsumer::BlobSizePolicy::kDisallowBlobWithInvalidSize));
EXPECT_EQ(12345u, result->size());
EXPECT_EQ(body.length(), result->size());
EXPECT_EQ(PublicState::kClosed, consumer->GetPublicState());
EXPECT_FALSE(loader->IsStarted());
EXPECT_FALSE(DidStartLoading());
}
TEST_F(BlobBytesConsumerTest, DrainAsBlobDataHandle_2) {
scoped_refptr<BlobDataHandle> blob_data_handle =
BlobDataHandle::Create(BlobData::Create(), -1);
TestThreadableLoader* loader = new TestThreadableLoader();
BlobBytesConsumer* consumer = BlobBytesConsumer::CreateForTesting(
&GetDocument(), blob_data_handle, loader);
scoped_refptr<BlobDataHandle> blob_data_handle = BlobDataHandle::Create(
"uuid", "", -1, CreateBlob("foo bar")->CloneBlobPtr().PassInterface());
;
BlobBytesConsumer* consumer =
new BlobBytesConsumer(&GetDocument(), blob_data_handle);
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
EXPECT_FALSE(loader->IsStarted());
EXPECT_FALSE(DidStartLoading());
scoped_refptr<BlobDataHandle> result = consumer->DrainAsBlobDataHandle(
BytesConsumer::BlobSizePolicy::kAllowBlobWithInvalidSize);
......@@ -405,109 +161,44 @@ TEST_F(BlobBytesConsumerTest, DrainAsBlobDataHandle_2) {
BytesConsumer::BlobSizePolicy::kAllowBlobWithInvalidSize));
EXPECT_EQ(UINT64_MAX, result->size());
EXPECT_EQ(PublicState::kClosed, consumer->GetPublicState());
EXPECT_FALSE(loader->IsStarted());
EXPECT_FALSE(DidStartLoading());
}
TEST_F(BlobBytesConsumerTest, DrainAsBlobDataHandle_3) {
scoped_refptr<BlobDataHandle> blob_data_handle =
BlobDataHandle::Create(BlobData::Create(), -1);
TestThreadableLoader* loader = new TestThreadableLoader();
BlobBytesConsumer* consumer = BlobBytesConsumer::CreateForTesting(
&GetDocument(), blob_data_handle, loader);
scoped_refptr<BlobDataHandle> blob_data_handle = BlobDataHandle::Create(
"uuid", "", -1, CreateBlob("foo bar")->CloneBlobPtr().PassInterface());
;
BlobBytesConsumer* consumer =
new BlobBytesConsumer(&GetDocument(), blob_data_handle);
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
EXPECT_FALSE(loader->IsStarted());
EXPECT_FALSE(DidStartLoading());
EXPECT_FALSE(consumer->DrainAsBlobDataHandle(
BytesConsumer::BlobSizePolicy::kDisallowBlobWithInvalidSize));
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
EXPECT_FALSE(loader->IsStarted());
EXPECT_FALSE(DidStartLoading());
}
TEST_F(BlobBytesConsumerTest, DrainAsFormData) {
scoped_refptr<BlobDataHandle> blob_data_handle =
BlobDataHandle::Create(BlobData::Create(), 12345);
TestThreadableLoader* loader = new TestThreadableLoader();
BlobBytesConsumer* consumer = BlobBytesConsumer::CreateForTesting(
&GetDocument(), blob_data_handle, loader);
String body = "hello, world";
scoped_refptr<BlobDataHandle> blob_data_handle = CreateBlob(body);
BlobBytesConsumer* consumer =
new BlobBytesConsumer(&GetDocument(), blob_data_handle);
EXPECT_EQ(PublicState::kReadableOrWaiting, consumer->GetPublicState());
EXPECT_FALSE(loader->IsStarted());
EXPECT_FALSE(DidStartLoading());
scoped_refptr<EncodedFormData> result = consumer->DrainAsFormData();
ASSERT_TRUE(result);
ASSERT_EQ(1u, result->Elements().size());
ASSERT_EQ(FormDataElement::kEncodedBlob, result->Elements()[0].type_);
ASSERT_TRUE(result->Elements()[0].optional_blob_data_handle_);
EXPECT_EQ(12345u, result->Elements()[0].optional_blob_data_handle_->size());
EXPECT_EQ(body.length(),
result->Elements()[0].optional_blob_data_handle_->size());
EXPECT_EQ(blob_data_handle->Uuid(), result->Elements()[0].blob_uuid_);
EXPECT_EQ(PublicState::kClosed, consumer->GetPublicState());
EXPECT_FALSE(loader->IsStarted());
}
TEST_F(BlobBytesConsumerTest, LoaderShouldBeCancelled) {
{
scoped_refptr<BlobDataHandle> blob_data_handle =
BlobDataHandle::Create(BlobData::Create(), 12345);
TestThreadableLoader* loader = new TestThreadableLoader();
BlobBytesConsumer* consumer = BlobBytesConsumer::CreateForTesting(
&GetDocument(), blob_data_handle, loader);
const char* buffer = nullptr;
size_t available;
EXPECT_EQ(Result::kShouldWait, consumer->BeginRead(&buffer, &available));
EXPECT_TRUE(loader->IsStarted());
loader->SetShouldBeCancelled();
}
ThreadState::Current()->CollectAllGarbage();
}
TEST_F(BlobBytesConsumerTest, SyncErrorDispatch) {
scoped_refptr<BlobDataHandle> blob_data_handle =
BlobDataHandle::Create(BlobData::Create(), 12345);
SyncErrorTestThreadableLoader* loader = new SyncErrorTestThreadableLoader();
BlobBytesConsumer* consumer = BlobBytesConsumer::CreateForTesting(
&GetDocument(), blob_data_handle, loader);
loader->SetClient(consumer);
BlobBytesConsumerTestClient* client = new BlobBytesConsumerTestClient();
consumer->SetClient(client);
const char* buffer = nullptr;
size_t available;
EXPECT_EQ(Result::kError, consumer->BeginRead(&buffer, &available));
EXPECT_TRUE(loader->IsStarted());
EXPECT_EQ(0, client->NumOnStateChangeCalled());
EXPECT_EQ(BytesConsumer::PublicState::kErrored, consumer->GetPublicState());
}
TEST_F(BlobBytesConsumerTest, SyncLoading) {
scoped_refptr<BlobDataHandle> blob_data_handle =
BlobDataHandle::Create(BlobData::Create(), 12345);
SyncLoadingTestThreadableLoader* loader =
new SyncLoadingTestThreadableLoader();
BlobBytesConsumer* consumer = BlobBytesConsumer::CreateForTesting(
&GetDocument(), blob_data_handle, loader);
std::unique_ptr<ReplayingHandle> src = ReplayingHandle::Create();
src->Add(DataConsumerCommand(DataConsumerCommand::kData, "hello, "));
src->Add(DataConsumerCommand(DataConsumerCommand::kWait));
src->Add(DataConsumerCommand(DataConsumerCommand::kData, "world"));
src->Add(DataConsumerCommand(DataConsumerCommand::kDone));
loader->SetClient(consumer);
loader->SetHandle(std::move(src));
BlobBytesConsumerTestClient* client = new BlobBytesConsumerTestClient();
consumer->SetClient(client);
const char* buffer = nullptr;
size_t available;
ASSERT_EQ(Result::kOk, consumer->BeginRead(&buffer, &available));
EXPECT_TRUE(loader->IsStarted());
ASSERT_EQ(7u, available);
EXPECT_EQ("hello, ", String(buffer, available));
EXPECT_EQ(0, client->NumOnStateChangeCalled());
EXPECT_EQ(BytesConsumer::PublicState::kReadableOrWaiting,
consumer->GetPublicState());
EXPECT_FALSE(DidStartLoading());
}
TEST_F(BlobBytesConsumerTest, ConstructedFromNullHandle) {
......
......@@ -5,6 +5,7 @@
#include "third_party/blink/renderer/core/fetch/form_data_bytes_consumer.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/fetch/blob_bytes_consumer.h"
#include "third_party/blink/renderer/core/fetch/bytes_consumer_for_data_consumer_handle.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
......@@ -319,7 +320,6 @@ class DataPipeAndDataBytesConsumer final : public BytesConsumer {
case PublicState::kErrored:
return;
case PublicState::kClosed:
NOTREACHED();
return;
case PublicState::kReadableOrWaiting:
break;
......
......@@ -362,6 +362,18 @@ BlobPtr BlobDataHandle::CloneBlobPtr() {
return blob_clone;
}
network::mojom::blink::DataPipeGetterPtr BlobDataHandle::AsDataPipeGetter() {
MutexLocker locker(blob_info_mutex_);
if (!blob_info_.is_valid())
return nullptr;
network::mojom::blink::DataPipeGetterPtr result;
BlobPtr blob;
blob.Bind(std::move(blob_info_));
blob->AsDataPipeGetter(MakeRequest(&result));
blob_info_ = blob.PassInterface();
return result;
}
void BlobDataHandle::ReadAll(mojo::ScopedDataPipeProducerHandle pipe,
mojom::blink::BlobReaderClientPtr client) {
MutexLocker locker(blob_info_mutex_);
......
......@@ -192,6 +192,7 @@ class PLATFORM_EXPORT BlobDataHandle
~BlobDataHandle();
mojom::blink::BlobPtr CloneBlobPtr();
network::mojom::blink::DataPipeGetterPtr AsDataPipeGetter();
void ReadAll(mojo::ScopedDataPipeProducerHandle,
mojom::blink::BlobReaderClientPtr);
......
......@@ -38,7 +38,6 @@
namespace blink {
class BlobBytesConsumer;
class BlobDataHandle;
class BlobURLRegistry;
class KURL;
......@@ -49,12 +48,11 @@ class PLATFORM_EXPORT BlobRegistry {
STATIC_ONLY(BlobRegistry);
// Calling methods in this class directly won't work when Blob URL management
// is switched to mojo. Instead codew should call PublicURLManager methods to
// is switched to mojo. Instead code should call PublicURLManager methods to
// create/revoke blob URLs.
// To avoid new usage of these methods, mark all as private with friends for
// existing usage.
private:
friend class BlobBytesConsumer;
friend class BlobURLRegistry;
// Methods for controlling Blob URLs.
......
......@@ -5,19 +5,52 @@
#include "third_party/blink/renderer/platform/blob/testing/fake_blob.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "mojo/public/cpp/system/data_pipe_utils.h"
#include "third_party/blink/public/platform/web_string.h"
namespace blink {
namespace {
FakeBlob::FakeBlob(const String& uuid) : uuid_(uuid) {}
class SimpleDataPipeGetter : public network::mojom::blink::DataPipeGetter {
public:
SimpleDataPipeGetter(const String& str) : str_(str) {}
~SimpleDataPipeGetter() override = default;
// network::mojom::DataPipeGetter implementation:
void Read(mojo::ScopedDataPipeProducerHandle handle,
ReadCallback callback) override {
std::move(callback).Run(0 /* OK */, str_.length());
bool result = mojo::BlockingCopyFromString(WebString(str_).Utf8(), handle);
DCHECK(result);
}
void Clone(network::mojom::blink::DataPipeGetterRequest request) override {
mojo::MakeStrongBinding(std::make_unique<SimpleDataPipeGetter>(str_),
std::move(request));
}
private:
String str_;
DISALLOW_COPY_AND_ASSIGN(SimpleDataPipeGetter);
};
} // namespace
FakeBlob::FakeBlob(const String& uuid, const String& body, State* state)
: uuid_(uuid), body_(body), state_(state) {}
void FakeBlob::Clone(mojom::blink::BlobRequest request) {
mojo::MakeStrongBinding(std::make_unique<FakeBlob>(uuid_),
mojo::MakeStrongBinding(std::make_unique<FakeBlob>(uuid_, body_, state_),
std::move(request));
}
void FakeBlob::AsDataPipeGetter(
network::mojom::blink::DataPipeGetterRequest request) {
NOTREACHED();
if (state_)
state_->did_initiate_read_operation = true;
mojo::MakeStrongBinding(std::make_unique<SimpleDataPipeGetter>(body_),
std::move(request));
}
void FakeBlob::ReadRange(uint64_t offset,
......@@ -27,9 +60,16 @@ void FakeBlob::ReadRange(uint64_t offset,
NOTREACHED();
}
void FakeBlob::ReadAll(mojo::ScopedDataPipeProducerHandle,
mojom::blink::BlobReaderClientPtr) {
NOTREACHED();
void FakeBlob::ReadAll(mojo::ScopedDataPipeProducerHandle handle,
mojom::blink::BlobReaderClientPtr client) {
if (state_)
state_->did_initiate_read_operation = true;
if (client)
client->OnCalculatedSize(body_.length(), body_.length());
bool result = mojo::BlockingCopyFromString(WebString(body_).Utf8(), handle);
DCHECK(result);
if (client)
client->OnComplete(0 /* OK */, body_.length());
}
void FakeBlob::ReadSideData(ReadSideDataCallback callback) {
......
......@@ -9,11 +9,17 @@
namespace blink {
// Mocked Blob implementation for testing. You can't read from a FakeBlob, but
// it does have a UUID.
// Mocked Blob implementation for testing. Implements all methods except for
// ReadRange and ReadSideData.
class FakeBlob : public mojom::blink::Blob {
public:
explicit FakeBlob(const String& uuid);
struct State {
bool did_initiate_read_operation = false;
};
FakeBlob(const String& uuid,
const String& body = String(),
State* state = nullptr);
void Clone(mojom::blink::BlobRequest) override;
void AsDataPipeGetter(network::mojom::blink::DataPipeGetterRequest) override;
......@@ -25,9 +31,10 @@ class FakeBlob : public mojom::blink::Blob {
mojom::blink::BlobReaderClientPtr) override;
void ReadSideData(ReadSideDataCallback) override;
void GetInternalUUID(GetInternalUUIDCallback) override;
private:
String uuid_;
String body_;
State* state_;
};
} // namespace blink
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment