blob: ad6ed23678aaa8198a464c28f0b6b3799224e802 [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/async_resource_handler.h"
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/format_macros.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/process/process.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
#include "content/browser/loader/resource_dispatcher_host_impl.h"
#include "content/browser/loader/resource_loader.h"
#include "content/browser/loader/resource_loader_delegate.h"
#include "content/browser/loader/resource_message_filter.h"
#include "content/browser/loader/resource_request_info_impl.h"
#include "content/common/resource_messages.h"
#include "content/public/browser/resource_context.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/common/content_features.h"
#include "content/public/common/previews_state.h"
#include "content/public/common/resource_request.h"
#include "content/public/common/resource_type.h"
#include "content/public/test/mock_resource_context.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "ipc/ipc_message.h"
#include "ipc/ipc_message_macros.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "net/ssl/client_cert_store.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_job.h"
#include "net/url_request/url_request_job_factory_impl.h"
#include "net/url_request/url_request_test_job.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/page_transition_types.h"
#include "url/gurl.h"
namespace content {
namespace {
std::string GenerateHeader(size_t response_data_size) {
return base::StringPrintf(
"HTTP/1.1 200 OK\n"
"Content-type: text/html\n"
"Content-Length: %" PRIuS "\n",
response_data_size);
}
int64_t TotalReceivedBytes(size_t response_data_size) {
return response_data_size + GenerateHeader(response_data_size).size();
}
std::string GenerateData(size_t response_data_size) {
return std::string(response_data_size, 'a');
}
class TestProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler {
public:
TestProtocolHandler(size_t response_data_size)
: response_data_size_(response_data_size) {}
net::URLRequestJob* MaybeCreateJob(
net::URLRequest* request,
net::NetworkDelegate* network_delegate) const override {
return new net::URLRequestTestJob(request, network_delegate,
GenerateHeader(response_data_size_),
GenerateData(response_data_size_), true);
}
private:
size_t response_data_size_;
};
// A subclass of ResourceMessageFilter that records IPC messages that are sent.
class RecordingResourceMessageFilter : public ResourceMessageFilter {
public:
RecordingResourceMessageFilter(ResourceContext* resource_context,
net::URLRequestContext* request_context)
: ResourceMessageFilter(
0,
nullptr,
nullptr,
nullptr,
nullptr,
base::Bind(&RecordingResourceMessageFilter::GetContexts,
base::Unretained(this)),
BrowserThread::GetTaskRunnerForThread(BrowserThread::IO)),
resource_context_(resource_context),
request_context_(request_context) {
InitializeForTest();
set_peer_process_for_testing(base::Process::Current());
}
const std::vector<std::unique_ptr<IPC::Message>>& messages() const {
return messages_;
}
// IPC::Sender implementation:
bool Send(IPC::Message* message) override {
// Unpickle the base::SharedMemoryHandle to avoid warnings about
// "MessageAttachmentSet destroyed with unconsumed descriptors".
if (message->type() == ResourceMsg_SetDataBuffer::ID) {
ResourceMsg_SetDataBuffer::Param params;
ResourceMsg_SetDataBuffer::Read(message, &params);
}
messages_.push_back(base::WrapUnique(message));
return true;
}
private:
~RecordingResourceMessageFilter() override {}
void GetContexts(ResourceType resource_type,
ResourceContext** resource_context,
net::URLRequestContext** request_context) {
*resource_context = resource_context_;
*request_context = request_context_;
}
ResourceContext* const resource_context_;
net::URLRequestContext* const request_context_;
std::vector<std::unique_ptr<IPC::Message>> messages_;
};
class AsyncResourceHandlerTest : public ::testing::Test,
public ResourceLoaderDelegate {
protected:
AsyncResourceHandlerTest()
: thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP), context_(true) {}
void TearDown() override {
if (filter_)
filter_->OnChannelClosing();
// Prevent memory leaks.
filter_ = nullptr;
rdh_.Shutdown();
base::RunLoop().RunUntilIdle();
}
void CreateRequestWithResponseDataSize(size_t response_data_size) {
test_job_factory_.SetProtocolHandler(
"test", base::MakeUnique<TestProtocolHandler>(response_data_size));
context_.set_job_factory(&test_job_factory_);
context_.Init();
std::unique_ptr<net::URLRequest> request =
context_.CreateRequest(GURL("test:test"), net::DEFAULT_PRIORITY,
nullptr, TRAFFIC_ANNOTATION_FOR_TESTS);
resource_context_ = base::MakeUnique<MockResourceContext>(&context_);
filter_ =
new RecordingResourceMessageFilter(resource_context_.get(), &context_);
ResourceRequestInfoImpl* info = new ResourceRequestInfoImpl(
filter_->requester_info_for_test(),
0, // route_id
-1, // frame_tree_node_id
0, // origin_pid
0, // request_id
0, // render_frame_id
false, // is_main_frame
false, // parent_is_main_frame
RESOURCE_TYPE_IMAGE, // resource_type
ui::PAGE_TRANSITION_LINK, // transition_type
false, // should_replace_current_entry
false, // is_download
false, // is_stream
false, // allow_download
false, // has_user_gesture
false, // enable load timing
false, // enable upload progress
false, // do_not_prompt_for_login
false, // keep_alive
blink::kWebReferrerPolicyDefault, // referrer_policy
blink::kWebPageVisibilityStateVisible, // visibility_state
resource_context_.get(), // context
false, // report_raw_headers
true, // is_async
PREVIEWS_OFF, // previews_state
nullptr, // body
false); // initiated_in_secure_context
info->AssociateWithRequest(request.get());
std::unique_ptr<AsyncResourceHandler> handler =
base::MakeUnique<AsyncResourceHandler>(request.get(), &rdh_);
loader_ = base::MakeUnique<ResourceLoader>(
std::move(request), std::move(handler), this);
}
void StartRequestAndWaitWithResponseDataSize(size_t response_data_size) {
CreateRequestWithResponseDataSize(response_data_size);
loader_->StartRequest();
finish_waiter_.reset(new base::RunLoop);
finish_waiter_->Run();
}
scoped_refptr<RecordingResourceMessageFilter> filter_;
private:
// ResourceLoaderDelegate implementation:
ResourceDispatcherHostLoginDelegate* CreateLoginDelegate(
ResourceLoader* loader,
net::AuthChallengeInfo* auth_info) override {
return nullptr;
}
bool HandleExternalProtocol(ResourceLoader* loader,
const GURL& url) override {
return false;
}
void DidStartRequest(ResourceLoader* loader) override {}
void DidReceiveRedirect(ResourceLoader* loader,
const GURL& new_url,
ResourceResponse* response) override {}
void DidReceiveResponse(ResourceLoader* loader,
ResourceResponse* response) override {}
void DidFinishLoading(ResourceLoader* loader) override {
loader_.reset();
finish_waiter_->Quit();
}
std::unique_ptr<net::ClientCertStore> CreateClientCertStore(
ResourceLoader* loader) override {
return nullptr;
}
TestBrowserThreadBundle thread_bundle_;
ResourceDispatcherHostImpl rdh_;
net::TestURLRequestContext context_;
net::URLRequestJobFactoryImpl test_job_factory_;
std::unique_ptr<MockResourceContext> resource_context_;
std::unique_ptr<ResourceLoader> loader_;
std::unique_ptr<base::RunLoop> finish_waiter_;
};
TEST_F(AsyncResourceHandlerTest, Construct) {
CreateRequestWithResponseDataSize(1);
}
TEST_F(AsyncResourceHandlerTest, OneChunkLengths) {
// Larger than kInlinedLeadingChunkSize and smaller than
// kMaxAllocationSize.
constexpr auto kDataSize = 4096;
StartRequestAndWaitWithResponseDataSize(kDataSize);
const auto& messages = filter_->messages();
ASSERT_EQ(4u, messages.size());
ASSERT_EQ(ResourceMsg_DataReceived::ID, messages[2]->type());
ResourceMsg_DataReceived::Param params;
ResourceMsg_DataReceived::Read(messages[2].get(), &params);
int encoded_data_length = std::get<3>(params);
EXPECT_EQ(kDataSize, encoded_data_length);
ASSERT_EQ(ResourceMsg_RequestComplete::ID, messages[3]->type());
ResourceMsg_RequestComplete::Param completion_params;
ResourceMsg_RequestComplete::Read(messages[3].get(), &completion_params);
ResourceRequestCompletionStatus completion_status =
std::get<1>(completion_params);
EXPECT_EQ(TotalReceivedBytes(kDataSize),
completion_status.encoded_data_length);
EXPECT_EQ(kDataSize, completion_status.encoded_body_length);
}
TEST_F(AsyncResourceHandlerTest, TwoChunksLengths) {
// Larger than kMaxAllocationSize.
constexpr auto kDataSize = 64 * 1024;
StartRequestAndWaitWithResponseDataSize(kDataSize);
const auto& messages = filter_->messages();
ASSERT_EQ(5u, messages.size());
ASSERT_EQ(ResourceMsg_DataReceived::ID, messages[2]->type());
ResourceMsg_DataReceived::Param params;
ResourceMsg_DataReceived::Read(messages[2].get(), &params);
int encoded_data_length = std::get<3>(params);
EXPECT_EQ(32768, encoded_data_length);
ASSERT_EQ(ResourceMsg_DataReceived::ID, messages[3]->type());
ResourceMsg_DataReceived::Read(messages[3].get(), &params);
encoded_data_length = std::get<3>(params);
EXPECT_EQ(32768, encoded_data_length);
ASSERT_EQ(ResourceMsg_RequestComplete::ID, messages[4]->type());
ResourceMsg_RequestComplete::Param completion_params;
ResourceMsg_RequestComplete::Read(messages[4].get(), &completion_params);
ResourceRequestCompletionStatus completion_status =
std::get<1>(completion_params);
EXPECT_EQ(TotalReceivedBytes(kDataSize),
completion_status.encoded_data_length);
EXPECT_EQ(kDataSize, completion_status.encoded_body_length);
}
} // namespace
} // namespace content