blob: ffca6d44b45becc8e0828755e099c73220a0b90d [file] [log] [blame]
// Copyright 2016 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 "content/browser/loader/mojo_async_resource_handler.h"
#include <string.h>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_vector.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "content/browser/loader/resource_dispatcher_host_impl.h"
#include "content/browser/loader/resource_request_info_impl.h"
#include "content/browser/loader/test_url_loader_client.h"
#include "content/common/resource_request_completion_status.h"
#include "content/common/url_loader.mojom.h"
#include "content/public/browser/appcache_service.h"
#include "content/public/browser/navigation_data.h"
#include "content/public/browser/resource_context.h"
#include "content/public/browser/resource_controller.h"
#include "content/public/browser/resource_dispatcher_host_delegate.h"
#include "content/public/browser/resource_throttle.h"
#include "content/public/browser/stream_info.h"
#include "content/public/common/resource_response.h"
#include "content/public/common/resource_type.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "mojo/public/c/system/data_pipe.h"
#include "mojo/public/c/system/types.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "net/base/auth.h"
#include "net/base/net_errors.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_response_info.h"
#include "net/http/http_status_code.h"
#include "net/http/http_util.h"
#include "net/ssl/client_cert_store.h"
#include "net/test/url_request/url_request_failed_job.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_filter.h"
#include "net/url_request/url_request_status.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/page_transition_types.h"
namespace content {
namespace {
constexpr int kSizeMimeSnifferRequiresForFirstOnWillRead = 2048;
class TestResourceDispatcherHostDelegate final
: public ResourceDispatcherHostDelegate {
public:
TestResourceDispatcherHostDelegate() = default;
~TestResourceDispatcherHostDelegate() override {
EXPECT_EQ(num_on_response_started_calls_expectation_,
num_on_response_started_calls_);
}
bool ShouldBeginRequest(const std::string& method,
const GURL& url,
ResourceType resource_type,
ResourceContext* resource_context) override {
ADD_FAILURE() << "ShouldBeginRequest should not be called.";
return false;
}
void RequestBeginning(net::URLRequest* request,
ResourceContext* resource_context,
AppCacheService* appcache_service,
ResourceType resource_type,
ScopedVector<ResourceThrottle>* throttles) override {
ADD_FAILURE() << "RequestBeginning should not be called.";
}
void DownloadStarting(net::URLRequest* request,
ResourceContext* resource_context,
int child_id,
int route_id,
bool is_content_initiated,
bool must_download,
ScopedVector<ResourceThrottle>* throttles) override {
ADD_FAILURE() << "DownloadStarting should not be called.";
}
ResourceDispatcherHostLoginDelegate* CreateLoginDelegate(
net::AuthChallengeInfo* auth_info,
net::URLRequest* request) override {
ADD_FAILURE() << "CreateLoginDelegate should not be called.";
return nullptr;
}
bool HandleExternalProtocol(
const GURL& url,
int child_id,
const ResourceRequestInfo::WebContentsGetter& web_contents_getter,
bool is_main_frame,
ui::PageTransition page_transition,
bool has_user_gesture,
ResourceContext* resource_context) override {
ADD_FAILURE() << "HandleExternalProtocol should not be called.";
return false;
}
bool ShouldForceDownloadResource(const GURL& url,
const std::string& mime_type) override {
ADD_FAILURE() << "ShouldForceDownloadResource should not be called.";
return false;
}
bool ShouldInterceptResourceAsStream(net::URLRequest* request,
const base::FilePath& plugin_path,
const std::string& mime_type,
GURL* origin,
std::string* payload) override {
ADD_FAILURE() << "ShouldInterceptResourceAsStream should not be called.";
return false;
}
void OnStreamCreated(net::URLRequest* request,
std::unique_ptr<content::StreamInfo> stream) override {
ADD_FAILURE() << "OnStreamCreated should not be called.";
}
void OnResponseStarted(net::URLRequest* request,
ResourceContext* resource_context,
ResourceResponse* response) override {
++num_on_response_started_calls_;
}
void OnRequestRedirected(const GURL& redirect_url,
net::URLRequest* request,
ResourceContext* resource_context,
ResourceResponse* response) override {
ADD_FAILURE() << "OnRequestRedirected should not be called.";
}
void RequestComplete(net::URLRequest* url_request) override {
ADD_FAILURE() << "RequestComplete should not be called.";
}
bool ShouldEnableLoFiMode(
const net::URLRequest& url_request,
content::ResourceContext* resource_context) override {
ADD_FAILURE() << "ShouldEnableLoFiMode should not be called.";
return false;
}
NavigationData* GetNavigationData(net::URLRequest* request) const override {
ADD_FAILURE() << "GetNavigationData should not be called.";
return nullptr;
}
std::unique_ptr<net::ClientCertStore> CreateClientCertStore(
ResourceContext* resource_context) override {
ADD_FAILURE() << "CreateClientCertStore should not be called.";
return nullptr;
}
int num_on_response_started_calls() const {
return num_on_response_started_calls_;
}
void set_num_on_response_started_calls_expectation(int expectation) {
num_on_response_started_calls_expectation_ = expectation;
}
private:
int num_on_response_started_calls_ = 0;
int num_on_response_started_calls_expectation_ = 0;
DISALLOW_COPY_AND_ASSIGN(TestResourceDispatcherHostDelegate);
};
class TestResourceController : public ResourceController {
public:
TestResourceController() {}
~TestResourceController() override {}
void Cancel() override { ADD_FAILURE() << "Cancel should not be called."; }
void CancelAndIgnore() override {
ADD_FAILURE() << "CancelAndIgnore should not be called.";
}
void CancelWithError(int error_code) override {
is_cancel_with_error_called_ = true;
error_ = error_code;
if (quit_closure_)
quit_closure_.Run();
}
void Resume() override { ++num_resume_calls_; }
void RunUntilCancelWithErrorCalled() {
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
void set_quit_closure(const base::Closure& quit_closure) {
quit_closure_ = quit_closure;
}
bool is_cancel_with_error_called() const {
return is_cancel_with_error_called_;
}
int error() const { return error_; }
int num_resume_calls() const { return num_resume_calls_; }
private:
bool is_cancel_with_error_called_ = false;
int error_ = net::OK;
int num_resume_calls_ = 0;
base::Closure quit_closure_;
DISALLOW_COPY_AND_ASSIGN(TestResourceController);
};
class MojoAsyncResourceHandlerWithCustomDataPipeOperations
: public MojoAsyncResourceHandler {
public:
MojoAsyncResourceHandlerWithCustomDataPipeOperations(
net::URLRequest* request,
ResourceDispatcherHostImpl* rdh,
mojo::InterfaceRequest<mojom::URLLoader> mojo_request,
mojom::URLLoaderClientPtr url_loader_client)
: MojoAsyncResourceHandler(request,
rdh,
std::move(mojo_request),
std::move(url_loader_client)) {}
~MojoAsyncResourceHandlerWithCustomDataPipeOperations() override {}
void ResetBeginWriteExpectation() { is_begin_write_expectation_set_ = false; }
void set_begin_write_expectation(MojoResult begin_write_expectation) {
is_begin_write_expectation_set_ = true;
begin_write_expectation_ = begin_write_expectation;
}
void set_end_write_expectation(MojoResult end_write_expectation) {
is_end_write_expectation_set_ = true;
end_write_expectation_ = end_write_expectation;
}
private:
MojoResult BeginWrite(void** data, uint32_t* available) override {
if (is_begin_write_expectation_set_)
return begin_write_expectation_;
return MojoAsyncResourceHandler::BeginWrite(data, available);
}
MojoResult EndWrite(uint32_t written) override {
if (is_end_write_expectation_set_)
return end_write_expectation_;
return MojoAsyncResourceHandler::EndWrite(written);
}
bool is_begin_write_expectation_set_ = false;
bool is_end_write_expectation_set_ = false;
MojoResult begin_write_expectation_ = MOJO_RESULT_UNKNOWN;
MojoResult end_write_expectation_ = MOJO_RESULT_UNKNOWN;
DISALLOW_COPY_AND_ASSIGN(
MojoAsyncResourceHandlerWithCustomDataPipeOperations);
};
class MojoAsyncResourceHandlerTestBase {
public:
MojoAsyncResourceHandlerTestBase()
: thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP),
browser_context_(new TestBrowserContext()) {
MojoAsyncResourceHandler::SetAllocationSizeForTesting(32 * 1024);
rdh_.SetDelegate(&rdh_delegate_);
url_request_delegate_.reset(new net::TestDelegate());
net::URLRequestContext* request_context =
browser_context_->GetResourceContext()->GetRequestContext();
request_ = request_context->CreateRequest(
net::URLRequestFailedJob::GetMockHttpUrl(net::ERR_TIMED_OUT),
net::DEFAULT_PRIORITY, url_request_delegate_.get());
ResourceRequestInfo::AllocateForTesting(
request_.get(), // request
RESOURCE_TYPE_XHR, // resource_type
browser_context_->GetResourceContext(), // context
2, // render_process_id
0, // render_view_id
0, // render_frame_id
true, // is_main_frame
false, // parent_is_main_frame
false, // allow_download
true, // is_async
false // is_using_lofi
);
handler_.reset(new MojoAsyncResourceHandlerWithCustomDataPipeOperations(
request_.get(), &rdh_, nullptr,
url_loader_client_.CreateInterfacePtrAndBind()));
handler_->SetController(&resource_controller_);
}
virtual ~MojoAsyncResourceHandlerTestBase() {
net::URLRequestFilter::GetInstance()->ClearHandlers();
MojoAsyncResourceHandler::SetAllocationSizeForTesting(
MojoAsyncResourceHandler::kDefaultAllocationSize);
base::RunLoop().RunUntilIdle();
}
// Returns false if something bad happens.
bool CallOnWillStart() {
bool defer = false;
if (!handler_->OnWillStart(request_->url(), &defer)) {
ADD_FAILURE() << "OnWillStart returns false.";
return false;
}
if (defer) {
ADD_FAILURE() << "OnWillStart sets |defer| true.";
return false;
}
return true;
}
// Returns false if something bad happens.
bool CallOnWillStartAndOnResponseStarted() {
rdh_delegate_.set_num_on_response_started_calls_expectation(1);
if (!CallOnWillStart())
return false;
scoped_refptr<ResourceResponse> response = new ResourceResponse();
bool defer = false;
if (!handler_->OnResponseStarted(response.get(), &defer)) {
ADD_FAILURE() << "OnResponseStarted returns false.";
return false;
}
if (defer) {
ADD_FAILURE() << "OnResponseStarted sets |defer| true.";
return false;
}
if (url_loader_client_.has_received_response()) {
ADD_FAILURE() << "URLLoaderClient unexpectedly gets a response.";
return false;
}
url_loader_client_.RunUntilResponseReceived();
return true;
}
TestBrowserThreadBundle thread_bundle_;
TestResourceDispatcherHostDelegate rdh_delegate_;
ResourceDispatcherHostImpl rdh_;
TestURLLoaderClient url_loader_client_;
TestResourceController resource_controller_;
std::unique_ptr<TestBrowserContext> browser_context_;
std::unique_ptr<net::TestDelegate> url_request_delegate_;
std::unique_ptr<net::URLRequest> request_;
std::unique_ptr<MojoAsyncResourceHandlerWithCustomDataPipeOperations>
handler_;
DISALLOW_COPY_AND_ASSIGN(MojoAsyncResourceHandlerTestBase);
};
class MojoAsyncResourceHandlerTest : public MojoAsyncResourceHandlerTestBase,
public ::testing::Test {};
// This test class is parameterized with MojoAsyncResourceHandler's allocation
// size.
class MojoAsyncResourceHandlerWithAllocationSizeTest
: public MojoAsyncResourceHandlerTestBase,
public ::testing::TestWithParam<size_t> {
protected:
MojoAsyncResourceHandlerWithAllocationSizeTest() {
MojoAsyncResourceHandler::SetAllocationSizeForTesting(GetParam());
}
};
TEST_F(MojoAsyncResourceHandlerTest, InFlightRequests) {
EXPECT_EQ(0, rdh_.num_in_flight_requests_for_testing());
handler_ = nullptr;
EXPECT_EQ(0, rdh_.num_in_flight_requests_for_testing());
}
TEST_F(MojoAsyncResourceHandlerTest, OnWillStart) {
bool defer = false;
EXPECT_TRUE(handler_->OnWillStart(request_->url(), &defer));
EXPECT_FALSE(defer);
}
TEST_F(MojoAsyncResourceHandlerTest, OnResponseStarted) {
rdh_delegate_.set_num_on_response_started_calls_expectation(1);
ASSERT_TRUE(CallOnWillStart());
scoped_refptr<ResourceResponse> response = new ResourceResponse();
response->head.content_length = 99;
response->head.request_start =
base::TimeTicks::UnixEpoch() + base::TimeDelta::FromDays(14);
response->head.response_start =
base::TimeTicks::UnixEpoch() + base::TimeDelta::FromDays(28);
bool defer = false;
EXPECT_EQ(0, rdh_delegate_.num_on_response_started_calls());
base::TimeTicks now1 = base::TimeTicks::Now();
ASSERT_TRUE(handler_->OnResponseStarted(response.get(), &defer));
base::TimeTicks now2 = base::TimeTicks::Now();
EXPECT_FALSE(defer);
EXPECT_EQ(request_->creation_time(), response->head.request_start);
EXPECT_LE(now1, response->head.response_start);
EXPECT_LE(response->head.response_start, now2);
EXPECT_EQ(1, rdh_delegate_.num_on_response_started_calls());
url_loader_client_.RunUntilResponseReceived();
EXPECT_EQ(response->head.request_start,
url_loader_client_.response_head().request_start);
EXPECT_EQ(response->head.response_start,
url_loader_client_.response_head().response_start);
EXPECT_EQ(99, url_loader_client_.response_head().content_length);
}
TEST_F(MojoAsyncResourceHandlerTest, OnWillReadAndInFlightRequests) {
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
EXPECT_EQ(0, rdh_.num_in_flight_requests_for_testing());
scoped_refptr<net::IOBuffer> io_buffer;
int io_buffer_size = 0;
EXPECT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
EXPECT_EQ(1, rdh_.num_in_flight_requests_for_testing());
handler_ = nullptr;
EXPECT_EQ(0, rdh_.num_in_flight_requests_for_testing());
}
TEST_F(MojoAsyncResourceHandlerTest, OnWillReadWithInsufficientResource) {
rdh_.set_max_num_in_flight_requests_per_process(0);
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
scoped_refptr<net::IOBuffer> io_buffer;
int io_buffer_size = 0;
EXPECT_FALSE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
EXPECT_FALSE(io_buffer);
EXPECT_EQ(0, io_buffer_size);
EXPECT_EQ(1, rdh_.num_in_flight_requests_for_testing());
EXPECT_TRUE(resource_controller_.is_cancel_with_error_called());
EXPECT_EQ(net::ERR_INSUFFICIENT_RESOURCES, resource_controller_.error());
handler_ = nullptr;
EXPECT_EQ(0, rdh_.num_in_flight_requests_for_testing());
}
TEST_F(MojoAsyncResourceHandlerTest, OnWillReadAndOnReadCompleted) {
bool defer = false;
scoped_refptr<net::IOBuffer> io_buffer;
int io_buffer_size = 0;
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
ASSERT_TRUE(io_buffer);
// The buffer size that the mime sniffer requires implicitly.
ASSERT_GE(io_buffer_size, kSizeMimeSnifferRequiresForFirstOnWillRead);
url_loader_client_.RunUntilResponseBodyArrived();
ASSERT_TRUE(url_loader_client_.response_body().is_valid());
io_buffer->data()[0] = 'A';
io_buffer->data()[1] = 'B';
ASSERT_TRUE(handler_->OnReadCompleted(2, &defer));
EXPECT_FALSE(defer);
std::string contents;
while (contents.size() < 2) {
char buffer[16];
uint32_t read_size = sizeof(buffer);
MojoResult result =
mojo::ReadDataRaw(url_loader_client_.response_body(), buffer,
&read_size, MOJO_READ_DATA_FLAG_NONE);
if (result == MOJO_RESULT_SHOULD_WAIT) {
base::RunLoop().RunUntilIdle();
continue;
}
contents.append(buffer, read_size);
}
EXPECT_EQ("AB", contents);
}
TEST_F(MojoAsyncResourceHandlerTest,
OnWillReadAndOnReadCompletedWithInsufficientInitialCapacity) {
MojoAsyncResourceHandler::SetAllocationSizeForTesting(2);
bool defer = false;
scoped_refptr<net::IOBuffer> io_buffer;
int io_buffer_size = 0;
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
ASSERT_TRUE(io_buffer);
// The buffer size that the mime sniffer requires implicitly.
ASSERT_GE(io_buffer_size, kSizeMimeSnifferRequiresForFirstOnWillRead);
url_loader_client_.RunUntilResponseBodyArrived();
ASSERT_TRUE(url_loader_client_.response_body().is_valid());
const std::string data("abcdefgh");
strcpy(io_buffer->data(), data.c_str());
ASSERT_TRUE(handler_->OnReadCompleted(data.size(), &defer));
EXPECT_TRUE(defer);
std::string contents;
while (contents.size() < data.size()) {
// This is needed for Resume to be called.
base::RunLoop().RunUntilIdle();
char buffer[16];
uint32_t read_size = sizeof(buffer);
MojoResult result =
mojo::ReadDataRaw(url_loader_client_.response_body(), buffer,
&read_size, MOJO_READ_DATA_FLAG_NONE);
if (result == MOJO_RESULT_SHOULD_WAIT)
continue;
ASSERT_EQ(MOJO_RESULT_OK, result);
contents.append(buffer, read_size);
}
EXPECT_EQ(data, contents);
EXPECT_EQ(0, resource_controller_.num_resume_calls());
}
TEST_F(MojoAsyncResourceHandlerTest,
IOBufferFromOnWillReadShouldRemainValidEvenIfHandlerIsGone) {
scoped_refptr<net::IOBuffer> io_buffer;
int io_buffer_size = 0;
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
ASSERT_TRUE(io_buffer);
// The io_buffer size that the mime sniffer requires implicitly.
ASSERT_GE(io_buffer_size, kSizeMimeSnifferRequiresForFirstOnWillRead);
handler_ = nullptr;
url_loader_client_.Unbind();
base::RunLoop().RunUntilIdle();
// Hopefully ASAN checks this operation's validity.
io_buffer->data()[0] = 'A';
}
TEST_F(MojoAsyncResourceHandlerTest, OnResponseCompleted) {
bool defer = false;
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
ResourceRequestInfoImpl::ForRequest(request_.get())
->set_was_ignored_by_handler(false);
net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, net::OK);
base::TimeTicks now1 = base::TimeTicks::Now();
handler_->OnResponseCompleted(status, &defer);
base::TimeTicks now2 = base::TimeTicks::Now();
EXPECT_FALSE(defer);
url_loader_client_.RunUntilComplete();
EXPECT_TRUE(url_loader_client_.has_received_completion());
EXPECT_EQ(net::OK, url_loader_client_.completion_status().error_code);
EXPECT_FALSE(url_loader_client_.completion_status().was_ignored_by_handler);
EXPECT_LE(now1, url_loader_client_.completion_status().completion_time);
EXPECT_LE(url_loader_client_.completion_status().completion_time, now2);
EXPECT_EQ(request_->GetTotalReceivedBytes(),
url_loader_client_.completion_status().encoded_data_length);
}
// This test case sets different status values from OnResponseCompleted.
TEST_F(MojoAsyncResourceHandlerTest, OnResponseCompleted2) {
rdh_.SetDelegate(nullptr);
bool defer = false;
// Don't use CallOnWillStartAndOnResponseStarted as this test case manually
// sets the null delegate.
ASSERT_TRUE(CallOnWillStart());
scoped_refptr<ResourceResponse> response = new ResourceResponse();
ASSERT_TRUE(handler_->OnResponseStarted(response.get(), &defer));
ASSERT_FALSE(defer);
ASSERT_FALSE(url_loader_client_.has_received_response());
url_loader_client_.RunUntilResponseReceived();
ResourceRequestInfoImpl::ForRequest(request_.get())
->set_was_ignored_by_handler(true);
net::URLRequestStatus status(net::URLRequestStatus::CANCELED,
net::ERR_ABORTED);
base::TimeTicks now1 = base::TimeTicks::Now();
handler_->OnResponseCompleted(status, &defer);
base::TimeTicks now2 = base::TimeTicks::Now();
EXPECT_FALSE(defer);
url_loader_client_.RunUntilComplete();
EXPECT_TRUE(url_loader_client_.has_received_completion());
EXPECT_EQ(net::ERR_ABORTED,
url_loader_client_.completion_status().error_code);
EXPECT_TRUE(url_loader_client_.completion_status().was_ignored_by_handler);
EXPECT_LE(now1, url_loader_client_.completion_status().completion_time);
EXPECT_LE(url_loader_client_.completion_status().completion_time, now2);
EXPECT_EQ(request_->GetTotalReceivedBytes(),
url_loader_client_.completion_status().encoded_data_length);
}
TEST_F(MojoAsyncResourceHandlerTest, OnResponseCompletedWithCanceledTimedOut) {
net::URLRequestStatus status(net::URLRequestStatus::CANCELED,
net::ERR_TIMED_OUT);
bool defer = false;
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
handler_->OnResponseCompleted(status, &defer);
EXPECT_FALSE(defer);
url_loader_client_.RunUntilComplete();
EXPECT_TRUE(url_loader_client_.has_received_completion());
EXPECT_EQ(net::ERR_TIMED_OUT,
url_loader_client_.completion_status().error_code);
}
TEST_F(MojoAsyncResourceHandlerTest, OnResponseCompletedWithFailedTimedOut) {
net::URLRequestStatus status(net::URLRequestStatus::FAILED,
net::ERR_TIMED_OUT);
bool defer = false;
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
handler_->OnResponseCompleted(status, &defer);
EXPECT_FALSE(defer);
url_loader_client_.RunUntilComplete();
EXPECT_TRUE(url_loader_client_.has_received_completion());
EXPECT_EQ(net::ERR_TIMED_OUT,
url_loader_client_.completion_status().error_code);
}
TEST_F(MojoAsyncResourceHandlerTest, ResponseCompletionShouldCloseDataPipe) {
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
scoped_refptr<net::IOBuffer> io_buffer;
int io_buffer_size = 0;
bool defer = false;
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
url_loader_client_.RunUntilResponseBodyArrived();
ASSERT_TRUE(url_loader_client_.response_body().is_valid());
ASSERT_TRUE(handler_->OnReadCompleted(0, &defer));
EXPECT_FALSE(defer);
net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, net::OK);
handler_->OnResponseCompleted(status, &defer);
EXPECT_FALSE(defer);
url_loader_client_.RunUntilComplete();
EXPECT_TRUE(url_loader_client_.has_received_completion());
EXPECT_EQ(net::OK, url_loader_client_.completion_status().error_code);
// This is needed because |*io_buffer| may keep the data producer alive.
io_buffer = nullptr;
while (true) {
char buffer[16];
uint32_t read_size = sizeof(buffer);
MojoResult result =
mojo::ReadDataRaw(url_loader_client_.response_body(), buffer,
&read_size, MOJO_READ_DATA_FLAG_NONE);
if (result == MOJO_RESULT_FAILED_PRECONDITION)
break;
ASSERT_EQ(result, MOJO_RESULT_SHOULD_WAIT);
}
}
TEST_F(MojoAsyncResourceHandlerTest, ResponseErrorDuringBodyTransmission) {
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
scoped_refptr<net::IOBuffer> io_buffer;
int io_buffer_size = 0;
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
url_loader_client_.RunUntilResponseBodyArrived();
ASSERT_TRUE(url_loader_client_.response_body().is_valid());
ASSERT_GT(io_buffer_size, 0);
memset(io_buffer->data(), 'a', io_buffer_size);
bool defer = false;
ASSERT_TRUE(handler_->OnReadCompleted(io_buffer_size, &defer));
// We don't care |defer|'s value here.
defer = false;
net::URLRequestStatus status(net::URLRequestStatus::FAILED, net::ERR_FAILED);
handler_->OnResponseCompleted(status, &defer);
EXPECT_FALSE(defer);
url_loader_client_.RunUntilComplete();
EXPECT_TRUE(url_loader_client_.has_received_completion());
EXPECT_EQ(net::ERR_FAILED, url_loader_client_.completion_status().error_code);
// This is needed because |*io_buffer| may keep the data producer alive.
io_buffer = nullptr;
std::string actual;
while (true) {
char buf[16];
uint32_t read_size = sizeof(buf);
MojoResult result =
mojo::ReadDataRaw(url_loader_client_.response_body(), buf, &read_size,
MOJO_READ_DATA_FLAG_NONE);
if (result == MOJO_RESULT_FAILED_PRECONDITION)
break;
if (result == MOJO_RESULT_SHOULD_WAIT) {
base::RunLoop().RunUntilIdle();
continue;
}
EXPECT_EQ(MOJO_RESULT_OK, result);
actual.append(buf, read_size);
}
EXPECT_EQ(std::string(io_buffer_size, 'a'), actual);
}
// In this case, an error is notified after OnWillRead, before OnReadCompleted.
TEST_F(MojoAsyncResourceHandlerTest, ResponseErrorDuringBodyTransmission2) {
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
scoped_refptr<net::IOBuffer> io_buffer;
int io_buffer_size = 0;
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
url_loader_client_.RunUntilResponseBodyArrived();
ASSERT_TRUE(url_loader_client_.response_body().is_valid());
bool defer = false;
net::URLRequestStatus status(net::URLRequestStatus::FAILED, net::ERR_FAILED);
handler_->OnResponseCompleted(status, &defer);
EXPECT_FALSE(defer);
url_loader_client_.RunUntilComplete();
EXPECT_TRUE(url_loader_client_.has_received_completion());
EXPECT_EQ(net::ERR_FAILED, url_loader_client_.completion_status().error_code);
// This is needed because |*io_buffer| may keep the data producer alive.
io_buffer = nullptr;
while (true) {
char buf[16];
uint32_t read_size = sizeof(buf);
MojoResult result =
mojo::ReadDataRaw(url_loader_client_.response_body(), buf, &read_size,
MOJO_READ_DATA_FLAG_NONE);
if (result == MOJO_RESULT_FAILED_PRECONDITION)
break;
ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, result);
base::RunLoop().RunUntilIdle();
}
}
TEST_F(MojoAsyncResourceHandlerTest, BeginWriteFailsOnWillRead) {
handler_->set_begin_write_expectation(MOJO_RESULT_UNKNOWN);
scoped_refptr<net::IOBuffer> io_buffer;
int io_buffer_size = 0;
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
ASSERT_FALSE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
EXPECT_FALSE(resource_controller_.is_cancel_with_error_called());
}
TEST_F(MojoAsyncResourceHandlerTest, BeginWriteReturnsShouldWaitOnWillRead) {
handler_->set_begin_write_expectation(MOJO_RESULT_SHOULD_WAIT);
scoped_refptr<net::IOBuffer> io_buffer;
int io_buffer_size = 0;
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
EXPECT_TRUE(io_buffer);
EXPECT_GT(io_buffer_size, 0);
}
TEST_F(MojoAsyncResourceHandlerTest,
BeginWriteReturnsShouldWaitOnWillReadAndThenReturnsOK) {
handler_->set_begin_write_expectation(MOJO_RESULT_SHOULD_WAIT);
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
size_t written = 0;
while (true) {
scoped_refptr<net::IOBuffer> io_buffer;
int io_buffer_size = 0;
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
EXPECT_TRUE(io_buffer);
EXPECT_GT(io_buffer_size, 0);
memset(io_buffer->data(), 'X', io_buffer_size);
written += io_buffer_size;
bool defer = false;
ASSERT_TRUE(handler_->OnReadCompleted(io_buffer_size, &defer));
if (defer)
break;
}
url_loader_client_.RunUntilResponseBodyArrived();
ASSERT_TRUE(url_loader_client_.response_body().is_valid());
handler_->ResetBeginWriteExpectation();
handler_->ResumeForTesting();
std::string actual;
while (actual.size() < written) {
char buf[16];
uint32_t read_size = sizeof(buf);
MojoResult result =
mojo::ReadDataRaw(url_loader_client_.response_body(), buf, &read_size,
MOJO_READ_DATA_FLAG_NONE);
ASSERT_TRUE(result == MOJO_RESULT_OK || result == MOJO_RESULT_SHOULD_WAIT);
if (result == MOJO_RESULT_OK)
actual.append(buf, read_size);
base::RunLoop().RunUntilIdle();
}
EXPECT_EQ(std::string(written, 'X'), actual);
EXPECT_EQ(1, resource_controller_.num_resume_calls());
}
TEST_F(MojoAsyncResourceHandlerTest,
EndWriteFailsOnWillReadWithInsufficientInitialCapacity) {
MojoAsyncResourceHandler::SetAllocationSizeForTesting(2);
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
handler_->set_end_write_expectation(MOJO_RESULT_UNKNOWN);
scoped_refptr<net::IOBuffer> io_buffer;
int io_buffer_size = 0;
ASSERT_FALSE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
}
TEST_F(MojoAsyncResourceHandlerTest, EndWriteFailsOnReadCompleted) {
scoped_refptr<net::IOBuffer> io_buffer;
int io_buffer_size = 0;
bool defer = false;
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
handler_->set_end_write_expectation(MOJO_RESULT_SHOULD_WAIT);
ASSERT_FALSE(handler_->OnReadCompleted(io_buffer_size, &defer));
}
TEST_F(MojoAsyncResourceHandlerTest,
EndWriteFailsOnReadCompletedWithInsufficientInitialCapacity) {
MojoAsyncResourceHandler::SetAllocationSizeForTesting(2);
scoped_refptr<net::IOBuffer> io_buffer;
int io_buffer_size = 0;
bool defer = false;
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
handler_->set_end_write_expectation(MOJO_RESULT_SHOULD_WAIT);
ASSERT_FALSE(handler_->OnReadCompleted(io_buffer_size, &defer));
}
TEST_F(MojoAsyncResourceHandlerTest,
EndWriteFailsOnResumeWithInsufficientInitialCapacity) {
MojoAsyncResourceHandler::SetAllocationSizeForTesting(8);
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
scoped_refptr<net::IOBuffer> io_buffer;
int io_buffer_size = 0;
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
url_loader_client_.RunUntilResponseBodyArrived();
ASSERT_TRUE(url_loader_client_.response_body().is_valid());
while (true) {
bool defer = false;
ASSERT_TRUE(handler_->OnReadCompleted(io_buffer_size, &defer));
ASSERT_GE(io_buffer_size, 0);
if (defer)
break;
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
}
while (true) {
char buf[16];
uint32_t read_size = sizeof(buf);
MojoResult result =
mojo::ReadDataRaw(url_loader_client_.response_body(), buf, &read_size,
MOJO_READ_DATA_FLAG_NONE);
if (result == MOJO_RESULT_SHOULD_WAIT)
break;
ASSERT_EQ(MOJO_RESULT_OK, result);
}
handler_->set_end_write_expectation(MOJO_RESULT_SHOULD_WAIT);
resource_controller_.RunUntilCancelWithErrorCalled();
EXPECT_FALSE(url_loader_client_.has_received_completion());
EXPECT_TRUE(resource_controller_.is_cancel_with_error_called());
EXPECT_EQ(net::ERR_FAILED, resource_controller_.error());
}
TEST_P(MojoAsyncResourceHandlerWithAllocationSizeTest,
OnWillReadWithLongContents) {
bool defer = false;
scoped_refptr<net::IOBuffer> io_buffer;
int io_buffer_size = 0;
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
ASSERT_TRUE(io_buffer);
// The io_buffer size that the mime sniffer requires implicitly.
ASSERT_GE(io_buffer_size, kSizeMimeSnifferRequiresForFirstOnWillRead);
std::string expected;
for (int i = 0; i < 3 * io_buffer_size + 2; ++i)
expected += ('A' + i % 26);
ASSERT_TRUE(handler_->OnReadCompleted(0, &defer));
ASSERT_FALSE(defer);
url_loader_client_.RunUntilResponseBodyArrived();
ASSERT_TRUE(url_loader_client_.response_body().is_valid());
size_t written = 0;
std::string actual;
while (actual.size() < expected.size()) {
while (written < expected.size() && !defer) {
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
const size_t to_be_written = std::min(static_cast<size_t>(io_buffer_size),
expected.size() - written);
memcpy(io_buffer->data(), &expected[written], to_be_written);
ASSERT_TRUE(handler_->OnReadCompleted(to_be_written, &defer));
written += to_be_written;
}
char buf[16];
uint32_t read_size = sizeof(buf);
MojoResult result =
mojo::ReadDataRaw(url_loader_client_.response_body(), buf, &read_size,
MOJO_READ_DATA_FLAG_NONE);
if (result != MOJO_RESULT_SHOULD_WAIT) {
ASSERT_EQ(MOJO_RESULT_OK, result);
actual.append(buf, read_size);
}
int resume_count = resource_controller_.num_resume_calls();
base::RunLoop().RunUntilIdle();
// Continue writing if controller->Resume() is called.
defer = (resume_count == resource_controller_.num_resume_calls());
}
EXPECT_EQ(expected, actual);
}
TEST_P(MojoAsyncResourceHandlerWithAllocationSizeTest,
BeginWriteFailsOnReadCompleted) {
scoped_refptr<net::IOBuffer> io_buffer;
int io_buffer_size = 0;
bool defer = false;
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
handler_->set_begin_write_expectation(MOJO_RESULT_UNKNOWN);
ASSERT_FALSE(handler_->OnReadCompleted(io_buffer_size, &defer));
}
TEST_P(MojoAsyncResourceHandlerWithAllocationSizeTest,
BeginWriteReturnsShouldWaitOnReadCompleted) {
scoped_refptr<net::IOBuffer> io_buffer;
int io_buffer_size = 0;
bool defer = false;
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
handler_->set_begin_write_expectation(MOJO_RESULT_SHOULD_WAIT);
ASSERT_TRUE(handler_->OnReadCompleted(io_buffer_size, &defer));
EXPECT_TRUE(defer);
}
TEST_P(MojoAsyncResourceHandlerWithAllocationSizeTest,
BeginWriteFailsOnResume) {
bool defer = false;
int io_buffer_size = 0;
scoped_refptr<net::IOBuffer> io_buffer;
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
ASSERT_TRUE(handler_->OnReadCompleted(0, &defer));
ASSERT_FALSE(defer);
url_loader_client_.RunUntilResponseBodyArrived();
ASSERT_TRUE(url_loader_client_.response_body().is_valid());
while (!defer) {
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
ASSERT_TRUE(handler_->OnReadCompleted(io_buffer_size, &defer));
}
handler_->set_begin_write_expectation(MOJO_RESULT_UNKNOWN);
while (!resource_controller_.is_cancel_with_error_called()) {
char buf[256];
uint32_t read_size = sizeof(buf);
MojoResult result =
mojo::ReadDataRaw(url_loader_client_.response_body(), buf, &read_size,
MOJO_READ_DATA_FLAG_NONE);
ASSERT_TRUE(result == MOJO_RESULT_OK || result == MOJO_RESULT_SHOULD_WAIT);
base::RunLoop().RunUntilIdle();
}
EXPECT_FALSE(url_loader_client_.has_received_completion());
EXPECT_EQ(net::ERR_FAILED, resource_controller_.error());
EXPECT_EQ(0, resource_controller_.num_resume_calls());
}
TEST_P(MojoAsyncResourceHandlerWithAllocationSizeTest, CancelWhileWaiting) {
bool defer = false;
ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
while (!defer) {
scoped_refptr<net::IOBuffer> io_buffer;
int io_buffer_size = 0;
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
ASSERT_TRUE(handler_->OnReadCompleted(io_buffer_size, &defer));
}
url_loader_client_.RunUntilResponseBodyArrived();
ASSERT_TRUE(url_loader_client_.response_body().is_valid());
defer = false;
net::URLRequestStatus status(net::URLRequestStatus::CANCELED,
net::ERR_ABORTED);
handler_->OnResponseCompleted(status, &defer);
ASSERT_FALSE(url_loader_client_.has_received_completion());
url_loader_client_.RunUntilComplete();
EXPECT_EQ(net::ERR_ABORTED,
url_loader_client_.completion_status().error_code);
while (true) {
char buffer[16];
uint32_t read_size = sizeof(buffer);
MojoResult result =
mojo::ReadDataRaw(url_loader_client_.response_body(), buffer,
&read_size, MOJO_READ_DATA_FLAG_NONE);
if (result == MOJO_RESULT_FAILED_PRECONDITION)
break;
base::RunLoop().RunUntilIdle();
DCHECK(result == MOJO_RESULT_SHOULD_WAIT || result == MOJO_RESULT_OK);
}
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, resource_controller_.num_resume_calls());
}
// Typically ResourceHandler methods are called in this order.
TEST_P(
MojoAsyncResourceHandlerWithAllocationSizeTest,
OnWillStartThenOnResponseStartedThenOnWillReadThenOnReadCompletedThenOnResponseCompleted) {
rdh_delegate_.set_num_on_response_started_calls_expectation(1);
bool defer = false;
ASSERT_TRUE(handler_->OnWillStart(request_->url(), &defer));
ASSERT_FALSE(defer);
scoped_refptr<ResourceResponse> response = new ResourceResponse();
ASSERT_TRUE(handler_->OnResponseStarted(response.get(), &defer));
ASSERT_FALSE(defer);
ASSERT_FALSE(url_loader_client_.has_received_response());
url_loader_client_.RunUntilResponseReceived();
int io_buffer_size = 0;
scoped_refptr<net::IOBuffer> io_buffer;
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
ASSERT_TRUE(io_buffer);
ASSERT_GT(io_buffer_size, 0);
io_buffer->data()[0] = 'A';
ASSERT_FALSE(url_loader_client_.response_body().is_valid());
url_loader_client_.RunUntilResponseBodyArrived();
ASSERT_TRUE(url_loader_client_.response_body().is_valid());
ASSERT_TRUE(handler_->OnReadCompleted(1, &defer));
ASSERT_FALSE(defer);
net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, net::OK);
handler_->OnResponseCompleted(status, &defer);
ASSERT_FALSE(defer);
ASSERT_FALSE(url_loader_client_.has_received_completion());
url_loader_client_.RunUntilComplete();
EXPECT_EQ(net::OK, url_loader_client_.completion_status().error_code);
// This is needed because |*io_buffer| may keep the data producer alive.
io_buffer = nullptr;
std::string body;
while (true) {
char buffer[16];
uint32_t read_size = sizeof(buffer);
MojoResult result =
mojo::ReadDataRaw(url_loader_client_.response_body(), buffer,
&read_size, MOJO_READ_DATA_FLAG_NONE);
if (result == MOJO_RESULT_FAILED_PRECONDITION)
break;
if (result == MOJO_RESULT_SHOULD_WAIT) {
base::RunLoop().RunUntilIdle();
} else {
ASSERT_EQ(result, MOJO_RESULT_OK);
body.append(buffer, read_size);
}
}
EXPECT_EQ("A", body);
}
// MimeResourceHandler calls delegated ResourceHandler's methods in this order.
TEST_P(
MojoAsyncResourceHandlerWithAllocationSizeTest,
OnWillStartThenOnWillReadThenOnResponseStartedThenOnReadCompletedThenOnResponseCompleted) {
rdh_delegate_.set_num_on_response_started_calls_expectation(1);
bool defer = false;
ASSERT_TRUE(handler_->OnWillStart(request_->url(), &defer));
ASSERT_FALSE(defer);
int io_buffer_size = 0;
scoped_refptr<net::IOBuffer> io_buffer;
ASSERT_TRUE(handler_->OnWillRead(&io_buffer, &io_buffer_size, -1));
ASSERT_TRUE(io_buffer);
ASSERT_GT(io_buffer_size, 0);
io_buffer->data()[0] = 'B';
ASSERT_FALSE(url_loader_client_.response_body().is_valid());
url_loader_client_.RunUntilResponseBodyArrived();
ASSERT_TRUE(url_loader_client_.response_body().is_valid());
scoped_refptr<ResourceResponse> response = new ResourceResponse();
ASSERT_TRUE(handler_->OnResponseStarted(response.get(), &defer));
ASSERT_FALSE(defer);
ASSERT_FALSE(url_loader_client_.has_received_response());
url_loader_client_.RunUntilResponseReceived();
ASSERT_TRUE(handler_->OnReadCompleted(1, &defer));
ASSERT_FALSE(defer);
net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, net::OK);
handler_->OnResponseCompleted(status, &defer);
ASSERT_FALSE(defer);
ASSERT_FALSE(url_loader_client_.has_received_completion());
url_loader_client_.RunUntilComplete();
EXPECT_EQ(net::OK, url_loader_client_.completion_status().error_code);
// This is needed because |*io_buffer| may keep the data producer alive.
io_buffer = nullptr;
std::string body;
while (true) {
char buffer[16];
uint32_t read_size = sizeof(buffer);
MojoResult result =
mojo::ReadDataRaw(url_loader_client_.response_body(), buffer,
&read_size, MOJO_READ_DATA_FLAG_NONE);
if (result == MOJO_RESULT_FAILED_PRECONDITION)
break;
if (result == MOJO_RESULT_SHOULD_WAIT) {
base::RunLoop().RunUntilIdle();
} else {
ASSERT_EQ(result, MOJO_RESULT_OK);
body.append(buffer, read_size);
}
}
EXPECT_EQ("B", body);
}
INSTANTIATE_TEST_CASE_P(MojoAsyncResourceHandlerWithAllocationSizeTest,
MojoAsyncResourceHandlerWithAllocationSizeTest,
::testing::Values(8, 32 * 2014));
} // namespace
} // namespace content