| // 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/common/resource_request.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_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/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))), |
| 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, ¶ms); |
| } |
| 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); |
| 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 |
| blink::WebReferrerPolicyDefault, // referrer_policy |
| blink::WebPageVisibilityStateVisible, // 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(), ¶ms); |
| |
| 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, InlinedChunkLengths) { |
| // TODO(ricea): Remove this Feature-enabling code once the feature is on by |
| // default. |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature( |
| features::kOptimizeLoadingIPCForSmallResources); |
| |
| // Smaller than kInlinedLeadingChunkSize. |
| constexpr auto kDataSize = 8; |
| StartRequestAndWaitWithResponseDataSize(kDataSize); |
| const auto& messages = filter_->messages(); |
| ASSERT_EQ(3u, messages.size()); |
| ASSERT_EQ(ResourceMsg_InlinedDataChunkReceived::ID, messages[1]->type()); |
| ResourceMsg_InlinedDataChunkReceived::Param params; |
| ResourceMsg_InlinedDataChunkReceived::Read(messages[1].get(), ¶ms); |
| |
| int encoded_data_length = std::get<2>(params); |
| EXPECT_EQ(kDataSize, encoded_data_length); |
| |
| ASSERT_EQ(ResourceMsg_RequestComplete::ID, messages[2]->type()); |
| ResourceMsg_RequestComplete::Param completion_params; |
| ResourceMsg_RequestComplete::Read(messages[2].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(), ¶ms); |
| |
| 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(), ¶ms); |
| |
| 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 |